File Coverage

blib/lib/MooX/Options/Role.pm
Criterion Covered Total %
statement 185 259 71.4
branch 95 156 60.2
condition 20 42 47.6
subroutine 16 21 76.1
pod 6 6 100.0
total 322 484 66.3


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