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   55563 use utf8;
  15         43  
  15         145  
6 15     15   807 use 5.010;
  15         58  
7              
8 15     15   94 use List::Util qw(max);
  15         32  
  15         1125  
9              
10 15     15   104 use namespace::autoclean;
  15         39  
  15         120  
11 15     15   1560 use Moose::Role;
  15         33  
  15         186  
12              
13 15     15   86747 use MooseX::App::Utils;
  15         40  
  15         559  
14 15     15   6355 use Module::Pluggable::Object;
  15         73104  
  15         757  
15 15     15   130 use File::Basename qw();
  15         47  
  15         500  
16 15     15   89 no if $] >= 5.018000, warnings => qw(experimental::smartmatch);
  15         42  
  15         131  
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   97 my ($self) = @_;
95 13         494 return 'MooseX::App::Message::Block'
96             }
97              
98             sub _build_app_namespace {
99 10     10   26 my ($self) = @_;
100 10         359 return [ $self->name ];
101             }
102              
103             sub _build_app_commands {
104 10     10   35 my ($self) = @_;
105              
106 10         25 my (@list);
107             # Process namespace list
108 10         38 foreach my $namespace ( @{ $self->app_namespace } ) {
  10         505  
109 10         55 push(@list,$self->command_scan_namespace($namespace));
110             }
111 10         47 my $commands = { @list };
112              
113             # Process excludes
114 10         26 foreach my $exclude ( @{ $self->app_exclude } ) {
  10         519  
115 3         11 foreach my $command (keys %{$commands}) {
  3         158  
116             delete $commands->{$command}
117 21 100       135 if $commands->{$command} =~ m/^\Q$exclude\E(::|$)/;
118             }
119             }
120              
121 10         274 return $commands;
122             }
123              
124             sub command_check {
125 82     82 1 709 my ($self) = @_;
126              
127 82         405 foreach my $attribute ($self->command_usage_attributes($self,'all')) {
128 201         927 $attribute->cmd_check();
129             }
130 81         322 return;
131             }
132              
133             sub command_scan_namespace {
134 10     10 1 31 my ($self,$namespace) = @_;
135              
136             # Find all packages in namespace
137 10         161 my $mpo = Module::Pluggable::Object->new(
138             search_path => [ $namespace ],
139             );
140              
141 10         477 my $commandsub = $self->app_command_name;
142              
143 10         25 my %return;
144             # Loop all packages
145 10         54 foreach my $command_class ($mpo->plugins) {
146 42         19995 my $command_class_name = substr($command_class,length($namespace)+2);
147              
148             # subcommands support
149 42         101 $command_class_name =~ s/::/ /g;
150              
151             # Extract command name
152 42         245 $command_class_name =~ s/^\Q$namespace\E:://;
153 42         98 $command_class_name =~ s/^.+::([^:]+)$/$1/;
154 42         128 my $command = $commandsub->($command_class_name,$command_class);
155              
156             # Check if command was loaded
157 42 50       332 $return{$command} = $command_class
158             if defined $command;
159             }
160              
161 10         822 return %return;
162             }
163              
164             sub command_args {
165 68     68 1 196 my ($self,$metaclass) = @_;
166              
167 68   33     210 $metaclass ||= $self;
168 68         368 my $parsed_argv = MooseX::App::ParsedArgv->instance;
169              
170 68 50       406 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         27093 my @attributes_option = $self->command_usage_attributes($metaclass,'option');
176              
177 68         413 my ($return,$errors) = $self->command_parse_options(\@attributes_option);
178              
179 67         182 my %raw_error;
180             # Loop all left over options
181 67         347 foreach my $option ($parsed_argv->available('option')) {
182 2         58 my $key = $option->key;
183 2         10 my $raw = $option->original;
184 2         4 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     21 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         6 my $error;
206 2 50       7 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         62 $error = $self->command_message(
214             header => "Unknown option '".$option->key."'", # LOCALIZE
215             type => "error",
216             );
217             }
218 2         5 unshift(@{$errors},$error);
  2         7  
219             }
220              
221             # Process positional parameters
222 67         325 my @attributes_parameter = $self->command_usage_attributes($metaclass,'parameter');
223              
224 67         245 foreach my $attribute (@attributes_parameter) {
225 36         141 my $element = $parsed_argv->consume('parameter');
226             last
227 36 100       98 unless defined $element;
228              
229 22         643 my ($parameter_value,$parameter_errors) = $self->command_process_attribute($attribute, [ $element->key ] );
230 22         56 push(@{$errors},@{$parameter_errors});
  22         293  
  22         48  
231 22         128 $return->{$attribute->name} = $parameter_value;
232             }
233              
234             # Handle all unconsumed parameters and options
235 67 100 66     2459 if ($self->app_strict || $metaclass->command_strict) {
236 33         195 foreach my $parameter ($parsed_argv->available('parameter')) {
237 2         22 unshift(@{$errors},
  2         76  
238             $self->command_message(
239             header => "Unknown parameter '".$parameter->key."'", # LOCALIZE
240             type => "error",
241             )
242             );
243             }
244             }
245              
246             # Handle ENV
247 67         331 foreach my $attribute ($self->command_usage_attributes($metaclass,'all')) {
248             next
249 373 100 66     13958 unless $attribute->can('has_cmd_env')
250             && $attribute->has_cmd_env;
251              
252 27         883 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         17 my $value = $ENV{$cmd_env};
258              
259 3 50       131 if ($attribute->has_type_constraint) {
260 3         121 my $type_constraint = $attribute->type_constraint;
261 3 100 66     134 if ($attribute->should_coerce
262             && $type_constraint->has_coercion) {
263 1         91 my $coercion = $type_constraint->coercion;
264 1   33     16 $value = $coercion->coerce($value) // $value;
265             }
266             }
267              
268 3         143 $return->{$attribute->name} = $value;
269 3         25 my $error = $attribute->cmd_type_constraint_check($value);
270 3 100       16 if ($error) {
271 1         3 push(@{$errors},
  1         10  
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         523 return ($return,$errors);
283             }
284              
285             sub command_proto {
286 74     74 1 232 my ($self,$metaclass) = @_;
287              
288 74   33     253 $metaclass ||= $self;
289              
290 74         155 my @attributes;
291 74         392 foreach my $attribute ($self->command_usage_attributes($metaclass,'proto')) {
292             next
293 94 50 33     369 unless $attribute->does('MooseX::App::Meta::Role::Attribute::Option')
294             && $attribute->has_cmd_type;
295 94         348 push(@attributes,$attribute);
296             }
297              
298 74         401 return $self->command_parse_options(\@attributes);
299             }
300              
301             sub command_parse_options {
302 142     142 1 387 my ($self,$attributes) = @_;
303              
304             # Build attribute lookup hash
305 142         270 my %option_to_attribute;
306 142         275 foreach my $attribute (@{$attributes}) {
  142         351  
307 331         1146 foreach my $name ($attribute->cmd_name_possible) {
308 566 100 66     1554 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         1400 $option_to_attribute{$name} = $attribute;
313             }
314             }
315              
316 141         478 my $match = {};
317 141         312 my $return = {};
318 141         280 my @errors;
319              
320             # Get ARGV
321 141         737 my $parsed_argv = MooseX::App::ParsedArgv->instance;
322              
323             # Loop all exact matches
324 141         626 foreach my $option ($parsed_argv->available('option')) {
325 199 100       5741 if (my $attribute = $option_to_attribute{$option->key}) {
326 87         468 $option->consume($attribute);
327 87         536 $match->{$attribute->name} = [ $option ];
328             }
329             }
330              
331             # Process fuzzy matches
332 141 100       4870 if ($self->app_fuzzy) {
333             # Loop all options (sorted by length)
334 137         496 foreach my $option (sort { length($b->key) <=> length($a->key) } $parsed_argv->available('option')) {
  72         1974  
335              
336             # No fuzzy matching for one-letter flags
337 107         3072 my $option_length = length($option->key);
338             next
339 107 100       303 if $option_length == 1;
340              
341 103         233 my ($match_attributes) = [];
342              
343             # Try to match attributes
344 103         436 foreach my $name (keys %option_to_attribute) {
345             next
346 456 100       970 if ($option_length >= length($name));
347              
348 111         247 my $name_short = lc(substr($name,0,$option_length));
349              
350             # Partial match
351 111 100       3038 if (lc($option->key) eq $name_short) {
352 16         52 my $attribute = $option_to_attribute{$name};
353 16 50       33 unless (grep { $attribute == $_ } @{$match_attributes}) {
  1         6  
  16         58  
354 16         28 push(@{$match_attributes},$attribute);
  16         47  
355             }
356             }
357             }
358              
359             # Process matches
360 103         216 given (scalar @{$match_attributes}) {
  103         213  
361             # No match
362 103         360 when(0) {}
363             # One match
364 15         63 when(1) {
365 14         29 my $attribute = $match_attributes->[0];
366 14         55 $option->consume();
367 14   100     104 $match->{$attribute->name} ||= [];
368 14         28 push(@{$match->{$attribute->name}},$option);
  14         72  
369             }
370             # Multiple matches
371 1         2 default {
372 1         9 $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         13 map { [ $_ ] }
379             sort
380 2         8 map { $_->cmd_name_primary }
381 1         31 @{$match_attributes}
  1         4  
382             ),
383             )
384             );
385             }
386             }
387             }
388             }
389              
390             # Check all attributes
391 141         294 foreach my $attribute (@{$attributes}) {
  141         342  
392              
393             next
394 329 100       1361 unless exists $match->{$attribute->name};
395              
396 93         179 my @mapped_values;
397 93         180 foreach my $element (@{$match->{$attribute->name}}) {
  93         388  
398 98         463 push(@mapped_values,$element->all_values);
399             }
400              
401             my $values = [
402 108         3289 map { $_->value }
403 93         309 sort { $a->position <=> $b->position }
  21         587  
404             @mapped_values
405             ];
406              
407             #warn Data::Dumper::Dumper($raw);
408 93         414 my ($value,$errors) = $self->command_process_attribute( $attribute, $values );
409 93         197 push(@errors,@{$errors});
  93         219  
410              
411 93         652 $return->{$attribute->name} = $value;
412             }
413              
414 141         963 return ($return,\@errors);
415             }
416              
417             sub command_process_attribute {
418 115     115 1 297 my ($self,$attribute,$raw) = @_;
419              
420 115 50       410 $raw = [ $raw ]
421             unless ref($raw) eq 'ARRAY';
422              
423 115         256 my @errors;
424             my $value;
425              
426             # Attribute with split
427 115 100       4437 if ($attribute->has_cmd_split) {
428 1         3 my @raw_unfolded;
429 1         2 foreach (@{$raw}) {
  1         3  
430 2         65 push(@raw_unfolded,split($attribute->cmd_split,$_));
431             }
432 1         3 $raw = \@raw_unfolded;
433             }
434              
435             # Attribute with counter - transform value count into value
436 115 100       3693 if ($attribute->cmd_count) {
437 1         5 $value = $raw = [ scalar(@$raw) ];
438             }
439              
440             # Attribute with type constraint
441 115 100       4365 if ($attribute->has_type_constraint) {
442 109         3979 my $type_constraint = $attribute->type_constraint;
443              
444 109 100       1164 if ($type_constraint->is_a_type_of('ArrayRef')) {
    100          
    100          
445 5         823 $value = $raw;
446             } elsif ($type_constraint->is_a_type_of('HashRef')) {
447 4         2795 $value = {};
448 4         10 foreach my $element (@{$raw}) {
  4         12  
449 6 100       35 if ($element =~ m/^([^=]+)=(.+?)$/) {
450 5   33     47 $value->{$1} ||= $2;
451             } else {
452 1         6 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         24175 $value = $raw->[-1];
463              
464             # if ($self->has_default
465             # && ! $self->is_default_a_coderef
466             # && $self->default == 1) {
467              
468             } else {
469 74         144728 $value = $raw->[-1];
470             }
471              
472 109 100       331 unless(defined $value) {
473 2         16 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     4111 if ($attribute->should_coerce
481             && $type_constraint->has_coercion) {
482 1         92 my $coercion = $type_constraint->coercion;
483 1   33     14 $value = $coercion->coerce($value) // $value;
484             }
485 107         1465 my $error = $attribute->cmd_type_constraint_check($value);
486 107 100       1046 if (defined $error) {
487 6         27 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         42 $value = $raw->[-1];
499             }
500              
501 115         556 return ($value,\@errors);
502             }
503              
504             sub command_candidates {
505 45     45 1 115 my ($self,$command) = @_;
506              
507 45         103 my $lc_command = lc($command);
508 45         1516 my $commands = $self->app_commands;
509              
510 45         88 my @candidates;
511 45         100 my $candidate_length = length($command);
512              
513             # Compare all commands to find matching candidates
514 45         212 foreach my $command_name (keys %$commands) {
515 318 100       794 if ($command_name eq $lc_command) {
    100          
516 1         6 return $command_name;
517             } elsif ($lc_command eq substr($command_name,0,$candidate_length)) {
518 32         80 push(@candidates,$command_name);
519             }
520             }
521              
522 44         185 return [ sort @candidates ];
523             }
524              
525             sub command_find {
526 64     64 1 187 my ($self,$commands) = @_;
527              
528 64         236 my $parsed_argv = MooseX::App::ParsedArgv->instance;
529 64         2358 my $all_commands = $self->app_commands;
530              
531             # Get parts
532 64         171 my (@parts,@command_parts);
533 64 100       240 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         5 @parts = ( lc($commands) );
538             }
539             } else {
540 63         2319 @parts = $parsed_argv->elements_argv;
541             }
542              
543             # Extract possible parts
544 64         264 foreach my $part (@parts) {
545             # Anyting staring with a dash cannot be a command
546             last
547 143 100       554 if $part =~ m/^-/;
548 87         265 push(@command_parts,lc($part));
549             }
550              
551             # Shortcut
552             return
553 64 50       294 unless scalar @command_parts;
554              
555             # basically do a longest-match search
556 64         240 for my $index (reverse(0..$#command_parts)) {
557 85         358 my $command = join ' ', @command_parts[0..$index];
558 85 100       324 if( $all_commands->{$command} ) {
559 36         262 $parsed_argv->shift_argv for 0..$index;
560 36         179 return $command;
561             }
562             }
563              
564             # didn't find an exact match, let's go to plan B
565 28         99 foreach my $index (reverse(0..$#command_parts)) {
566 42         135 my $command = join ' ', @command_parts[0..$index];
567 42         158 my $candidate = $self->command_candidates($command);
568 42 50       160 if (ref $candidate eq '') {
569 0         0 $parsed_argv->shift_argv;
570 0         0 return $candidate;
571             }
572 42         71 given (scalar @{$candidate}) {
  42         106  
573 42         131 when (0) {
574 15         35 next;
575             }
576 27         78 when (1) {
577 27 100       1031 if ($self->app_fuzzy) {
578 26         134 $parsed_argv->shift_argv;
579 26         145 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         6 my $command = $command_parts[0];
600 1         7 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 235 my ($self,$metaclass) = @_;
608              
609 74   33     255 $metaclass ||= $self;
610              
611 74         185 my %hints;
612             my %names;
613 74         457 my $return = { permute => [], novalue => [], fixedvalue => {} };
614 74         462 foreach my $attribute ($self->command_usage_attributes($metaclass,[qw(option proto)])) {
615 347         678 my $permute = 0;
616 347         573 my $bool = 0;
617 347         11099 my $type_constraint = $attribute->type_constraint;
618 347 100       3266 if ($type_constraint) {
619 309 100 100     2120 $permute = 1
620             if $type_constraint->is_a_type_of('ArrayRef')
621             || $type_constraint->is_a_type_of('HashRef');
622              
623 309 100       299203 $bool = 1
624             if $type_constraint->is_a_type_of('Bool');
625             }
626              
627 347   100     128793 my $hint = {
628             name => $attribute->name,
629             bool => $bool,
630             novalue => $bool || $attribute->cmd_count,
631             permute => $permute,
632             };
633              
634 347         1271 foreach my $name ($attribute->cmd_name_list) {
635 569         1739 $names{$name} = $hints{$name} = $hint;
636             }
637              
638             # Negated values
639 347 100       6880 if ($bool) {
    100          
640 144         365 $hint->{fixedvalue} = 1;
641 144 100       5540 if ($attribute->has_cmd_negate) {
642 11         30 my $hint_neg = { %{$hint} }; # shallow copy
  11         78  
643 11         38 $hint_neg->{fixedvalue} = 0;
644 11         25 foreach my $name (@{$attribute->cmd_negate}) {
  11         377  
645 14         61 $names{$name} = $hints{$name} = $hint_neg;
646             }
647             }
648             } elsif ($attribute->cmd_count) {
649 6         33 $hint->{fixedvalue} = 1;
650             }
651             }
652              
653 74 100       2678 if ($self->app_fuzzy) {
654 72   50     436 my $length = max(map { length($_) } keys %names) // 0;
  563         1277  
655 72         348 foreach my $l (reverse(2..$length)) {
656 625         912 my %tmp;
657 625         1496 foreach my $name (keys %names) {
658             next
659 4976 100       8861 if length($name) < $l;
660 2244         3775 my $short_name = substr($name,0,$l);
661             next
662 2244 100       4283 if defined $hints{$short_name};
663 1827   100     7261 $tmp{$short_name} ||= [];
664             next
665             if defined $tmp{$short_name}->[0]
666 1827 50 66     4367 && $tmp{$short_name}->[0]->{name} eq $names{$name}->{name};
667 1827         2466 push(@{$tmp{$short_name}},$names{$name})
  1827         3846  
668             }
669 625         1653 foreach my $short_name (keys %tmp) {
670             next
671 1575 100       2084 if scalar @{$tmp{$short_name}} > 1;
  1575         3110  
672 1338         2870 $hints{$short_name} = $tmp{$short_name}->[0];
673             }
674             }
675             }
676              
677 74         461 foreach my $name (keys %hints) {
678 1920 100       3523 if ($hints{$name}->{novalue}) {
679 1096         1483 push(@{$return->{novalue}},$name);
  1096         2196  
680             }
681 1920 100       3396 if ($hints{$name}->{permute}) {
682 97         133 push(@{$return->{permute}},$name);
  97         189  
683             }
684 1920 100       3459 if (defined $hints{$name}->{fixedvalue}) {
685 1096         2127 $return->{fixedvalue}{$name} = $hints{$name}->{fixedvalue};
686             }
687             }
688              
689              
690             #warn Data::Dumper::Dumper($return);
691 74         812 return $return;
692             }
693              
694             sub command_message {
695 159     159 1 622 my ($self,@args) = @_;
696 159         6111 my $messageclass = $self->app_messageclass;
697 159         821 Class::Load::load_class($messageclass);
698 159         11315 return $messageclass->new(@args);
699             }
700              
701             sub command_check_attributes {
702 67     67 1 226 my ($self,$command_meta,$errors,$params) = @_;
703              
704 67   33     279 $command_meta ||= $self;
705              
706             # Check required values
707 67         389 foreach my $attribute ($self->command_usage_attributes($command_meta,[qw(option proto parameter)])) {
708 373 100 100     14999 if ($attribute->is_required
      100        
709             && ! exists $params->{$attribute->name}
710             && ! $attribute->has_default) {
711 7 100       205 push(@{$errors},
  7         249  
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         753 return $errors;
721             }
722              
723             sub command_usage_attributes {
724 629     629 1 2103 my ($self,$metaclass,$types) = @_;
725              
726 629   66     1725 $metaclass ||= $self;
727 629   100     1649 $types ||= [qw(option proto)];
728              
729 629 50       2617 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         245372 my @return;
734 629         2563 foreach my $attribute ($metaclass->get_all_attributes) {
735             next
736 4618 100 100     117420 unless $attribute->does('MooseX::App::Meta::Role::Attribute::Option')
737             && $attribute->has_cmd_type;
738              
739             next
740 3194 100 100     89353 unless $types eq 'all'
741             || $attribute->cmd_type ~~ $types;
742              
743 2002         5432 push(@return,$attribute);
744             }
745              
746             return (sort {
747 629 50       13304 $a->cmd_position <=> $b->cmd_position ||
  2782         89349  
748             $a->cmd_usage_name cmp $b->cmd_usage_name
749             } @return);
750             }
751              
752             sub command_usage_options {
753 35     35 1 133 my ($self,$metaclass,$headline) = @_;
754              
755 35   50     146 $headline ||= 'options:'; # LOCALIZE
756 35   33     140 $metaclass ||= $self;
757              
758 35         69 my @options;
759 35         201 foreach my $attribute ($self->command_usage_attributes($metaclass,[qw(option proto)])) {
760 172         625 push(@options,[
761             $attribute->cmd_usage_name(),
762             $attribute->cmd_usage_description()
763             ]);
764             }
765              
766             return
767 35 50       178 unless scalar @options > 0;
768              
769 35         235 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 165 my ($self,$metaclass,$headline) = @_;
777              
778 35   50     121 $headline ||= 'parameter:'; # LOCALIZE
779 35   33     133 $metaclass ||= $self;
780              
781 35         77 my @parameters;
782 35         161 foreach my $attribute (
783 21         665 sort { $a->cmd_position <=> $b->cmd_position }
784             $self->command_usage_attributes($metaclass,'parameter')
785             ) {
786 21         70 push(@parameters,[
787             $attribute->cmd_usage_name(),
788             $attribute->cmd_usage_description()
789             ]);
790             }
791              
792             return
793 35 100       182 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 110 my ($self,$command_meta_class) = @_;
803              
804 29         1109 my $caller = $self->app_base;
805              
806 29         88 my ($command_name,$usage);
807 29 100       121 if ($command_meta_class) {
808 23         177 $command_name = $self->command_class_to_command($command_meta_class->name);
809             } else {
810 6         16 $command_name = '<command>';
811             }
812              
813 29   66     127 $command_meta_class ||= $self;
814 29 100 66     442 if ($command_meta_class->can('command_usage')
815             && $command_meta_class->command_usage_predicate) {
816 1         71 $usage = MooseX::App::Utils::format_text($command_meta_class->command_usage);
817             }
818              
819 29 100       108 unless (defined $usage) {
820             # LOCALIZE
821 28         127 $usage = "$caller $command_name ";
822 28         125 my @parameter= $self->command_usage_attributes($command_meta_class,'parameter');
823 28         134 foreach my $attribute (@parameter) {
824 21 100       717 if ($attribute->is_required) {
825 2         25 $usage .= "<".$attribute->cmd_usage_name.'> ';
826             } else {
827 19         197 $usage .= '['.$attribute->cmd_usage_name.'] ';
828             }
829             }
830 28         131 $usage .= "[long options...]
831             $caller help
832             $caller $command_name --help";
833 28         187 $usage = MooseX::App::Utils::format_text($usage);
834             }
835              
836 29         154 return $self->command_message(
837             header => 'usage:', # LOCALIZE
838             body => $usage,
839             );
840             }
841              
842             sub command_usage_description {
843 36     36 1 188 my ($self,$command_meta_class) = @_;
844              
845 36   33     132 $command_meta_class ||= $self;
846 36 100 66     474 if ($command_meta_class->can('command_long_description')
    100 66        
847             && $command_meta_class->command_long_description_predicate) {
848 13         552 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         43 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         70 return;
860             }
861              
862             sub command_class_to_command {
863 55     55 1 210 my ($self,$command_class) = @_;
864              
865 55         2370 my $commands = $self->app_commands;
866 55         395 foreach my $element (keys %$commands) {
867 137 100       435 if ($command_class eq $commands->{$element}) {
868 55         241 return $element;
869             }
870             }
871              
872 0         0 return;
873             }
874              
875             sub command_subcommands {
876 29     29 0 98 my ($self,$command_meta_class) = @_;
877              
878 29   33     119 $command_meta_class ||= $self;
879 29         183 my $command_class = $command_meta_class->name;
880 29         151 my $command_name = $self->command_class_to_command($command_class);
881              
882 29         1011 my $commands = $self->app_commands;
883 29         85 my $subcommands = {};
884 29         73 foreach my $command (keys %{$commands}) {
  29         123  
885             next
886 145 100 100     1102 if $command eq $command_name
887             || $command !~ m/^\Q$command_name\E\s(.+)/;
888 2         10 $subcommands->{$1} = $commands->{$command};
889             }
890              
891 29         179 return $subcommands;
892             }
893              
894             sub command_usage_command {
895 29     29 1 280 my ($self,$command_meta_class) = @_;
896              
897 29   33     124 $command_meta_class ||= $self;
898              
899 29         67 my @usage;
900 29         187 push(@usage,$self->command_usage_header($command_meta_class));
901 29         169 push(@usage,$self->command_usage_description($command_meta_class));
902 29         188 push(@usage,$self->command_usage_parameters($command_meta_class,'parameters:')); # LOCALIZE
903 29         160 push(@usage,$self->command_usage_options($command_meta_class,'options:')); # LOCALIZE
904 29         190 push(@usage,$self->command_usage_subcommands('available subcommands:',$self->command_subcommands($command_meta_class))); # LOCALIZE
905              
906 29         1222 return @usage;
907             }
908              
909             sub command_usage_global {
910 6     6 1 24 my ($self) = @_;
911              
912 6         15 my @usage;
913 6         31 push(@usage,$self->command_usage_header());
914              
915 6         32 my $description = $self->command_usage_description($self);
916 6 100       31 push(@usage,$description)
917             if $description;
918 6         36 push(@usage,$self->command_usage_parameters($self,'global parameters:')); # LOCALIZE
919 6         37 push(@usage,$self->command_usage_options($self,'global options:')); # LOCALIZE
920 6         220 push(@usage,$self->command_usage_subcommands('available commands:',$self->app_commands)); # LOCALIZE
921              
922 6         236 return @usage;
923             }
924              
925             sub command_usage_subcommands {
926 35     35 0 173 my ($self,$headline,$commands) = @_;
927              
928 35         87 my @commands;
929              
930 35         146 foreach my $command (keys %$commands) {
931 25         4307 my $class = $commands->{$command};
932 25         72 Class::Load::load_class($class);
933             }
934              
935 35         320 foreach my $command (keys %$commands) {
936 25         180 my $class = $commands->{$command};
937              
938 25 50 33     884 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         10821 my $command_description;
944 25 50       142 $command_description = $class->meta->command_short_description
945             if $class->meta->can('command_short_description');
946              
947 25   100     91 $command_description ||= '';
948 25         103 push(@commands,[$command,$command_description]);
949             }
950              
951 35         162 @commands = sort { $a->[0] cmp $b->[0] } @commands;
  30         74  
952 35         139 push(@commands,['help','Prints this usage information']); # LOCALIZE
953              
954 35         162 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