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