File Coverage

blib/lib/MooX/Options/Role.pm
Criterion Covered Total %
statement 231 247 93.5
branch 137 154 88.3
condition 42 51 82.3
subroutine 19 21 90.4
pod 6 6 100.0
total 435 479 90.6


line stmt bran cond sub pod time code
1             package MooX::Options::Role;
2              
3 22     22   186977 use strictures 2;
  22         213  
  22         1257  
4              
5             ## no critic (ProhibitExcessComplexity)
6              
7             our $VERSION = "4.101";
8              
9             =head1 NAME
10              
11             MooX::Options::Role - role that is apply to your object
12              
13             =head1 USAGE
14              
15             Don't use MooX::Options::Role directly. It is used by L to upgrade your module. But it is useless alone.
16              
17             =cut
18              
19 22     22   5932 use Carp qw/croak/;
  22         60  
  22         1472  
20 22     22   171 use Module::Runtime qw(use_module);
  22         62  
  22         168  
21 22     22   10048 use MooX::Options::Descriptive;
  22         76  
  22         417  
22 22     22   11171 use Scalar::Util qw/blessed/;
  22         62  
  22         29592  
23              
24             ### PRIVATE
25              
26             sub _option_name {
27 357     357   1383 my ( $name, %data ) = @_;
28 357         1278 my $cmdline_name = join( '|', grep {defined} ( $name, $data{short} ) );
  714         2464  
29             ## no critic (RegularExpressions::RequireExtendedFormatting)
30 357 100       2309 $cmdline_name =~ m/[^\w]$/
31             and croak
32             "cmdline argument '$cmdline_name' should end with a word character";
33 356 100 100     1585 $cmdline_name .= '+' if $data{repeatable} && !defined $data{format};
34 356 50       1021 $cmdline_name .= '!' if $data{negativable};
35 356 100       1098 $cmdline_name .= '!' if $data{negatable};
36 356 100       1154 $cmdline_name .= '=' . $data{format} if defined $data{format};
37 356         1671 return $cmdline_name;
38             }
39              
40             sub _options_prepare_descriptive {
41 153     153   442 my ($options_data) = @_;
42              
43 153         536 my @options;
44             my %all_options;
45 153         0 my %has_to_split;
46              
47 153         450 my $data_record_loaded = 0;
48 153         864 for my $name (
49             sort {
50             $options_data->{$a}{order}
51             <=> $options_data->{$b}{order} # sort by order
52 382 50       1269 or $a cmp $b # sort by attr name
53             } keys %$options_data
54             )
55             {
56 357         11768 my %data = %{ $options_data->{$name} };
  357         1945  
57 357         978 my $doc = $data{doc};
58 357 100       2044 $doc = "no doc for $name" if !defined $doc;
59 357         848 my $option = {};
60 357 100       988 $option->{hidden} = 1 if $data{hidden};
61              
62 357 100       1091 push @options, [] if $data{spacer_before};
63 357         1502 push @options, [ _option_name( $name, %data ), $doc, $option ];
64 356 100       1239 push @options, [] if $data{spacer_after};
65              
66 356         695 push @{ $all_options{$name} }, $name;
  356         1274  
67             croak
68             "There is already an option '$data{short}' - can't use it to shorten '$name'"
69 356 100 100     1689 if $data{short} and exists $options_data->{ $data{short} };
70             croak
71             "There is already an abbreviation '$data{short}' - can't use it to shorten '$name'"
72 355 100 100     1267 if $data{short} and defined $all_options{ $data{short} };
73 51         190 push @{ $all_options{ $data{short} } }, $name
74 354 100       911 if $data{short};
75              
76 354         1185 for ( my $i = 1; $i <= length($name); $i++ ) {
77 3105         6058 my $long_short = substr( $name, 0, $i );
78 1753         7003 push @{ $all_options{$long_short} }, $name
79             unless exists $options_data->{$long_short}
80 3105 100 100     11722 or defined $all_options{$long_short};
81             }
82              
83 354 100       1245 if ( defined $data{autosplit} ) {
84 153 100       374 if ( !$data_record_loaded ) {
85 64         379 use_module("Data::Record");
86 64         3043 use_module("Regexp::Common");
87 64         2290 Regexp::Common->import;
88 64         575119 $data_record_loaded = 1;
89             }
90             $has_to_split{$name} = Data::Record->new(
91             { split => $data{autosplit},
92             unless => $Regexp::Common::RE{quoted}
93             }
94 153         1071 );
95             }
96             }
97              
98 150         5447 return \@options, \%has_to_split, \%all_options;
99             }
100              
101             sub _options_fix_argv {
102 150     150   401 my ( $option_data, $has_to_split, $all_options ) = @_;
103              
104 150         352 my @new_argv;
105              
106             #parse all argv
107 150         621 while ( defined( my $arg = shift @ARGV ) ) {
108 161 100       519 if ( $arg eq '--' ) {
109 1         4 push @new_argv, $arg, @ARGV;
110 1         3 last;
111             }
112 160 100       558 if ( index( $arg, '-' ) != 0 ) {
113 3         6 push @new_argv, $arg;
114 3         12 next;
115             }
116              
117 157         625 my ( $arg_name_with_dash, $arg_values ) = split( /=/x, $arg, 2 );
118 157 100 100     858 if ( index( $arg_name_with_dash, '--' ) < 0 && !defined $arg_values )
119             {
120 7 100       25 $arg_values
121             = length($arg_name_with_dash) > 2
122             ? substr( $arg_name_with_dash, 2 )
123             : undef;
124 7         16 $arg_name_with_dash = substr( $arg_name_with_dash, 0, 2 );
125             }
126 157 100       538 unshift @ARGV, $arg_values if defined $arg_values;
127              
128 157         1176 my ( $dash, $negative, $arg_name_without_dash )
129             = $arg_name_with_dash =~ /^(\-+)(no\-)?(.*)$/x;
130 157         620 $arg_name_without_dash =~ s/\-/_/gx;
131              
132 157         399 my $original_long_option = $all_options->{$arg_name_without_dash};
133 157 100       427 if ( defined $original_long_option ) {
134             ## no critic (ErrorHandling::RequireCarping)
135             # uncoverable branch false
136 146 50       485 @$original_long_option == 1
137             or die
138             "Internal error, duplicate map for abbreviation detected!";
139 146         382 $original_long_option = $original_long_option->[0];
140             }
141              
142 157         333 my $arg_name = $dash;
143              
144 157 100 66     517 if ( defined $negative && defined $original_long_option ) {
145             $arg_name .=
146             $option_data->{$original_long_option}{negatable}
147 2 50       16 ? 'no-'
148             : 'no_';
149             }
150              
151 157         341 $arg_name .= $arg_name_without_dash;
152              
153 157 100 100     917 if ( defined $original_long_option
154             && ( defined( my $arg_value = shift @ARGV ) ) )
155             {
156 140         388 my $autorange = $option_data->{$original_long_option}{autorange};
157             my $argv_processor = sub {
158              
159             #remove the quoted if exist to chain
160 189     189   715 $_[0] =~ s/^['"]|['"]$//gx;
161 189 100       472 if ($autorange) {
162             push @new_argv,
163 90         300 map { $arg_name => $_ } _expand_autorange( $_[0] );
  136         893  
164             }
165             else {
166 99         818 push @new_argv, $arg_name, $_[0];
167             }
168              
169 140         862 };
170              
171 140 100       494 if ( my $rec = $has_to_split->{$original_long_option} ) {
172 106         525 foreach my $record ( $rec->records($arg_value) ) {
173 155         26118 $argv_processor->($record);
174             }
175             }
176             else {
177 34         99 $argv_processor->($arg_value);
178             }
179             }
180             else {
181 17         92 push @new_argv, $arg_name;
182             }
183             }
184              
185 150         609 return @new_argv;
186             }
187              
188             sub _expand_autorange {
189 90     90   187 my ($arg_value) = @_;
190              
191 90         156 my @expanded_arg_value;
192 90         337 my ( $left_figure, $autorange_found, $right_figure )
193             = $arg_value =~ /^(\d*)(\.\.)(\d*)$/x;
194 90 100       212 if ($autorange_found) {
195 31 100       72 $left_figure = $right_figure unless length($left_figure);
196 31 100       71 $right_figure = $left_figure unless length($right_figure);
197 31 100 66     128 if ( length $left_figure && length $right_figure ) {
198 29         133 push @expanded_arg_value, $left_figure .. $right_figure;
199             }
200             }
201 90 100       330 return @expanded_arg_value ? @expanded_arg_value : $arg_value;
202             }
203              
204             ### PRIVATE
205              
206 22     22   213 use Moo::Role;
  22         66  
  22         231  
207             with "MooX::Locale::Passthrough";
208              
209             requires qw/_options_data _options_config/;
210              
211             =head1 METHODS
212              
213             These methods will be composed into your class
214              
215             =head2 new_with_options
216              
217             Same as new but parse ARGV with L
218              
219             Check full doc L for more details.
220              
221             =cut
222              
223             sub new_with_options {
224 126     126 1 335005 my ( $class, %params ) = @_;
225              
226             #save subcommand
227              
228 126 50       783 if ( ref( my $command_chain = $params{command_chain} ) eq 'ARRAY' ) {
229             $class->can('around')->(
230             _options_prog_name => sub {
231 0     0   0 my $prog_name = Getopt::Long::Descriptive::prog_name;
232 0         0 for my $cmd (@$command_chain) {
233 0 0 0     0 next if !blessed $cmd || !$cmd->can('command_name');
234 0 0       0 if ( defined( my $cmd_name = $cmd->command_name ) ) {
235 0         0 $prog_name .= ' ' . $cmd_name;
236             }
237             }
238              
239 0         0 return $prog_name;
240             }
241 0         0 );
242             }
243              
244 126 50       551 if ( ref( my $command_commands = $params{command_commands} ) eq 'HASH' ) {
245             $class->can('around')->(
246             _options_sub_commands => sub {
247             return [
248             ## no critic (BuiltinFunctions::RequireBlockMap)
249             map +{
250             name => $_,
251 0     0   0 command => $command_commands->{$_},
252             },
253             sort keys %$command_commands
254             ];
255             }
256 0         0 );
257             }
258              
259 126         782 my %cmdline_params = $class->parse_options(%params);
260              
261 112 100       789 if ( $cmdline_params{h} ) {
262 1         25 return $class->options_usage( $params{h}, $cmdline_params{h} );
263             }
264 111 100       389 if ( $cmdline_params{help} ) {
265 5         61 return $class->options_help( $params{help}, $cmdline_params{help} );
266             }
267 106 100       373 if ( $cmdline_params{man} ) {
268 1         27 return $class->options_man( $cmdline_params{man} );
269             }
270 105 100       352 if ( $cmdline_params{usage} ) {
271             return $class->options_short_usage( $params{usage},
272 1         12 $cmdline_params{usage} );
273             }
274              
275 104         202 my $self;
276             return $self
277 104 100       217 if eval { $self = $class->new(%cmdline_params); 1 };
  104         2395  
  100         43446  
278 4 50       7398 if ( $@ =~ /^Attribute\s\((.*?)\)\sis\srequired/x ) {
    100          
    50          
    50          
279 0         0 print STDERR "$1 is missing\n";
280             }
281             elsif ( $@ =~ /^Missing\srequired\sarguments:\s(.*)\sat\s/x ) {
282 2         15 my @missing_required = split /,\s/x, $1;
283             print STDERR
284             join( "\n",
285 2         8 ( map { $_ . " is missing" } @missing_required ), '' );
  4         148  
286             }
287             elsif ( $@ =~ /^(.*?)\srequired/x ) {
288 0         0 print STDERR "$1 is missing\n";
289             }
290             elsif ( $@ =~ /^isa\scheck.*?failed:\s/x ) {
291 2         212 print STDERR substr( $@, index( $@, ':' ) + 2 );
292             }
293             else {
294 0         0 print STDERR $@;
295             }
296 4         52 %cmdline_params = $class->parse_options( h => 1 );
297 4         26 return $class->options_usage( 1, $cmdline_params{h} );
298             }
299              
300             =head2 parse_options
301              
302             Parse your options, call L and convert the result for the "new" method.
303              
304             It is use by "new_with_options".
305              
306             =cut
307              
308             my $decode_json;
309              
310             sub parse_options {
311 153     153 1 591 my ( $class, %params ) = @_;
312              
313 153         5301 my %options_data = $class->_options_data;
314 153         7326 my %options_config = $class->_options_config;
315 153 100       3996 if ( defined $options_config{skip_options} ) {
316 150         405 delete @options_data{ @{ $options_config{skip_options} } };
  150         490  
317             }
318              
319 153         730 my ( $options, $has_to_split, $all_options )
320             = _options_prepare_descriptive( \%options_data );
321              
322 150 100       878 local @ARGV = @ARGV if $options_config{protect_argv};
323 150         622 @ARGV = _options_fix_argv( \%options_data, $has_to_split, $all_options );
324              
325 150         369 my @flavour;
326 150 100       559 if ( defined $options_config{flavour} ) {
327 147         617 push @flavour, { getopt_conf => $options_config{flavour} };
328             }
329              
330 150         679 my $prog_name = $class->_options_prog_name();
331              
332             # create usage str
333 150         920 my $usage_str = $options_config{usage_string};
334 150 100       991 $usage_str = sprintf( $class->__("USAGE: %s %s"),
335             $prog_name, " [-h] [" . $class->__("long options ...") . "]" )
336             if !defined $usage_str;
337              
338 150         2054 my ( $opt, $usage ) = describe_options(
339             ($usage_str),
340             @$options,
341             [],
342             [ 'usage', $class->__("show a short help message") ],
343             [ 'h', $class->__("show a compact help message") ],
344             [ 'help', $class->__("show a long help message") ],
345             [ 'man', $class->__("show the manual") ],
346             ,
347             @flavour
348             );
349              
350 150         270632 $usage->{prog_name} = $prog_name;
351 150         508 $usage->{target} = $class;
352              
353 150 100       609 if ( $usage->{should_die} ) {
354 9         65 return $class->options_usage( 1, $usage );
355             }
356              
357 141         454 my %cmdline_params = %params;
358 141         491 for my $name ( keys %options_data ) {
359 335         562 my %data = %{ $options_data{$name} };
  335         1622  
360 335 100 100     1346 if ( !defined $cmdline_params{$name}
361             || $options_config{prefer_commandline} )
362             {
363 332         1177 my $val = $opt->$name();
364 332 100       2427 if ( defined $val ) {
365 87 100       281 if ( $data{json} ) {
366             defined $decode_json
367 4 100       15 or $decode_json = eval {
368 1         9 use_module("JSON::MaybeXS");
369 1         89 JSON::MaybeXS->can("decode_json");
370             };
371             defined $decode_json
372 4 50       15 or $decode_json = eval {
373 0         0 use_module("JSON::PP");
374 0         0 JSON::PP->can("decode_json");
375             };
376             ## no critic (ErrorHandling::RequireCarping)
377 4 50       11 $@ and die $@;
378 4 100       7 if (!eval {
379 4         49 $cmdline_params{$name} = $decode_json->($val);
380 2         9 1;
381             }
382             )
383             {
384 2         111 print STDERR $@;
385 2         15 return $class->options_usage( 1, $usage );
386             }
387             }
388             else {
389 83         294 $cmdline_params{$name} = $val;
390             }
391             }
392             }
393             }
394              
395 139 100 66     523 if ( $opt->h() || defined $params{h} ) {
396 5         47 $cmdline_params{h} = $usage;
397             }
398              
399 139 100 100     1452 if ( $opt->help() || defined $params{help} ) {
400 26         214 $cmdline_params{help} = $usage;
401             }
402              
403 139 100 100     1153 if ( $opt->man() || defined $params{man} ) {
404 3         25 $cmdline_params{man} = $usage;
405             }
406              
407 139 100 66     1119 if ( $opt->usage() || defined $params{usage} ) {
408 1         11 $cmdline_params{usage} = $usage;
409             }
410              
411 139         4515 return %cmdline_params;
412             }
413              
414             =head2 options_usage
415              
416             Display help message.
417              
418             Check full doc L for more details.
419              
420             =cut
421              
422             sub options_usage {
423 31     31 1 30783 my ( $class, $code, @messages ) = @_;
424 31         87 my $usage;
425 31 100 100     251 if ( @messages
426             && ref $messages[-1] eq 'MooX::Options::Descriptive::Usage' )
427             {
428 16         206 $usage = shift @messages;
429             }
430 31 100       132 $code = 0 if !defined $code;
431 31 100       159 if ( !$usage ) {
432 15         77 local @ARGV = ();
433 15         73 my %cmdline_params = $class->parse_options( help => $code );
434 15         66 $usage = $cmdline_params{help};
435             }
436 31         160 my $message = "";
437 31 100       150 $message .= join( "\n", @messages, '' ) if @messages;
438 31         198 $message .= $usage . "\n";
439 31 100       192 if ( $code > 0 ) {
440 17         329 CORE::warn $message;
441             }
442             else {
443 14         3115 print $message;
444             }
445 31 100       22109 exit($code) if $code >= 0;
446 1         4 return;
447             }
448              
449             =head2 options_help
450              
451             Display long usage message
452              
453             =cut
454              
455             sub options_help {
456 10     10 1 23891 my ( $class, $code, $usage ) = @_;
457 10 100       57 $code = 0 if !defined $code;
458              
459 10 100 66     83 if ( !defined $usage || !ref $usage ) {
460 5         24 local @ARGV = ();
461 5         39 my %cmdline_params = $class->parse_options( help => $code );
462 5         27 $usage = $cmdline_params{help};
463             }
464 10         65 my $message = $usage->option_help . "\n";
465 10 50       56 if ( $code > 0 ) {
466 0         0 CORE::warn $message;
467             }
468             else {
469 10         681 print $message;
470             }
471 10 100       112 exit($code) if $code >= 0;
472 1         3 return;
473             }
474              
475             =head2 options_short_usage
476              
477             Display quick usage message, with only the list of options
478              
479             =cut
480              
481             sub options_short_usage {
482 2     2 1 2139 my ( $class, $code, $usage ) = @_;
483 2 100       12 $code = 0 if !defined $code;
484              
485 2 100 66     18 if ( !defined $usage || !ref $usage ) {
486 1         4 local @ARGV = ();
487 1         8 my %cmdline_params = $class->parse_options( help => $code );
488 1         7 $usage = $cmdline_params{help};
489             }
490 2         20 my $message = "USAGE: " . $usage->option_short_usage . "\n";
491 2 100       10 if ( $code > 0 ) {
492 1         18 CORE::warn $message;
493             }
494             else {
495 1         81 print $message;
496             }
497 2 50       129 exit($code) if $code >= 0;
498 0         0 return;
499             }
500              
501             =head2 options_man
502              
503             Display a pod like a manual
504              
505             =cut
506              
507             sub options_man {
508 3     3 1 6347 my ( $class, $usage, $output ) = @_;
509 3         12 local @ARGV = ();
510 3 100       14 if ( !$usage ) {
511 2         6 local @ARGV = ();
512 2         10 my %cmdline_params = $class->parse_options( man => 1 );
513 2         10 $usage = $cmdline_params{man};
514             }
515              
516 3         24 use_module( "Path::Class", "0.32" );
517 3         27714 my $man_file
518             = Path::Class::file( Path::Class::tempdir( CLEANUP => 1 ),
519             'help.pod' );
520 3         2152 $man_file->spew( iomode => '>:encoding(UTF-8)', $usage->option_pod );
521              
522 3         3157 use_module("Pod::Usage");
523 3         49399 Pod::Usage::pod2usage(
524             -verbose => 2,
525             -input => $man_file->stringify,
526             -exitval => 'NOEXIT',
527             -output => $output
528             );
529              
530 3         966878 exit(0);
531             }
532              
533             ### PRIVATE NEED TO BE EXPORTED
534              
535             sub _options_prog_name {
536 150     150   1015 return Getopt::Long::Descriptive::prog_name;
537             }
538              
539             sub _options_sub_commands {
540 94     94   587 return;
541             }
542              
543             ### PRIVATE NEED TO BE EXPORTED
544              
545             =head1 SUPPORT
546              
547             You can find documentation for this module with the perldoc command.
548              
549             perldoc MooX::ConfigFromFile
550              
551             You can also look for information at:
552              
553             =over 4
554              
555             =item * RT: CPAN's request tracker (report bugs here)
556              
557             L
558              
559             =item * AnnoCPAN: Annotated CPAN documentation
560              
561             L
562              
563             =item * CPAN Ratings
564              
565             L
566              
567             =item * Search CPAN
568              
569             L
570              
571             =back
572              
573             =head1 AUTHOR
574              
575             celogeek
576              
577             =head1 COPYRIGHT AND LICENSE
578              
579             This software is copyright (c) 2013 by celogeek .
580              
581             This software is copyright (c) 2017 by Jens Rehsack.
582              
583             This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
584              
585             =cut
586              
587             1;