File Coverage

blib/lib/MooseX/App/Meta/Role/Attribute/Option.pm
Criterion Covered Total %
statement 114 145 78.6
branch 71 112 63.3
condition 20 56 35.7
subroutine 16 17 94.1
pod 9 9 100.0
total 230 339 67.8


line stmt bran cond sub pod time code
1             # ============================================================================
2             package MooseX::App::Meta::Role::Attribute::Option;
3             # ============================================================================
4              
5 14     14   51 use utf8;
  14         18  
  14         90  
6 14     14   523 use 5.010;
  14         39  
7              
8 14     14   5736 use namespace::autoclean;
  14         145709  
  14         52  
9 14     14   5359 use Moose::Role;
  14         2033554  
  14         75  
10              
11 14     14   58179 use List::Util qw(uniq);
  14         20  
  14         22391  
12              
13             has 'cmd_type' => (
14             is => 'rw',
15             isa => 'MooseX::App::Types::CmdTypes',
16             predicate => 'has_cmd_type',
17             );
18              
19             has 'cmd_tags' => (
20             is => 'rw',
21             isa => 'MooseX::App::Types::List',
22             coerce => 1,
23             predicate => 'has_cmd_tags',
24             );
25              
26             has 'cmd_flag' => (
27             is => 'rw',
28             isa => 'MooseX::App::Types::Identifier',
29             predicate => 'has_cmd_flag',
30             );
31              
32             has 'cmd_aliases' => (
33             is => 'rw',
34             isa => 'MooseX::App::Types::IdentifierList',
35             predicate => 'has_cmd_aliases',
36             coerce => 1,
37             );
38              
39             has 'cmd_split' => (
40             is => 'rw',
41             isa => Moose::Util::TypeConstraints::union([qw(Str RegexpRef)]),
42             predicate => 'has_cmd_split',
43             );
44              
45             has 'cmd_count' => (
46             is => 'rw',
47             isa => 'Bool',
48             default => sub { 0 },
49             );
50              
51             has 'cmd_negate' => (
52             is => 'rw',
53             isa => 'MooseX::App::Types::IdentifierList',
54             coerce => 1,
55             predicate => 'has_cmd_negate',
56             );
57              
58             has 'cmd_env' => (
59             is => 'rw',
60             isa => 'MooseX::App::Types::Env',
61             predicate => 'has_cmd_env',
62             );
63              
64             has 'cmd_position' => (
65             is => 'rw',
66             isa => 'Int',
67             default => sub { 0 },
68             );
69              
70             my $GLOBAL_COUNTER = 1;
71              
72             around 'new' => sub {
73             my $orig = shift;
74             my $class = shift;
75              
76             my $self = $class->$orig(@_);
77              
78             if ($self->has_cmd_type) {
79             if ($self->cmd_position == 0) {
80             $GLOBAL_COUNTER++;
81             $self->cmd_position($GLOBAL_COUNTER);
82             }
83             }
84              
85             return $self;
86             };
87              
88             sub cmd_check {
89 151     151 1 184 my ($self) = @_;
90              
91 151         512 my $name = $self->name;
92 151         168 my $from_constraint;
93 151         4389 my $type_constraint = $self->type_constraint;
94 151 50 33     1533 $from_constraint = $type_constraint->parameterized_from
95             if $type_constraint && $type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized');
96              
97             # Check for useless flags
98 151 50       5813 if ($self->cmd_type eq 'parameter') {
99 0 0       0 if ($self->cmd_count) {
100 0         0 Moose->throw_error("Parameter $name has 'cmd_count'. This attribute only works with options");
101             }
102 0 0       0 if ($self->has_cmd_negate) {
103 0         0 Moose->throw_error("Parameter $name has 'cmd_negate'. This attribute only works with options");
104             }
105 0 0       0 if ($self->has_cmd_negate) {
106 0         0 Moose->throw_error("Parameter $name has 'cmd_negate'. This attribute only works with options");
107             }
108 0 0 0     0 if (defined $type_constraint
109             && $type_constraint->is_a_type_of('Bool')) {
110 0         0 Moose->throw_error("Parameter $name has 'cmd_negate'. This attribute only works with options");
111             }
112 0 0 0     0 if (($from_constraint && $from_constraint->is_a_type_of('Ref'))
      0        
      0        
113             || ($type_constraint && $type_constraint->is_a_type_of('Ref'))) {
114 0         0 Moose->throw_error("Parameter $name may not have Ref type constraints");
115             }
116             }
117              
118             # Check negate
119 151 0 0     4943 if ($self->has_cmd_negate
      33        
120             && (!$type_constraint || $type_constraint->is_a_type_of('Bool'))) {
121 0         0 Moose->throw_error("Option $name has 'cmd_negate' but has no Bool type constraint");
122             }
123              
124             # Check type constraints
125 151 50       352 if (defined $type_constraint) {
126 151 50 33     4300 if ($self->cmd_count
127             && ! $type_constraint->is_a_type_of('Num')) {
128 0         0 Moose->throw_error("Option $name has 'cmd_count' but has no Num/Int type constraint");
129             }
130 151 50 0     4797 if ($self->has_cmd_split
      33        
131             && ! (
132             ($from_constraint && $from_constraint->is_a_type_of('ArrayRef'))
133             || $type_constraint->is_a_type_of('ArrayRef'))
134             ) {
135 0         0 Moose->throw_error("Option $name has 'cmd_split' but has no ArrayRef type constraint");
136             }
137             }
138              
139             # Check for uniqness
140 151         396 my @names = $self->cmd_name_possible;
141 151 50       806 if (scalar(uniq(@names)) != scalar(@names)) {
142 0         0 Moose->throw_error("Option $name has duplicate names/aliases");
143             }
144              
145 151         476 return;
146             }
147              
148             sub cmd_type_constraint_description {
149 6     6 1 15 my ($self,$type_constraint,$singular) = @_;
150              
151 6   33     17 $type_constraint //= $self->type_constraint;
152 6   50     53 $singular //= 1;
153              
154 6 100       78 if ($type_constraint->isa('Moose::Meta::TypeConstraint::Enum')) {
    50          
    100          
    50          
    0          
    0          
155 1         3 return 'one of these values: '.join(', ',@{$type_constraint->values});
  1         27  
156             } elsif ($type_constraint->isa('Moose::Meta::TypeConstraint::Parameterized')) {
157 0         0 my $from = $type_constraint->parameterized_from;
158 0 0       0 if ($from->is_a_type_of('ArrayRef')) {
    0          
159 0         0 return $self->cmd_type_constraint_description($type_constraint->type_parameter);
160             } elsif ($from->is_a_type_of('HashRef')) {
161 0         0 return 'key-value pairs of '.$self->cmd_type_constraint_description($type_constraint->type_parameter,0);
162             }
163             # TODO union
164             } elsif ($type_constraint->equals('Int')) {
165 4 50       335 return $singular ? 'an integer':'integers'; # LOCALIZE
166             } elsif ($type_constraint->equals('Num')) {
167 1 50       160 return $singular ? 'a number':'numbers'; # LOCALIZE
168             } elsif ($type_constraint->equals('Str')) {
169 0 0       0 return $singular ? 'a string':'strings';
170             } elsif ($type_constraint->equals('HashRef')) {
171 0         0 return 'key-value pairs'; # LOCALIZE
172             }
173              
174 0 0       0 if ($type_constraint->has_parent) {
175 0         0 return $self->cmd_type_constraint_description($type_constraint->parent);
176             }
177              
178 0         0 return;
179             }
180              
181             sub cmd_type_constraint_check {
182 114     114 1 185 my ($self,$value) = @_;
183              
184             return
185 114 50       3452 unless ($self->has_type_constraint);
186 114         3211 my $type_constraint = $self->type_constraint;
187              
188 114 100       3679 if ($type_constraint->has_coercion) {
189 6         52 $value = $type_constraint->coerce($value)
190             }
191              
192             # Check type constraints
193 114 100       1339 unless ($type_constraint->check($value)) {
194 7 50       499 if (ref($value) eq 'ARRAY') {
    50          
195 0         0 $value = join(', ',grep { defined } @$value);
  0         0  
196             } elsif (ref($value) eq 'HASH') {
197 0         0 $value = join(', ',map { $_.'='.$value->{$_} } keys %$value)
  0         0  
198             }
199              
200             # We have a custom message
201 7 100       225 if ($type_constraint->has_message) {
202 1         9 return $type_constraint->get_message($value);
203             # No message
204             } else {
205 6         47 my $message_human = $self->cmd_type_constraint_description($type_constraint);
206 6 50       31 if (defined $message_human) {
207 6         35 return "Value must be ". $message_human ." (not '$value')";
208             } else {
209 0         0 return $type_constraint->get_message($value);
210             }
211             }
212             }
213              
214 107         6609 return;
215             }
216              
217             sub cmd_usage_description {
218 195     195 1 233 my ($self) = @_;
219              
220 195 100       6370 my $description = ($self->has_documentation) ? $self->documentation : '';
221 195         3908 my @tags = $self->cmd_tags_list();
222 195 100       401 if (scalar @tags) {
223 145 100       337 $description .= ' '
224             if $description;
225 145         444 $description .= '['.join('; ',@tags).']';
226             }
227 195         815 return $description
228             }
229              
230             sub cmd_usage_name {
231 251     251 1 842 my ($self) = @_;
232              
233 251 100       7055 if ($self->cmd_type eq 'parameter') {
234 42         72 return $self->cmd_name_primary;
235             } else {
236             return join(' ',
237 209 100       474 map { (length($_) == 1) ? "-$_":"--$_" }
  340         1517  
238             $self->cmd_name_possible
239             );
240             }
241             }
242              
243             sub cmd_name_primary {
244 1137     1137 1 1413 my ($self) = @_;
245              
246 1137 100       35504 if ($self->has_cmd_flag) {
247 326         9507 return $self->cmd_flag;
248             } else {
249 811         2931 return $self->name;
250             }
251             }
252              
253             sub cmd_name_list {
254 1038     1038 1 1202 my ($self) = @_;
255              
256 1038         1907 my @names = ($self->cmd_name_primary);
257              
258 1038 100       33140 if ($self->has_cmd_aliases) {
259 259         388 push(@names, @{$self->cmd_aliases});
  259         7522  
260             }
261              
262 1038         2354 return @names;
263             }
264              
265             sub cmd_name_possible {
266 691     691 1 925 my ($self) = @_;
267              
268 691 50       19152 if ($self->cmd_type eq 'parameter') {
269 0         0 return $self->cmd_name_primary;
270             }
271              
272 691         1566 my @names = $self->cmd_name_list();
273              
274             # TODO check boolean type constraint
275 691 100       22068 if ($self->has_cmd_negate) {
276 12         16 push(@names, @{$self->cmd_negate});
  12         328  
277             }
278              
279 691         1578 return @names;
280             }
281              
282             sub cmd_tags_list {
283 198     198 1 314 my ($self) = @_;
284              
285 198         196 my @tags;
286              
287 198 100 66     5788 if ($self->is_required
      100        
288             && ! $self->is_lazy_build
289             && ! $self->has_default) {
290 25         1485 push(@tags,'Required')
291             }
292              
293 198 100 100     1390 if ($self->has_default && ! $self->is_default_a_coderef) {
294 1 50 33     45 if ($self->has_type_constraint
295             && $self->type_constraint->is_a_type_of('Bool')) {
296             # if ($attribute->default) {
297             # push(@tags,'Default:Enabled');
298             # } else {
299             # push(@tags,'Default:Disabled');
300             # }
301             } else {
302 0         0 push(@tags,'Default:"'.$self->default.'"');
303             }
304             }
305              
306 198 100       6959 if ($self->has_cmd_split) {
307 5         141 my $split = $self->cmd_split;
308 5 50       20 if (ref($split) eq 'Regexp') {
309 5         12 $split = "$split";
310 5         28 $split =~ s/^\(\?\^\w*:(.+)\)$/$1/x;
311             }
312 5         19 push(@tags,'Multiple','Split by "'.$split.'"');
313             }
314              
315 198 100       6219 if ($self->has_type_constraint) {
316 183         5353 my $type_constraint = $self->type_constraint;
317 183 100       1253 if ($type_constraint->is_a_type_of('ArrayRef')) {
    100          
318 10 100       1681 if (! $self->has_cmd_split) {
319 5         15 push(@tags,'Multiple');
320             }
321             } elsif ($type_constraint->is_a_type_of('HashRef')) {
322 5         2384 push(@tags,'Key-Value');
323             }
324 183 100       139501 unless ($self->should_coerce) {
325 166 100       1072 if ($type_constraint->is_a_type_of('Int')) {
    100          
    100          
    100          
326 39         3604 push(@tags,'Integer');
327             } elsif ($type_constraint->is_a_type_of('Num')) {
328 5         2346 push(@tags ,'Number');
329             } elsif ($type_constraint->is_a_type_of('Bool')) {
330 60         37198 push(@tags ,'Flag');
331             } elsif ($type_constraint->isa('Moose::Meta::TypeConstraint::Enum')) {
332 9         10756 push(@tags ,'Possible values: '.join(', ',@{$type_constraint->values}));
  9         277  
333             }
334             }
335             }
336              
337 198 100 66     68987 if ($self->can('has_cmd_env')
338             && $self->has_cmd_env) {
339 14         448 push(@tags,'Env: '.$self->cmd_env)
340             }
341              
342 198 100 33     7454 if ($self->can('cmd_tags')
      66        
343             && $self->can('cmd_tags')
344             && $self->has_cmd_tags) {
345 29         47 push(@tags,@{$self->cmd_tags});
  29         888  
346             }
347              
348 198         536 return @tags;
349             }
350              
351             {
352             package Moose::Meta::Attribute::Custom::Trait::AppOption;
353              
354 14     14   85 use strict;
  14         16  
  14         299  
355 14     14   49 use warnings;
  14         19  
  14         1045  
356              
357 0     0     sub register_implementation { return 'MooseX::App::Meta::Role::Attribute::Option' }
358             }
359              
360             1;
361              
362             =pod
363              
364             =encoding utf8
365              
366             =head1 NAME
367              
368             MooseX::App::Meta::Role::Attribute::Option - Meta attribute role for options
369              
370             =head1 DESCRIPTION
371              
372             This meta attribute role will automatically be applied to all attributes
373             that should be used as options.
374              
375             =head1 ACCESSORS
376              
377             In your app and command classes you can
378             use the following attributes in option or parameter definitions.
379              
380             option 'myoption' => (
381             is => 'rw',
382             isa => 'ArrayRef[Str]',
383             documentation => 'My special option',
384             cmd_flag => 'myopt',
385             cmd_aliases => [qw(mopt localopt)],
386             cmd_tags => [qw(Important!)],
387             cmd_env => 'MY_OPTION',
388             cmd_position => 1,
389             cmd_split => qr/,/,
390             cmd_negate => 'notoption'
391             );
392              
393             =head2 cmd_flag
394              
395             Use this name instead of the attribute name as the option name
396              
397             =head2 cmd_type
398              
399             Option to mark if this attribute should be used as an option or parameter value.
400              
401             Allowed values are:
402              
403             =over
404              
405             =item * option - Command line option
406              
407             =item * proto - Command line option that should be processed prior to other
408             options (eg. a config-file option that sets other attribues) Usually only
409             used for plugin developmemt
410              
411             =item * parameter - Positional parameter command line value
412              
413             =back
414              
415             =head2 cmd_env
416              
417             Environment variable name (only uppercase letters, numeric and underscores
418             allowed). If variable was not specified otherwise the value will be
419             taken from %ENV.
420              
421             =head2 cmd_aliases
422              
423             Arrayref of alternative option names
424              
425             =head2 cmd_tags
426              
427             Extra option tags displayed in the usage information (in brackets)
428              
429             =head2 cmd_position
430              
431             Override the order of the parameters in the usage message.
432              
433             =head2 cmd_split
434              
435             Splits multiple values at the given separator string or regular expression.
436             Only works in conjunction with an 'ArrayRef[*]' type constraint.
437             ie. '--myattr value1,value2' with cmd_split set to ',' would produce an
438             arrayref with to elements.
439              
440             =head2 cmd_count
441              
442             Similar to the Getopt::Long '+' modifier, cmd_count turns the attribute into
443             a counter. Every occurrence of the attribute in @ARGV (without any value)
444             would increment the resulting value by one
445              
446             =head2 cmd_negate
447              
448             Sets names for the negated variant of a boolean field. Only works in conjunction
449             with a 'Bool' type constraint.
450              
451             =head1 METHODS
452              
453             These methods are only of interest to plugin authors.
454              
455             =head2 cmd_check
456              
457             Runs sanity checks on options and parameters. Will usually only be executed if
458             either HARNESS_ACTIVE or APP_DEVELOPER environment are set.
459              
460             =head2 cmd_name_possible
461              
462             my @names = $attribute->cmd_name_possible();
463              
464             Returns a list of all possible option names.
465              
466             =head2 cmd_name_list
467              
468             my @names = $attribute->cmd_name_list();
469              
470             Similar to cmd_name_possible this method returns a list of option names,
471             except for names set via cmd_negate.
472              
473             =head2 cmd_name_primary
474              
475             my $name = $attribute->cmd_name_primary();
476              
477             Returns the primary option name
478              
479             =head2 cmd_usage_name
480              
481             my $name = $attribute->cmd_usage_name();
482              
483             Returns the name as used by the usage text
484              
485             =head2 cmd_usage_description
486              
487             my $name = $attribute->cmd_usage_description();
488              
489             Returns the description as used by the usage text
490              
491             =head2 cmd_tags_list
492              
493             my @tags = $attribute->cmd_tags_list();
494              
495             Returns a list of tags
496              
497             =head2 cmd_type_constraint_check
498              
499             $attribute->cmd_type_constraint_check($value)
500              
501             Checks the type constraint. Returns an error message if the check fails
502              
503             =head2 cmd_type_constraint_description
504              
505             $attribute->cmd_type_constraint_description($type_constraint,$singular)
506              
507             Creates a description of the selected type constraint.
508              
509             =cut
510