File Coverage

blib/lib/MooseX/App/Meta/Role/Class/Base.pm
Criterion Covered Total %
statement 414 434 95.3
branch 132 152 86.8
condition 72 116 62.0
subroutine 33 33 100.0
pod 19 21 90.4
total 670 756 88.6


line stmt bran cond sub pod time code
1             # ============================================================================
2             package MooseX::App::Meta::Role::Class::Base;
3             # ============================================================================
4              
5 14     14   34923 use utf8;
  14         22  
  14         96  
6 14     14   652 use 5.010;
  14         39  
7              
8 14     14   57 use List::Util qw(max);
  14         23  
  14         1016  
9              
10 14     14   64 use namespace::autoclean;
  14         22  
  14         100  
11 14     14   1536 use Moose::Role;
  14         30  
  14         100  
12              
13 14     14   56014 use MooseX::App::Utils;
  14         27  
  14         355  
14 14     14   4228 use Path::Class;
  14         262705  
  14         808  
15 14     14   4165 use Module::Pluggable::Object;
  14         45527  
  14         694  
16 14     14   79 no if $] >= 5.018000, warnings => qw(experimental::smartmatch);
  14         21  
  14         117  
17              
18             has 'app_messageclass' => (
19             is => 'rw',
20             isa => 'ClassName',
21             lazy => 1,
22             builder => '_build_app_messageclass',
23             );
24              
25             has 'app_namespace' => (
26             is => 'rw',
27             isa => 'MooseX::App::Types::List',
28             coerce => 1,
29             lazy => 1,
30             builder => '_build_app_namespace',
31             );
32              
33             has 'app_exclude' => (
34             is => 'rw',
35             isa => 'MooseX::App::Types::List',
36             coerce => 1,
37             default => sub { [] },
38             );
39              
40             has 'app_base' => (
41             is => 'rw',
42             isa => 'Str',
43             default => sub { Path::Class::File->new($0)->basename },
44             );
45              
46             has 'app_strict' => (
47             is => 'rw',
48             isa => 'Bool',
49             default => sub {0},
50             );
51              
52             has 'app_fuzzy' => (
53             is => 'rw',
54             isa => 'Bool',
55             default => sub {1},
56             );
57              
58             has 'app_command_name' => (
59             is => 'rw',
60             isa => 'CodeRef',
61             default => sub { \&MooseX::App::Utils::class_to_command },
62             );
63              
64             has 'app_prefer_commandline' => (
65             is => 'rw',
66             isa => 'Bool',
67             default => sub {0},
68             );
69              
70             has 'app_permute' => (
71             is => 'rw',
72             isa => 'Bool',
73             default => sub {0},
74             );
75              
76             has 'app_commands' => (
77             is => 'rw',
78             isa => 'HashRef[Str]',
79             traits => ['Hash'],
80             handles => {
81             command_register => 'set',
82             command_get => 'get',
83             command_classes => 'values',
84             command_list => 'shallow_clone',
85             },
86             lazy => 1,
87             builder => '_build_app_commands',
88             );
89              
90             sub _build_app_messageclass {
91 13     13   27 my ($self) = @_;
92 13         395 return 'MooseX::App::Message::Block'
93             }
94              
95             sub _build_app_namespace {
96 10     10   19 my ($self) = @_;
97 10         322 return [ $self->name ];
98             }
99              
100             sub _build_app_commands {
101 10     10   19 my ($self) = @_;
102              
103 10         15 my (@list);
104             # Process namespace list
105 10         16 foreach my $namespace ( @{ $self->app_namespace } ) {
  10         321  
106 10         41 push(@list,$self->command_scan_namespace($namespace));
107             }
108 10         34 my $commands = { @list };
109              
110             # Process excludes
111 10         16 foreach my $exclude ( @{ $self->app_exclude } ) {
  10         379  
112 3         7 foreach my $command (keys %{$commands}) {
  3         9  
113             delete $commands->{$command}
114 21 100       96 if $commands->{$command} =~ m/^\Q$exclude\E(::|$)/;
115             }
116             }
117              
118 10         236 return $commands;
119             }
120              
121             sub command_check {
122 68     68 1 133 my ($self) = @_;
123              
124 68         310 foreach my $attribute ($self->command_usage_attributes($self,'all')) {
125 151         569 $attribute->cmd_check();
126             }
127 68         166 return;
128             }
129              
130             sub command_scan_namespace {
131 10     10 1 21 my ($self,$namespace) = @_;
132              
133             # Find all packages in namespace
134 10         121 my $mpo = Module::Pluggable::Object->new(
135             search_path => [ $namespace ],
136             );
137              
138 10         381 my $commandsub = $self->app_command_name;
139              
140 10         15 my %return;
141             # Loop all packages
142 10         41 foreach my $command_class ($mpo->plugins) {
143 42         12593 my $command_class_name = substr($command_class,length($namespace)+2);
144              
145             # subcommands support
146 42         56 $command_class_name =~ s/::/ /g;
147              
148             # Extract command name
149 42         160 $command_class_name =~ s/^\Q$namespace\E:://;
150 42         39 $command_class_name =~ s/^.+::([^:]+)$/$1/;
151 42         86 my $command = $commandsub->($command_class_name,$command_class);
152              
153             # Check if command was loaded
154 42 50       166 $return{$command} = $command_class
155             if defined $command;
156             }
157              
158 10         928 return %return;
159             }
160              
161             sub command_args {
162 68     68 1 105 my ($self,$metaclass) = @_;
163              
164 68   33     181 $metaclass ||= $self;
165 68         282 my $parsed_argv = MooseX::App::ParsedArgv->instance;
166              
167 68 50       260 unless ($metaclass->does_role('MooseX::App::Role::Common')) {
168 0         0 Moose->throw_error('Class '.$metaclass->name.' is not a proper MooseX::App::Command class. You either need to use MooseX::App::Command or exclude this class via app_exclude')
169             }
170              
171             # Process options
172 68         18759 my @attributes_option = $self->command_usage_attributes($metaclass,'option');
173              
174 68         289 my ($return,$errors) = $self->command_parse_options(\@attributes_option);
175              
176 67         133 my %raw_error;
177             # Loop all left over options
178 67         263 foreach my $option ($parsed_argv->available('option')) {
179 2         73 my $key = $option->key;
180 2         12 my $raw = $option->original;
181 2         5 my $message;
182             next
183 2 50       7 if defined $raw_error{$raw};
184              
185             # Get possible options with double dash - might be missing
186 2 50 33     10 if (length $key == 1
187             && $raw =~ m/^-(\w+)$/) {
188             POSSIBLE_ATTRIBUTES:
189 0         0 foreach my $attribute ($self->command_usage_attributes($metaclass,[qw(option proto)])) {
190 0         0 foreach my $name ($attribute->cmd_name_possible) {
191             # TODO fuzzy match
192 0 0       0 if ($name eq $1) {
193 0         0 $raw_error{$raw} = 1;
194 0         0 $message = "Did you mean '--$name'?";
195 0         0 last POSSIBLE_ATTRIBUTES;
196             }
197             }
198             }
199             }
200              
201             # Handle error messages
202 2         4 my $error;
203 2 50       7 if (defined $message) {
204 0         0 $error = $self->command_message(
205             header => "Unknown option '".$raw."'", # LOCALIZE
206             body => $message,
207             type => "error",
208             );
209             } else {
210 2         68 $error = $self->command_message(
211             header => "Unknown option '".$option->key."'", # LOCALIZE
212             type => "error",
213             );
214             }
215 2         5 unshift(@{$errors},$error);
  2         8  
216             }
217              
218             # Process positional parameters
219 67         496 my @attributes_parameter = $self->command_usage_attributes($metaclass,'parameter');
220              
221 67         179 foreach my $attribute (@attributes_parameter) {
222 36         137 my $element = $parsed_argv->consume('parameter');
223             last
224 36 100       84 unless defined $element;
225              
226 22         530 my ($parameter_value,$parameter_errors) = $self->command_process_attribute($attribute, [ $element->key ] );
227 22         39 push(@{$errors},@{$parameter_errors});
  22         23  
  22         25  
228 22         99 $return->{$attribute->name} = $parameter_value;
229             }
230              
231             # Handle all unconsumed parameters and options
232 67 100 66     2213 if ($self->app_strict || $metaclass->command_strict) {
233 33         132 foreach my $parameter ($parsed_argv->available('parameter')) {
234 2         3 unshift(@{$errors},
  2         59  
235             $self->command_message(
236             header => "Unknown parameter '".$parameter->key."'", # LOCALIZE
237             type => "error",
238             )
239             );
240             }
241             }
242              
243             # Handle ENV
244 67         233 foreach my $attribute ($self->command_usage_attributes($metaclass,'all')) {
245             next
246 373 100 66     12387 unless $attribute->can('has_cmd_env')
247             && $attribute->has_cmd_env;
248              
249 27         811 my $cmd_env = $attribute->cmd_env;
250              
251 27 100 100     152 if (exists $ENV{$cmd_env}
252             && ! defined $return->{$attribute->name}) {
253              
254 3         7 my $value = $ENV{$cmd_env};
255              
256 3 50       92 if ($attribute->has_type_constraint) {
257 3         81 my $type_constraint = $attribute->type_constraint;
258 3 100 66     88 if ($attribute->should_coerce
259             && $type_constraint->has_coercion) {
260 1         60 my $coercion = $type_constraint->coercion;
261 1   33     10 $value = $coercion->coerce($value) // $value;
262             }
263             }
264              
265 3         90 $return->{$attribute->name} = $value;
266 3         11 my $error = $attribute->cmd_type_constraint_check($value);
267 3 100       11 if ($error) {
268 1         2 push(@{$errors},
  1         9  
269             $self->command_message(
270             header => "Invalid environment value for '".$cmd_env."'", # LOCALIZE
271             type => "error",
272             body => $error,
273             )
274             );
275             }
276             }
277             }
278              
279 67         413 return ($return,$errors);
280             }
281              
282             sub command_proto {
283 74     74 1 135 my ($self,$metaclass) = @_;
284              
285 74   33     182 $metaclass ||= $self;
286              
287 74         129 my @attributes;
288 74         305 foreach my $attribute ($self->command_usage_attributes($metaclass,'proto')) {
289             next
290 94 50 33     305 unless $attribute->does('MooseX::App::Meta::Role::Attribute::Option')
291             && $attribute->has_cmd_type;
292 94         239 push(@attributes,$attribute);
293             }
294              
295 74         331 return $self->command_parse_options(\@attributes);
296             }
297              
298             sub command_parse_options {
299 142     142 1 209 my ($self,$attributes) = @_;
300              
301             # Build attribute lookup hash
302 142         201 my %option_to_attribute;
303 142         176 foreach my $attribute (@{$attributes}) {
  142         282  
304 331         962 foreach my $name ($attribute->cmd_name_possible) {
305 566 100 66     1339 if (defined $option_to_attribute{$name}
306             && $option_to_attribute{$name} != $attribute) {
307 1         8 Moose->throw_error('Command line option conflict: '.$name);
308             }
309 565         934 $option_to_attribute{$name} = $attribute;
310             }
311             }
312              
313 141         276 my $match = {};
314 141         235 my $return = {};
315 141         167 my @errors;
316              
317             # Get ARGV
318 141         664 my $parsed_argv = MooseX::App::ParsedArgv->instance;
319              
320             # Loop all exact matches
321 141         537 foreach my $option ($parsed_argv->available('option')) {
322 199 100       5286 if (my $attribute = $option_to_attribute{$option->key}) {
323 87         499 $option->consume($attribute);
324 87         419 $match->{$attribute->name} = [ $option ];
325             }
326             }
327              
328             # Process fuzzy matches
329 141 100       4286 if ($self->app_fuzzy) {
330             # Loop all options (sorted by length)
331 137         443 foreach my $option (sort { length($b->key) <=> length($a->key) } $parsed_argv->available('option')) {
  72         1617  
332              
333             # No fuzzy matching for one-letter flags
334 107         2688 my $option_length = length($option->key);
335             next
336 107 100       269 if $option_length == 1;
337              
338 103         166 my ($match_attributes) = [];
339              
340             # Try to match attributes
341 103         300 foreach my $name (keys %option_to_attribute) {
342             next
343 456 100       639 if ($option_length >= length($name));
344              
345 111         177 my $name_short = lc(substr($name,0,$option_length));
346              
347             # Partial match
348 111 100       2640 if (lc($option->key) eq $name_short) {
349 16         21 my $attribute = $option_to_attribute{$name};
350 16 50       24 unless (grep { $attribute == $_ } @{$match_attributes}) {
  1         5  
  16         45  
351 16         15 push(@{$match_attributes},$attribute);
  16         33  
352             }
353             }
354             }
355              
356             # Process matches
357 103         117 given (scalar @{$match_attributes}) {
  103         142  
358             # No match
359 103         250 when(0) {}
360             # One match
361 15         25 when(1) {
362 14         18 my $attribute = $match_attributes->[0];
363 14         40 $option->consume();
364 14   100     77 $match->{$attribute->name} ||= [];
365 14         17 push(@{$match->{$attribute->name}},$option);
  14         68  
366             }
367             # Multiple matches
368 1         2 default {
369 1         7 $option->consume();
370             push(@errors,
371             $self->command_message(
372             header => "Ambiguous option '".$option->key."'", # LOCALIZE
373             type => "error",
374             body => "Could be\n".MooseX::App::Utils::format_list( # LOCALIZE
375 2         9 map { [ $_ ] }
376             sort
377 2         5 map { $_->cmd_name_primary }
378 1         24 @{$match_attributes}
  1         2  
379             ),
380             )
381             );
382             }
383             }
384             }
385             }
386              
387             # Check all attributes
388 141         195 foreach my $attribute (@{$attributes}) {
  141         246  
389              
390             next
391 329 100       1083 unless exists $match->{$attribute->name};
392              
393 93         122 my @mapped_values;
394 93         111 foreach my $element (@{$match->{$attribute->name}}) {
  93         366  
395 98         386 push(@mapped_values,$element->all_values);
396             }
397              
398             my $values = [
399 108         2824 map { $_->value }
400 93         233 sort { $a->position <=> $b->position }
  21         460  
401             @mapped_values
402             ];
403              
404             #warn Data::Dumper::Dumper($raw);
405 93         353 my ($value,$errors) = $self->command_process_attribute( $attribute, $values );
406 93         126 push(@errors,@{$errors});
  93         136  
407              
408 93         479 $return->{$attribute->name} = $value;
409             }
410              
411 141         756 return ($return,\@errors);
412             }
413              
414             sub command_process_attribute {
415 115     115 1 151 my ($self,$attribute,$raw) = @_;
416              
417 115 50       331 $raw = [ $raw ]
418             unless ref($raw) eq 'ARRAY';
419              
420 115         113 my @errors;
421             my $value;
422              
423             # Attribute with split
424 115 100       3766 if ($attribute->has_cmd_split) {
425 1         2 my @raw_unfolded;
426 1         3 foreach (@{$raw}) {
  1         2  
427 2         52 push(@raw_unfolded,split($attribute->cmd_split,$_));
428             }
429 1         3 $raw = \@raw_unfolded;
430             }
431              
432             # Attribute with counter - transform value count into value
433 115 100       3269 if ($attribute->cmd_count) {
434 1         3 $value = $raw = [ scalar(@$raw) ];
435             }
436              
437             # Attribute with type constraint
438 115 100       3711 if ($attribute->has_type_constraint) {
439 113         3329 my $type_constraint = $attribute->type_constraint;
440              
441 113 100       850 if ($type_constraint->is_a_type_of('ArrayRef')) {
    100          
    100          
442 5         540 $value = $raw;
443             } elsif ($type_constraint->is_a_type_of('HashRef')) {
444 4         1870 $value = {};
445 4         7 foreach my $element (@{$raw}) {
  4         12  
446 6 100       29 if ($element =~ m/^([^=]+)=(.+?)$/) {
447 5   33     30 $value->{$1} ||= $2;
448             } else {
449 1         4 push(@errors,
450             $self->command_message(
451             header => "Invalid value for '".$attribute->cmd_name_primary."'", # LOCALIZE
452             type => "error",
453             body => "Value must be supplied as 'key=value' (not '$element')", # LOCALIZE
454             )
455             );
456             }
457             }
458             } elsif ($type_constraint->is_a_type_of('Bool')) {
459 26         15103 $value = $raw->[-1];
460              
461             # if ($self->has_default
462             # && ! $self->is_default_a_coderef
463             # && $self->default == 1) {
464              
465             } else {
466 78         104833 $value = $raw->[-1];
467             }
468              
469 113 100       242 unless(defined $value) {
470 2         21 push(@errors,
471             $self->command_message(
472             header => "Missing value for '".$attribute->cmd_name_primary."'", # LOCALIZE
473             type => "error",
474             )
475             );
476             } else {
477 111 100 66     3362 if ($attribute->should_coerce
478             && $type_constraint->has_coercion) {
479 5         388 my $coercion = $type_constraint->coercion;
480 5   33     420 $value = $coercion->coerce($value) // $value;
481             }
482 111         2162 my $error = $attribute->cmd_type_constraint_check($value);
483 111 100       371 if (defined $error) {
484 6         26 push(@errors,
485             $self->command_message(
486             header => "Invalid value for '".$attribute->cmd_name_primary."'", # LOCALIZE
487             type => "error",
488             body => $error,
489             )
490             );
491             }
492             }
493              
494             } else {
495 2         6 $value = $raw->[-1];
496             }
497              
498 115         342 return ($value,\@errors);
499             }
500              
501             sub command_candidates {
502 45     45 1 58 my ($self,$command) = @_;
503              
504 45         73 my $lc_command = lc($command);
505 45         1264 my $commands = $self->app_commands;
506              
507 45         53 my @candidates;
508 45         54 my $candidate_length = length($command);
509              
510             # Compare all commands to find matching candidates
511 45         136 foreach my $command_name (keys %$commands) {
512 318 100       581 if ($command_name eq $lc_command) {
    100          
513 1         5 return $command_name;
514             } elsif ($lc_command eq substr($command_name,0,$candidate_length)) {
515 32         54 push(@candidates,$command_name);
516             }
517             }
518              
519 44         147 return [ sort @candidates ];
520             }
521              
522             sub command_find {
523 64     64 1 115 my ($self,$commands) = @_;
524              
525 64         211 my $parsed_argv = MooseX::App::ParsedArgv->instance;
526 64         2122 my $all_commands = $self->app_commands;
527              
528             # Get parts
529 64         120 my (@parts,@command_parts);
530 64 100       184 if (defined $commands) {
531 1 50       3 if (ref($commands) eq 'ARRAY') {
532 0         0 @parts = map { lc } @{$commands};
  0         0  
  0         0  
533             } else {
534 1         4 @parts = ( lc($commands) );
535             }
536             } else {
537 63         2046 @parts = $parsed_argv->elements_argv;
538             }
539              
540             # Extract possible parts
541 64         190 foreach my $part (@parts) {
542             # Anyting staring with a dash cannot be a command
543             last
544 143 100       444 if $part =~ m/^-/;
545 87         183 push(@command_parts,lc($part));
546             }
547              
548             # Shortcut
549             return
550 64 50       198 unless scalar @command_parts;
551              
552             # basically do a longest-match search
553 64         250 for my $index (reverse(0..$#command_parts)) {
554 85         285 my $command = join ' ', @command_parts[0..$index];
555 85 100       297 if( $all_commands->{$command} ) {
556 36         255 $parsed_argv->shift_argv for 0..$index;
557 36         146 return $command;
558             }
559             }
560              
561             # didn't find an exact match, let's go to plan B
562 28         87 foreach my $index (reverse(0..$#command_parts)) {
563 42         100 my $command = join ' ', @command_parts[0..$index];
564 42         125 my $candidate = $self->command_candidates($command);
565 42 50       145 if (ref $candidate eq '') {
566 0         0 $parsed_argv->shift_argv;
567 0         0 return $candidate;
568             }
569 42         40 given (scalar @{$candidate}) {
  42         54  
570 42         91 when (0) {
571 15         22 next;
572             }
573 27         50 when (1) {
574 27 100       905 if ($self->app_fuzzy) {
575 26         98 $parsed_argv->shift_argv;
576 26         111 return $candidate->[0];
577             } else {
578 1         9 return $self->command_message(
579             header => "Unknown command '$command'", # LOCALIZE
580             type => "error",
581             body => "Did you mean '".$candidate->[0]."'?", # LOCALIZE
582             );
583             }
584             }
585 0         0 default {
586             return $self->command_message(
587             header => "Ambiguous command '$command'", # LOCALIZE
588             type => "error",
589             body => "Which command did you mean?\n". # LOCALIZE
590 0         0 MooseX::App::Utils::format_list(map { [ $_ ] } sort @{$candidate}),
  0         0  
  0         0  
591             );
592             }
593             }
594             }
595              
596 1         2 my $command = $command_parts[0];
597 1         7 return $self->command_message(
598             header => "Unknown command '$command'", # LOCALIZE
599             type => "error",
600             );
601             }
602              
603             sub command_parser_hints {
604 74     74 1 117 my ($self,$metaclass) = @_;
605              
606 74   33     215 $metaclass ||= $self;
607              
608 74         121 my %hints;
609             my %names;
610 74         401 my $return = { permute => [], novalue => [], fixedvalue => {} };
611 74         340 foreach my $attribute ($self->command_usage_attributes($metaclass,[qw(option proto)])) {
612 347         394 my $permute = 0;
613 347         328 my $bool = 0;
614 347         9304 my $type_constraint = $attribute->type_constraint;
615 347 100       2213 if ($type_constraint) {
616 329 100 100     1696 $permute = 1
617             if $type_constraint->is_a_type_of('ArrayRef')
618             || $type_constraint->is_a_type_of('HashRef');
619              
620 329 100       224596 $bool = 1
621             if $type_constraint->is_a_type_of('Bool');
622             }
623              
624 347   100     96951 my $hint = {
625             name => $attribute->name,
626             bool => $bool,
627             novalue => $bool || $attribute->cmd_count,
628             permute => $permute,
629             };
630              
631 347         1020 foreach my $name ($attribute->cmd_name_list) {
632 569         1151 $names{$name} = $hints{$name} = $hint;
633             }
634              
635             # Negated values
636 347 100       5978 if ($bool) {
    100          
637 144         240 $hint->{fixedvalue} = 1;
638 144 100       4935 if ($attribute->has_cmd_negate) {
639 11         17 my $hint_neg = { %{$hint} }; # shallow copy
  11         50  
640 11         24 $hint_neg->{fixedvalue} = 0;
641 11         15 foreach my $name (@{$attribute->cmd_negate}) {
  11         306  
642 14         45 $names{$name} = $hints{$name} = $hint_neg;
643             }
644             }
645             } elsif ($attribute->cmd_count) {
646 6         17 $hint->{fixedvalue} = 1;
647             }
648             }
649              
650 74 100       2391 if ($self->app_fuzzy) {
651 72   50     326 my $length = max(map { length($_) } keys %names) // 0;
  563         968  
652 72         289 foreach my $l (reverse(2..$length)) {
653 625         417 my %tmp;
654 625         961 foreach my $name (keys %names) {
655             next
656 4976 100       5943 if length($name) < $l;
657 2244         1891 my $short_name = substr($name,0,$l);
658             next
659 2244 100       2762 if defined $hints{$short_name};
660 1827   100     4774 $tmp{$short_name} ||= [];
661             next
662             if defined $tmp{$short_name}->[0]
663 1827 50 66     3323 && $tmp{$short_name}->[0]->{name} eq $names{$name}->{name};
664 1827         1108 push(@{$tmp{$short_name}},$names{$name})
  1827         2493  
665             }
666 625         1090 foreach my $short_name (keys %tmp) {
667             next
668 1575 100       980 if scalar @{$tmp{$short_name}} > 1;
  1575         2256  
669 1338         1772 $hints{$short_name} = $tmp{$short_name}->[0];
670             }
671             }
672             }
673              
674 74         417 foreach my $name (keys %hints) {
675 1920 100       2428 if ($hints{$name}->{novalue}) {
676 1096         692 push(@{$return->{novalue}},$name);
  1096         1306  
677             }
678 1920 100       2374 if ($hints{$name}->{permute}) {
679 97         75 push(@{$return->{permute}},$name);
  97         118  
680             }
681 1920 100       2388 if (defined $hints{$name}->{fixedvalue}) {
682 1096         1315 $return->{fixedvalue}{$name} = $hints{$name}->{fixedvalue};
683             }
684             }
685              
686              
687             #warn Data::Dumper::Dumper($return);
688 74         739 return $return;
689             }
690              
691             sub command_message {
692 159     159 1 495 my ($self,@args) = @_;
693 159         5386 my $messageclass = $self->app_messageclass;
694 159         648 Class::Load::load_class($messageclass);
695 159         9770 return $messageclass->new(@args);
696             }
697              
698             sub command_check_attributes {
699 67     67 1 143 my ($self,$command_meta,$errors,$params) = @_;
700              
701 67   33     217 $command_meta ||= $self;
702              
703             # Check required values
704 67         294 foreach my $attribute ($self->command_usage_attributes($command_meta,[qw(option proto parameter)])) {
705 373 100 100     12892 if ($attribute->is_required
      100        
706             && ! exists $params->{$attribute->name}
707             && ! $attribute->has_default) {
708 7 100       162 push(@{$errors},
  7         237  
709             $self->command_message(
710             header => "Required ".($attribute->cmd_type eq 'parameter' ? 'parameter':'option')." '".$attribute->cmd_name_primary."' missing", # LOCALIZE
711             type => "error",
712             )
713             );
714             }
715             }
716              
717 67         485 return $errors;
718             }
719              
720             sub command_usage_attributes {
721 615     615 1 1150 my ($self,$metaclass,$types) = @_;
722              
723 615   66     1407 $metaclass ||= $self;
724 615   100     1209 $types ||= [qw(option proto)];
725              
726 615 50       2163 unless ($metaclass->does_role('MooseX::App::Role::Common')) {
727 0         0 Moose->throw_error('Class '.$metaclass->name.' is not a proper MooseX::App::Command class. You either need to use MooseX::App::Command or exclude this class via app_exclude')
728             }
729              
730 615         165901 my @return;
731 615         2142 foreach my $attribute ($metaclass->get_all_attributes) {
732             next
733 4541 100 100     73188 unless $attribute->does('MooseX::App::Meta::Role::Attribute::Option')
734             && $attribute->has_cmd_type;
735              
736             next
737 3141 100 100     76076 unless $types eq 'all'
738             || $attribute->cmd_type ~~ $types;
739              
740 1949         3205 push(@return,$attribute);
741             }
742              
743             return (sort {
744 615 50       11090 $a->cmd_position <=> $b->cmd_position ||
  2755         76159  
745             $a->cmd_usage_name cmp $b->cmd_usage_name
746             } @return);
747             }
748              
749             sub command_usage_options {
750 35     35 1 77 my ($self,$metaclass,$headline) = @_;
751              
752 35   50     99 $headline ||= 'options:'; # LOCALIZE
753 35   33     99 $metaclass ||= $self;
754              
755 35         51 my @options;
756 35         140 foreach my $attribute ($self->command_usage_attributes($metaclass,[qw(option proto)])) {
757 172         570 push(@options,[
758             $attribute->cmd_usage_name(),
759             $attribute->cmd_usage_description()
760             ]);
761             }
762              
763             return
764 35 50       185 unless scalar @options > 0;
765              
766 35         201 return $self->command_message(
767             header => $headline,
768             body => MooseX::App::Utils::format_list(@options),
769             );
770             }
771              
772             sub command_usage_parameters {
773 35     35 1 85 my ($self,$metaclass,$headline) = @_;
774              
775 35   50     102 $headline ||= 'parameter:'; # LOCALIZE
776 35   33     97 $metaclass ||= $self;
777              
778 35         47 my @parameters;
779 35         104 foreach my $attribute (
780 21         514 sort { $a->cmd_position <=> $b->cmd_position }
781             $self->command_usage_attributes($metaclass,'parameter')
782             ) {
783 21         56 push(@parameters,[
784             $attribute->cmd_usage_name(),
785             $attribute->cmd_usage_description()
786             ]);
787             }
788              
789             return
790 35 100       156 unless scalar @parameters > 0;
791              
792 7         38 return $self->command_message(
793             header => $headline,
794             body => MooseX::App::Utils::format_list(@parameters),
795             );
796             }
797              
798             sub command_usage_header {
799 29     29 1 60 my ($self,$command_meta_class) = @_;
800              
801 29         996 my $caller = $self->app_base;
802              
803 29         47 my ($command_name,$usage);
804 29 100       88 if ($command_meta_class) {
805 23         147 $command_name = $self->command_class_to_command($command_meta_class->name);
806             } else {
807 6         13 $command_name = '<command>';
808             }
809              
810 29   66     103 $command_meta_class ||= $self;
811 29 100 66     405 if ($command_meta_class->can('command_usage')
812             && $command_meta_class->command_usage_predicate) {
813 1         44 $usage = MooseX::App::Utils::format_text($command_meta_class->command_usage);
814             }
815              
816 29 100       92 unless (defined $usage) {
817             # LOCALIZE
818 28         107 $usage = "$caller $command_name ";
819 28         96 my @parameter= $self->command_usage_attributes($command_meta_class,'parameter');
820 28         73 foreach my $attribute (@parameter) {
821 21 100       583 if ($attribute->is_required) {
822 2         18 $usage .= "<".$attribute->cmd_usage_name.'> ';
823             } else {
824 19         100 $usage .= '['.$attribute->cmd_usage_name.'] ';
825             }
826             }
827 28         123 $usage .= "[long options...]
828             $caller help
829             $caller $command_name --help";
830 28         149 $usage = MooseX::App::Utils::format_text($usage);
831             }
832              
833 29         126 return $self->command_message(
834             header => 'usage:', # LOCALIZE
835             body => $usage,
836             );
837             }
838              
839             sub command_usage_description {
840 36     36 1 109 my ($self,$command_meta_class) = @_;
841              
842 36   33     114 $command_meta_class ||= $self;
843 36 100 66     403 if ($command_meta_class->can('command_long_description')
    100 66        
844             && $command_meta_class->command_long_description_predicate) {
845 13         476 return $self->command_message(
846             header => 'description:', # LOCALIZE
847             body => MooseX::App::Utils::format_text($command_meta_class->command_long_description),
848             );
849             } elsif ($command_meta_class->can('command_short_description')
850             && $command_meta_class->command_short_description_predicate) {
851 1         35 return $self->command_message(
852             header => 'short description:', # LOCALIZE
853             body => MooseX::App::Utils::format_text($command_meta_class->command_short_description),
854             );
855             }
856 22         51 return;
857             }
858              
859             sub command_class_to_command {
860 55     55 1 126 my ($self,$command_class) = @_;
861              
862 55         2033 my $commands = $self->app_commands;
863 55         306 foreach my $element (keys %$commands) {
864 220 100       438 if ($command_class eq $commands->{$element}) {
865 55         155 return $element;
866             }
867             }
868              
869 0         0 return;
870             }
871              
872             sub command_subcommands {
873 29     29 0 56 my ($self,$command_meta_class) = @_;
874              
875 29   33     101 $command_meta_class ||= $self;
876 29         161 my $command_class = $command_meta_class->name;
877 29         126 my $command_name = $self->command_class_to_command($command_class);
878              
879 29         867 my $commands = $self->app_commands;
880 29         61 my $subcommands = {};
881 29         49 foreach my $command (keys %{$commands}) {
  29         96  
882             next
883 145 100 100     877 if $command eq $command_name
884             || $command !~ m/^\Q$command_name\E\s(.+)/;
885 2         5 $subcommands->{$1} = $commands->{$command};
886             }
887              
888 29         270 return $subcommands;
889             }
890              
891             sub command_usage_command {
892 29     29 1 200 my ($self,$command_meta_class) = @_;
893              
894 29   33     99 $command_meta_class ||= $self;
895              
896 29         46 my @usage;
897 29         142 push(@usage,$self->command_usage_header($command_meta_class));
898 29         186 push(@usage,$self->command_usage_description($command_meta_class));
899 29         134 push(@usage,$self->command_usage_parameters($command_meta_class,'parameters:')); # LOCALIZE
900 29         138 push(@usage,$self->command_usage_options($command_meta_class,'options:')); # LOCALIZE
901 29         164 push(@usage,$self->command_usage_subcommands('available subcommands:',$self->command_subcommands($command_meta_class))); # LOCALIZE
902              
903 29         1029 return @usage;
904             }
905              
906             sub command_usage_global {
907 6     6 1 12 my ($self) = @_;
908              
909 6         15 my @usage;
910 6         27 push(@usage,$self->command_usage_header());
911              
912 6         24 my $description = $self->command_usage_description($self);
913 6 100       22 push(@usage,$description)
914             if $description;
915 6         30 push(@usage,$self->command_usage_parameters($self,'global parameters:')); # LOCALIZE
916 6         28 push(@usage,$self->command_usage_options($self,'global options:')); # LOCALIZE
917 6         176 push(@usage,$self->command_usage_subcommands('available commands:',$self->app_commands)); # LOCALIZE
918              
919 6         218 return @usage;
920             }
921              
922             sub command_usage_subcommands {
923 35     35 0 88 my ($self,$headline,$commands) = @_;
924              
925 35         53 my @commands;
926              
927 35         122 foreach my $command (keys %$commands) {
928 25         3052 my $class = $commands->{$command};
929 25         58 Class::Load::load_class($class);
930             }
931              
932 35         231 foreach my $command (keys %$commands) {
933 25         38 my $class = $commands->{$command};
934              
935 25 50 33     791 unless ($class->can('meta')
936             && $class->DOES('MooseX::App::Role::Common')) {
937 0         0 Moose->throw_error('Class '.$class.' is not a proper MooseX::App::Command class. You either need to use MooseX::App::Command or exclude this class via app_exclude')
938             }
939              
940 25         7414 my $command_description;
941 25 50       104 $command_description = $class->meta->command_short_description
942             if $class->meta->can('command_short_description');
943              
944 25   100     76 $command_description ||= '';
945 25         70 push(@commands,[$command,$command_description]);
946             }
947              
948 35         125 @commands = sort { $a->[0] cmp $b->[0] } @commands;
  31         43  
949 35         116 push(@commands,['help','Prints this usage information']); # LOCALIZE
950              
951 35         158 return $self->command_message(
952             header => $headline,
953             body => MooseX::App::Utils::format_list(@commands),
954             );
955             }
956              
957             1;
958              
959             __END__
960              
961             =pod
962              
963             =encoding utf8
964              
965             =head1 NAME
966              
967             MooseX::App::Meta::Role::Class::Base - Meta class role for application base class
968              
969             =head1 DESCRIPTION
970              
971             This meta class role will automatically be applied to the application base
972             class. This documentation is only of interest if you intend to write
973             plugins for MooseX-App.
974              
975             =head1 ACCESSORS
976              
977             =head2 app_messageclass
978              
979             Message class for generating error messages. Defaults to
980             MooseX::App::Message::Block. The default can be overwritten by altering
981             the C<_build_app_messageclass> method. Defaults to MooseX::App::Message::Block
982              
983             =head2 app_namespace
984              
985             Usually MooseX::App will take the package name of the base class as the
986             namespace for commands. This namespace can be changed.
987              
988             =head2 app_exclude
989              
990             Exclude namespaces included in app_namespace
991              
992             =head2 app_base
993              
994             Usually MooseX::App will take the name of the calling wrapper script to
995             construct the program name in various help messages. This name can
996             be changed via the app_base accessor. Defaults to the base name of $0
997              
998             =head2 app_fuzzy
999              
1000             Boolean flag that controls if command names and attributes should be
1001             matched exactly or fuzzy. Defaults to true.
1002              
1003             =head2 app_command_name
1004              
1005             Coderef attribute that controls how package names are translated to command
1006             names and attributes. Defaults to &MooseX::App::Utils::class_to_command
1007              
1008             =head2 app_commands
1009              
1010             Hashref with command to command class map.
1011              
1012             =head2 app_strict
1013              
1014             Boolean flag that controls if an application with superfluous/unknown
1015             positional parameters should terminate with an error message or not.
1016             If disabled all extra parameters will be copied to the L<extra_argv>
1017             command class attribute.
1018              
1019             =head2 app_prefer_commandline
1020              
1021             By default, arguments passed to new_with_command and new_with_options have a
1022             higher priority than the command line options. This boolean flag will give
1023             the command line an higher priority.
1024              
1025             =head2 app_permute
1026              
1027             Boolean flag that controls if command line arguments that take multiple values
1028             (ie ArrayRef or HashRef type constraints) can be permuted.
1029              
1030             =head1 METHODS
1031              
1032             =head2 command_check
1033              
1034             Runs sanity checks on options and parameters. Will usually only be executed if
1035             either HARNESS_ACTIVE or APP_DEVELOPER environment are set.
1036              
1037             =head2 command_register
1038              
1039             $self->command_register($command_moniker,$command_class);
1040              
1041             Registers an additional command
1042              
1043             =head2 command_get
1044              
1045             my $command_class = $self->command_register($command_moniker);
1046              
1047             Returns a command class for the given command moniker
1048              
1049             =head2 command_class_to_command
1050              
1051             my $command_moniker = $meta->command_class_to_command($command_class);
1052              
1053             Returns the command moniker for the given command class.
1054              
1055             =head2 command_message
1056              
1057             my $message = $meta->command_message(
1058             header => $header,
1059             type => 'error',
1060             body => $message
1061             );
1062              
1063             Generates a message object (using the class from L<app_messageclass>)
1064              
1065             =head2 command_usage_attributes
1066              
1067             my @attributes = $meta->command_usage_attributes($metaclass);
1068              
1069             Returns a list of attributes/command options for the given meta class.
1070              
1071             =head2 command_usage_command
1072              
1073             my @messages = $meta->command_usage_command($command_metaclass);
1074              
1075             Returns a list of messages containing the documentation for a given
1076             command meta class.
1077              
1078             =head2 command_usage_description
1079              
1080             my $message = $meta->command_usage_description($command_metaclass);
1081              
1082             Returns a messages with the basic command description.
1083              
1084             =head2 command_usage_global
1085              
1086             my @messages = $meta->command_usage_global();
1087              
1088             Returns a list of messages containing the documentation for the application.
1089              
1090             =head2 command_usage_header
1091              
1092             my $message = $meta->command_usage_header();
1093             my $message = $meta->command_usage_header($command_meta_class);
1094              
1095             Returns a message containing the basic usage documentation
1096              
1097             =head2 command_find
1098              
1099             my @commands = $meta->command_find($commands_arrayref);
1100              
1101             Returns a list of command names matching the user input
1102              
1103             =head2 command_candidates
1104              
1105             my $commands = $meta->command_candidates($user_command_input);
1106              
1107             Returns either a single command or an arrayref of possibly matching commands.
1108              
1109             =head2 command_proto
1110              
1111             my ($result,$errors) = $meta->command_proto($command_meta_class);
1112              
1113             Returns all parsed options (as hashref) and erros (as arrayref) for the proto
1114             command. Is a wrapper around L<command_parse_options>.
1115              
1116             =head2 command_args
1117              
1118             my ($options,$errors) = $self->command_args($command_meta_class);
1119              
1120             Returns all parsed options (as hashref) and erros (as arrayref) for the main
1121             command. Is a wrapper around L<command_parse_options>.
1122              
1123             =head2 command_parse_options
1124              
1125             my ($options,$errors) = $self->command_parse_options(\@attribute_metaclasses);
1126              
1127             Tries to parse the selected attributes from @ARGV.
1128              
1129             =head2 command_scan_namespace
1130              
1131             my %namespaces = $self->command_scan_namespace($namespace);
1132              
1133             Scans a namespace for command classes. Returns a hash with command names
1134             as keys and package names as values.
1135              
1136             =head2 command_process_attribute
1137              
1138             my @attributes = $self->command_process_attribute($attribute_metaclass,$matches);
1139              
1140             TODO
1141             ###Returns a list of all attributes with the given type
1142              
1143             =head2 command_usage_options
1144              
1145             my $usage = $self->command_usage_options($metaclass,$headline);
1146              
1147             Returns the options usage as a message object
1148              
1149             =head2 command_usage_parameters
1150              
1151             my $usage = $self->command_usage_parameters($metaclass,$headline);
1152              
1153             Returns the positional parameters usage as a message object
1154              
1155             =head2 command_check_attributes
1156              
1157             $errors = $self->command_check_attributes($command_metaclass,$errors,$params)
1158              
1159             Checks all attributes. Returns/alters the $errors arrayref
1160              
1161             =head2 command_parser_hints
1162              
1163             $self->command_parser_hints($self,$metaclass)
1164              
1165             Generates parser hints as required by L<MooseX::App::ParsedArgv>
1166              
1167             =cut