File Coverage

blib/lib/OptArgs2.pm
Criterion Covered Total %
statement 319 451 70.7
branch 93 184 50.5
condition 48 144 33.3
subroutine 37 49 75.5
pod 9 23 39.1
total 506 851 59.4


line stmt bran cond sub pod time code
1 3     3   384657 use strict;
  3         15  
  3         73  
2 3     3   14 use warnings;
  3         5  
  3         87  
3              
4             package OptArgs2;
5 3     3   477 use Encode qw/decode/;
  3         8894  
  3         163  
6             use Exporter::Tidy
7 3         17 default => [qw/class_optargs cmd optargs subcmd arg opt/],
8 3     3   3974 other => [qw/usage cols rows/];
  3         37  
9              
10             our $VERSION = '2.0.0_6';
11             our @CARP_NOT = (
12             qw/
13             OptArgs2
14             OptArgs2::Arg
15             OptArgs2::Cmd
16             OptArgs2::CmdBase
17             OptArgs2::Opt
18             OptArgs2::OptArgBase
19             OptArgs2::SubCmd
20             /
21             );
22              
23             # constants
24             sub STYLE_USAGE() { 'Usage' } # default
25             sub STYLE_HELP() { 'Help' }
26             sub STYLE_HELPTREE() { 'HelpTree' }
27             sub STYLE_HELPSUMMARY() { 'HelpSummary' }
28              
29             our %CURRENT; # legacy interface
30             my %COMMAND;
31             my @chars;
32              
33             sub _chars {
34 0 0   0   0 if ( $^O eq 'MSWin32' ) {
35 0         0 require Win32::Console;
36 0         0 @chars = Win32::Console->new()->Size();
37             }
38             else {
39 0         0 require Term::Size::Perl;
40 0         0 @chars = Term::Size::Perl::chars();
41             }
42 0         0 \@chars;
43             }
44              
45             sub cols {
46 0   0 0 1 0 $chars[0] // _chars()->[0];
47             }
48              
49             sub rows {
50 0   0 0 1 0 $chars[1] // _chars()->[1];
51             }
52              
53             my %error_types = (
54             CmdExists => undef,
55             CmdNotFound => undef,
56             Conflict => undef,
57             InvalidIsa => undef,
58             ParentCmdNotFound => undef,
59             SubCmdExists => undef,
60             UndefOptArg => undef,
61             Usage => undef,
62             );
63              
64             sub throw_error {
65 1     1 0 6 require Carp;
66              
67 1         2 my $proto = shift;
68 1   33     4 my $type = shift // Carp::croak( 'Usage', 'error($TYPE, [$msg])' );
69 1         3 my $pkg = 'OptArgs2::Error::' . $type;
70 1   33     2 my $msg = shift // "($pkg)";
71 1 50       5 $msg = sprintf( $msg, @_ ) if @_;
72              
73             Carp::croak( 'Usage', "unknown error type: $type" )
74 1 50       3 unless exists $error_types{$type};
75              
76 1         241 $msg .= ' ' . Carp::longmess('');
77              
78 3     3   1109 no strict 'refs';
  3         5  
  3         615  
79 1         29 *{ $pkg . '::ISA' } = ['OptArgs2::Status'];
  1         17  
80              
81 1         7 die bless \$msg, $pkg;
82             }
83              
84             my %usage_types = (
85             ArgRequired => undef,
86             Help => undef,
87             HelpSummary => undef,
88             HelpTree => undef,
89             OptRequired => undef,
90             OptUnknown => undef,
91             SubCmdRequired => undef,
92             SubCmdUnknown => undef,
93             UnexpectedOptArg => undef,
94             );
95              
96             sub throw_usage {
97 4     4 0 8 my $proto = shift;
98 4   33     10 my $type = shift // $proto->error( 'Usage', 'usage($TYPE, $str)' );
99 4   33     24 my $str = shift // $proto->error( 'Usage', 'usage($type, $STR)' );
100 4         8 my $pkg = 'OptArgs2::Usage::' . $type;
101              
102             $proto->error( 'Usage', "unknown usage reason: $type" )
103 4 50       11 unless exists $usage_types{$type};
104              
105 4 50       27 if ( -t STDERR ) {
106 0         0 my $lines = scalar( split /\n/, $str );
107 0 0       0 $lines++ if $str =~ m/\n\z/;
108              
109 0 0       0 if ( $lines >= OptArgs2::rows() ) {
110 0         0 require OptArgs2::Pager;
111 0         0 my $pager = OptArgs2::Pager->new( auto => 0 );
112 0         0 local *STDERR = $pager->fh;
113              
114 3     3   19 no strict 'refs';
  3         5  
  3         219  
115 0         0 *{ $pkg . '::ISA' } = ['OptArgs2::Status'];
  0         0  
116 0         0 die bless \$str, $pkg;
117             }
118             }
119              
120 3     3   16 no strict 'refs';
  3         6  
  3         1991  
121 4         13 *{ $pkg . '::ISA' } = ['OptArgs2::Status'];
  4         51  
122 4         39 die bless \$str, $pkg;
123             }
124              
125             sub class_optargs {
126 11   33 11 1 8206 my $class = shift
127             || OptArgs2->throw_error( 'Usage', 'class_optargs($CMD,[@argv])' );
128              
129 11   33     44 my $cmd = $COMMAND{$class}
130             || OptArgs2->throw_error( 'CmdNotFound',
131             'command class not found: ' . $class );
132              
133 11         21 my @source = @_;
134              
135 11 100 100     33 if ( !@_ and @ARGV ) {
136             my $CODESET =
137 1         2 eval { require I18N::Langinfo; I18N::Langinfo::CODESET() };
  1         630  
  1         678  
138              
139 1 50       5 if ($CODESET) {
140 1         3 my $codeset = I18N::Langinfo::langinfo($CODESET);
141 1         8 $_ = decode( $codeset, $_ ) for @ARGV;
142             }
143              
144 1         596 @source = @ARGV;
145             }
146              
147 11         38 $cmd->parse(@source);
148             }
149              
150             sub cmd {
151 6   33 6 1 6356 my $class = shift || OptArgs2->throw_error( 'Usage', 'cmd($CLASS,@args)' );
152              
153             OptArgs2->throw_error( 'CmdExists', "command already defined: $class" )
154 6 50       20 if exists $COMMAND{$class};
155              
156 6         46 $COMMAND{$class} = OptArgs2::Cmd->new( class => $class, @_ );
157             }
158              
159             sub optargs {
160 1     1 1 5663 my $class = caller;
161 1         6 cmd( $class, @_ );
162 1         4 ( class_optargs($class) )[1];
163             }
164              
165             sub subcmd {
166 6   33 6 1 153 my $class =
167             shift || OptArgs2->throw_error( 'Usage', 'subcmd($CLASS,%%args)' );
168              
169             OptArgs2->throw_error( 'SubCmdExists',
170             "subcommand already defined: $class" )
171 6 50       24 if exists $COMMAND{$class};
172              
173 6 100       46 OptArgs2->throw_error( 'ParentCmdNotFound',
174             "no '::' in class '$class' - must have a parent" )
175             unless $class =~ m/(.+)::(.+)/;
176              
177 5         12 my $parent_class = $1;
178              
179             OptArgs2->throw_error( 'ParentCmdNotFound',
180             "parent class not found: " . $parent_class )
181 5 50       14 unless exists $COMMAND{$parent_class};
182              
183 5         16 $COMMAND{$class} = $COMMAND{$parent_class}->add_cmd(
184             class => $class,
185             @_
186             );
187             }
188              
189             sub usage {
190 0   0 0 1 0 my $class = shift || do {
191             my ($pkg) = caller;
192             $pkg;
193             };
194 0         0 my $style = shift;
195              
196             OptArgs2->throw_error( 'CmdNotFound', "command not found: $class" )
197 0 0       0 unless exists $COMMAND{$class};
198              
199 0         0 return $COMMAND{$class}->usage_string($style);
200             }
201              
202             # Legacy interface, no longer documented
203              
204             sub arg {
205 0     0 1 0 my $name = shift;
206              
207 0   0     0 $OptArgs2::CURRENT //= cmd( ( scalar caller ), comment => '' );
208 0         0 $OptArgs2::CURRENT->add_arg(
209             name => $name,
210             @_,
211             );
212             }
213              
214             sub opt {
215 0     0 1 0 my $name = shift;
216              
217 0   0     0 $OptArgs2::CURRENT //= cmd( ( scalar caller ), comment => '' );
218 0         0 $OptArgs2::CURRENT->add_opt(
219             name => $name,
220             @_,
221             );
222             }
223              
224             package OptArgs2::Status {
225             use overload
226 5     5   151 bool => sub { 1 },
227 0     0   0 '""' => sub { ${ $_[0] } },
  0         0  
228 3     3   1081 fallback => 1;
  3         869  
  3         26  
229             }
230              
231             package OptArgs2::CODEREF {
232             our @CARP_NOT = @OptArgs2::CARP_NOT;
233              
234             sub TIESCALAR {
235 0     0   0 my $class = shift;
236 0 0       0 ( 3 == @_ )
237             or Optargs2->throw_error( 'Usage', 'args: optargs,name,sub' );
238 0         0 return bless [@_], $class;
239             }
240              
241             sub FETCH {
242 0     0   0 my $self = shift;
243 0         0 my ( $optargs, $name, $sub ) = @$self;
244 0         0 untie $optargs->{$name};
245 0         0 $optargs->{$name} = $sub->($optargs);
246             }
247             }
248              
249             package OptArgs2::OptArgBase {
250             use OptArgs2::OptArgBase_CI
251 3         43 abstract => 1,
252             has => {
253             comment => { required => 1, },
254             default => {},
255             getopt => {},
256             isa => { required => 1, },
257             isa_name => { is => 'rw', },
258             name => { required => 1, },
259             required => {},
260             show_default => {},
261             },
262 3     3   1775 ;
  3         7  
263              
264             our @CARP_NOT = @OptArgs2::CARP_NOT;
265             our %isa2name = (
266             'ArrayRef' => 'Str',
267             'Bool' => '',
268             'Counter' => '',
269             'Flag' => '',
270             'HashRef' => 'Str',
271             'Int' => 'Int',
272             'Num' => 'Num',
273             'Str' => 'Str',
274             'SubCmd' => 'Str',
275             );
276              
277             }
278              
279             package OptArgs2::Arg {
280             use OptArgs2::Arg_CI
281 3         21 isa => 'OptArgs2::OptArgBase',
282             has => {
283             cmd => { is => 'rw', weaken => 1, },
284             fallthru => {},
285             greedy => {},
286 3     3   1006 };
  3         10  
287              
288             our @CARP_NOT = @OptArgs2::CARP_NOT;
289             my %arg2getopt = (
290             'Str' => '=s',
291             'Int' => '=i',
292             'Num' => '=f',
293             'ArrayRef' => '=s@',
294             'HashRef' => '=s%',
295             'SubCmd' => '=s',
296             );
297              
298             sub BUILD {
299 6     6 0 8 my $self = shift;
300              
301 6 50 66     22 OptArgs2->throw_error( 'Conflict',
302             q{'default' and 'required' conflict} )
303             if $self->required and defined $self->default;
304             }
305              
306             sub name_alias_type_comment {
307 4     4 0 6 my $self = shift;
308 4         7 my $value = shift;
309              
310 4         5 my $deftype = '';
311 4 50       8 if ( defined $value ) {
312 0         0 $deftype = '[' . $value . ']';
313             }
314             else {
315             $deftype = $self->isa_name
316 4   33     11 // $OptArgs2::OptArgBase::isa2name{ $self->isa }
      33        
317             // OptArgs2->throw_error( 'InvalidIsa',
318             'invalid isa type: ' . $self->isa );
319             }
320              
321 4         13 my $comment = $self->comment;
322 4 100       8 if ( $self->required ) {
323 3 50       15 $comment .= ' ' if length $comment;
324 3         6 $comment .= '*required*';
325             }
326              
327 4         10 return $self->name, '', $deftype, $comment;
328             }
329              
330             }
331              
332             package OptArgs2::Opt {
333             use OptArgs2::Opt_CI
334 3         25 isa => 'OptArgs2::OptArgBase',
335             has => {
336             alias => {},
337             hidden => {},
338             trigger => {},
339 3     3   1079 };
  3         6  
340              
341             our @CARP_NOT = @OptArgs2::CARP_NOT;
342              
343             my %isa2getopt = (
344             'ArrayRef' => '=s@',
345             'Bool' => '!',
346             'Counter' => '+',
347             'Flag' => '!',
348             'HashRef' => '=s%',
349             'Int' => '=i',
350             'Num' => '=f',
351             'Str' => '=s',
352             );
353              
354             sub new_from {
355 8     8 0 13 my $proto = shift;
356 8         29 my $ref = {@_};
357              
358             # legacy interface
359 8 50       21 if ( exists $ref->{ishelp} ) {
360 0         0 delete $ref->{ishelp};
361 0   0     0 $ref->{isa} //= OptArgs2::STYLE_HELP;
362             }
363              
364 8 100       45 if ( $ref->{isa} =~ m/^Help/ ) { # one of the STYLE_HELPs
365 6         12 my $style = $ref->{isa};
366 6         8 my $name = $style;
367 6         33 $name =~ s/([a-z])([A-Z])/$1-$2/g;
368 6         9 $ref->{isa} = 'Counter';
369 6   33     29 $ref->{name} //= lc $name;
370 6   33     39 $ref->{alias} //= lc substr $ref->{name}, 0, 1;
371 6   33     38 $ref->{comment} //= "print a $style message and exit";
372             $ref->{trigger} //= sub {
373 0     0   0 my $cmd = shift;
374 0         0 my $val = shift;
375              
376 0 0       0 if ( $val == 1 ) {
    0          
377 0         0 OptArgs2->throw_usage( OptArgs2::STYLE_HELP,
378             $cmd->usage_string(OptArgs2::STYLE_HELP) );
379             }
380             elsif ( $val == 2 ) {
381 0         0 OptArgs2->throw_usage( OptArgs2::STYLE_HELPTREE,
382             $cmd->usage_string(OptArgs2::STYLE_HELPTREE) );
383             }
384             else {
385 0         0 OptArgs2->throw_usage(
386             'UnexpectedOptArg',
387             $cmd->usage_string(
388             OptArgs2::STYLE_USAGE,
389             qq{"--$ref->{name}" used too many times}
390             )
391             );
392             }
393 6   50     37 };
394             }
395              
396 8 50       27 if ( !exists $isa2getopt{ $ref->{isa} } ) {
397             return OptArgs2->throw_error( 'InvalidIsa',
398             'invalid isa "%s" for opt "%s"',
399 0         0 $ref->{isa}, $ref->{name} );
400             }
401              
402 8         17 $ref->{getopt} = $ref->{name};
403 8 50       20 if ( $ref->{name} =~ m/_/ ) {
404 0         0 ( my $x = $ref->{name} ) =~ s/_/-/g;
405 0         0 $ref->{getopt} .= '|' . $x;
406             }
407 8 100       21 $ref->{getopt} .= '|' . $ref->{alias} if $ref->{alias};
408 8         18 $ref->{getopt} .= $isa2getopt{ $ref->{isa} };
409              
410 8         40 return $proto->new(%$ref);
411             }
412              
413             sub name_alias_type_comment {
414 4     4 0 7 my $self = shift;
415 4         6 my $value = shift;
416              
417 4         8 ( my $opt = $self->name ) =~ s/_/-/g;
418 4 50       18 if ( $self->isa eq 'Bool' ) {
419 0 0       0 if ($value) {
    0          
420 0         0 $opt = 'no-' . $opt;
421             }
422             elsif ( not defined $value ) {
423 0         0 $opt = '[no-]' . $opt;
424             }
425             }
426 4         10 $opt = '--' . $opt;
427              
428 4   50     16 my $alias = $self->alias // '';
429 4 50       16 if ( length $alias ) {
430 4         7 $opt .= ',';
431 4         6 $alias = '-' . $alias;
432             }
433              
434 4         6 my $deftype = '';
435 4 50       9 if ( defined $value ) {
436 0 0       0 if ( $self->isa eq 'Flag' ) {
    0          
    0          
437 0         0 $deftype = '(set)';
438             }
439             elsif ( $self->isa eq 'Bool' ) {
440 0 0       0 $deftype = '(' . ( $value ? 'true' : 'false' ) . ')';
441             }
442             elsif ( $self->isa eq 'Counter' ) {
443 0         0 $deftype = '(' . $value . ')';
444             }
445             else {
446 0         0 $deftype = '[' . $value . ']';
447             }
448             }
449             else {
450             $deftype = $self->isa_name
451 4   33     10 // $OptArgs2::OptArgBase::isa2name{ $self->isa }
      33        
452             // OptArgs2->throw_error( 'InvalidIsa',
453             'invalid isa type: ' . $self->isa );
454             }
455              
456 4         13 my $comment = $self->comment;
457 4 50       7 if ( $self->required ) {
458 0 0       0 $comment .= ' ' if length $comment;
459 0         0 $comment .= '*required*';
460             }
461              
462 4         16 return $opt, $alias, $deftype, $comment;
463             }
464              
465             }
466              
467             package OptArgs2::CmdBase {
468             use overload
469 30     30   54 bool => sub { 1 },
470 0     0   0 '""' => sub { shift->class },
471 3     3   19 fallback => 1;
  3         4  
  3         36  
472 3     3   2166 use Getopt::Long qw/GetOptionsFromArray/;
  3         26877  
  3         10  
473 3     3   475 use List::Util qw/max/;
  3         8  
  3         441  
474             use OptArgs2::CmdBase_CI
475             abstract => 1,
476             has => {
477             abbrev => { is => 'rw', },
478 11         30 args => { default => sub { [] }, },
479             class => { required => 1, },
480             comment => { required => 1, },
481             hidden => {},
482             optargs => {
483             is => 'rw',
484 0         0 default => sub { [] }
485             },
486 11         34 opts => { default => sub { [] }, },
487             parent => { weaken => 1, },
488             _opts => {
489 0         0 default => sub { {} }
490             },
491             _args => {
492 0         0 default => sub { {} }
493             },
494             _subcmds => {
495 5         21 default => sub { {} }
496             },
497             show_default => { default => 0, },
498 8         79 show_color => { default => sub { -t STDERR }, },
499 6         24 subcmds => { default => sub { [] }, },
500 3         64 _values => { is => 'rw' },
501             },
502 3     3   1022 ;
  3         7  
503              
504             our @CARP_NOT = @OptArgs2::CARP_NOT;
505              
506             sub BUILD {
507 11     11 0 18 my $self = shift;
508              
509             # legacy interface
510 11 50       35 if ( 'CODE' eq ref $self->optargs ) {
511 0         0 local $OptArgs2::CURRENT = $self;
512 0         0 $self->optargs->();
513 0         0 return;
514             }
515              
516 11         20 while ( my ( $name, $args ) = splice @{ $self->optargs }, 0, 2 ) {
  19         35  
517 8 100       29 if ( $args->{isa} =~ s/^--// ) {
518 2         18 $self->add_opt(
519             name => $name,
520             %$args,
521             );
522             }
523             else {
524 6         63 $self->add_arg(
525             name => $name,
526             %$args,
527             );
528             }
529             }
530             }
531              
532             sub add_arg {
533 6     6 0 15 my $self = shift;
534 6         25 my $arg = OptArgs2::Arg->new(
535             cmd => $self,
536             show_default => $self->show_default,
537             @_,
538             );
539              
540 6         8 push( @{ $self->args }, $arg );
  6         25  
541 6         14 $arg;
542             }
543              
544             sub add_cmd {
545 5     5 0 17 my $self = shift;
546 5         15 my $subcmd = OptArgs2::SubCmd->new(
547             abbrev => $self->abbrev,
548             show_default => $self->show_default,
549             @_,
550             parent => $self,
551             );
552              
553             OptArgs2->throw_error( 'CmdExists', 'cmd exists' )
554 5 50       14 if exists $self->_subcmds->{ $subcmd->name };
555              
556 5         18 $self->_subcmds->{ $subcmd->name } = $subcmd;
557 5         7 push( @{ $self->subcmds }, $subcmd );
  5         20  
558              
559 5         14 return $subcmd;
560             }
561              
562             sub add_opt {
563 8     8 0 14 my $self = shift;
564 8         19 my $opt = OptArgs2::Opt->new_from(
565             show_default => $self->show_default,
566             @_,
567             );
568              
569 8         13 push( @{ $self->opts }, $opt );
  8         35  
570 8         27 $opt;
571             }
572              
573             sub parents {
574 23     23 0 58 my $self = shift;
575 23 100       69 return unless $self->parent;
576 4         10 return ( $self->parent->parents, $self->parent );
577             }
578              
579             sub parse {
580 11     11 0 15 my $self = shift;
581 11         17 my $source = \@_;
582              
583             map {
584 11 50       21 OptArgs2->throw_error( 'UndefOptArg',
  8         29  
585             'optargs argument undefined!' )
586             if !defined $_
587             } @$source;
588              
589 11         34 my $source_hash = { map { %$_ } grep { ref $_ eq 'HASH' } @$source };
  0         0  
  8         19  
590 11         21 $source = [ grep { ref $_ ne 'HASH' } @$source ];
  8         21  
591              
592 11         38 Getopt::Long::Configure(qw/pass_through no_auto_abbrev no_ignore_case/);
593              
594 11         516 my $reason;
595 11         15 my $optargs = {};
596 11         22 my @trigger;
597              
598 11         14 my $cmd = $self;
599              
600             # Start with the parents options
601 11         31 my @opts = map { @{ $_->opts } } $cmd->parents, $cmd;
  11         42  
  11         23  
602 11         38 my @args = @{ $cmd->args };
  11         27  
603              
604 11   66     28 OPTARGS: while ( @opts or @args ) {
605 11         27 while ( my $try = shift @opts ) {
606 13         15 my $result;
607 13         39 my $name = $try->name;
608              
609 13 50       28 if ( exists $source_hash->{$name} ) {
610 0         0 $result = delete $source_hash->{$name};
611             }
612             else {
613 13         35 GetOptionsFromArray( $source, $try->getopt => \$result );
614             }
615              
616 13 50 33     1647 if ( defined($result) and my $t = $try->trigger ) {
617 0         0 push @trigger, [ $t, $name ];
618             }
619              
620 13 100 66     56 if ( defined( $result //= $try->default ) ) {
    50          
621              
622 1 50       12 if ( 'CODE' eq ref $result ) {
623 0         0 tie $optargs->{$name}, 'OptArgs2::CODEREF', $optargs,
624             $name,
625             $result;
626             }
627             else {
628 1         5 $optargs->{$name} = $result;
629             }
630             }
631             elsif ( $try->required ) {
632 0         0 $name =~ s/_/-/g;
633 0   0     0 $reason //=
634             [ 'OptRequired', qq{missing required option "--$name"} ];
635             }
636             }
637              
638             # Sub command check
639 11 100 100     30 if ( @$source and my @subcmds = @{ $cmd->subcmds } ) {
  8         24  
640 7         13 my $result = $source->[0];
641 7 100       16 if ( $cmd->abbrev ) {
642 2         713 require Text::Abbrev;
643             my %abbrev =
644 2         49 Text::Abbrev::abbrev( map { $_->name } @subcmds );
  2         9  
645 2   33     61 $result = $abbrev{$result} // $result;
646             }
647              
648 7 100       17 if ( exists $cmd->_subcmds->{$result} ) {
649 4         6 shift @$source;
650 4         10 $cmd = $cmd->_subcmds->{$result};
651 4         7 push( @opts, @{ $cmd->opts } );
  4         11  
652              
653             # Ignoring any remaining arguments
654 4         6 @args = @{ $cmd->args };
  4         9  
655              
656 4         16 next OPTARGS;
657             }
658             }
659              
660 7         19 while ( my $try = shift @args ) {
661 7         8 my $result;
662 7         30 my $name = $try->name;
663              
664 7 100       47 if (@$source) {
    50          
665 4 50 0     30 if (
      33        
      33        
666             # $try->isa ne 'SubCmd'
667             # and (
668             ( $source->[0] =~ m/^--\S/ )
669             or (
670             $source->[0] =~ m/^-\S/
671             and !(
672             $source->[0] =~ m/^-\d/
673             and ( $try->isa ne 'Num'
674             or $try->isa ne 'Int' )
675             )
676             )
677             )
678              
679             # )
680             {
681 0         0 my $o = shift @$source;
682 0   0     0 $reason //= [ 'OptUnknown', qq{unknown option "$o"} ];
683 0         0 last OPTARGS;
684             }
685              
686 4 50       14 if ( $try->greedy ) {
    50          
    50          
687              
688             # Interesting feature or not? "GREEDY... LATER"
689             # my @later;
690             # if ( @args and @$source > @args ) {
691             # push( @later, pop @$source ) for @args;
692             # }
693             # Should also possibly check early for post-greedy arg,
694             # except they might be wanted for display
695             # purposes
696              
697 0 0 0     0 if ( $try->isa eq 'ArrayRef' or $try->isa eq 'SubCmd' )
    0          
698             {
699 0         0 $result = [@$source];
700             }
701             elsif ( $try->isa eq 'HashRef' ) {
702             $result = {
703 0         0 map { split /=/, $_ }
  0         0  
704             split /,/, @$source
705             };
706             }
707             else {
708 0         0 $result = "@$source";
709             }
710              
711 0         0 $source = [];
712              
713             # $source = \@later;
714             }
715             elsif ( $try->isa eq 'ArrayRef' ) {
716 0         0 $result = [ shift @$source ];
717             }
718             elsif ( $try->isa eq 'HashRef' ) {
719             $result =
720 0         0 { map { split /=/, $_ } split /,/, shift @$source };
  0         0  
721             }
722             else {
723 4         9 $result = shift @$source;
724             }
725              
726             # TODO: type check using Param::Utils?
727             }
728             elsif ( exists $source_hash->{$name} ) {
729 0         0 $result = delete $source_hash->{$name};
730             }
731              
732 7 100 66     33 if ( defined( $result //= $try->default ) ) {
    100          
733             $reason //= [
734             'SubCmdUnknown',
735             "unknown $name: "
736             . (
737             ( 'ARRAY' eq ref $result ) ? $result->[0]
738             : ( 'HASH' eq ref $result ) ? (
739             join ',',
740 4 50 50     11 map { "$_=$result->{$_}" } keys %$result
  0 50 100     0  
    100          
741             )
742             : $result
743             )
744             ]
745             if $try->isa eq 'SubCmd' and not $try->fallthru;
746              
747 4 50       21 if ( 'CODE' eq ref $result ) {
748 0         0 tie $optargs->{$name}, 'OptArgs2::CODEREF', $optargs,
749             $name,
750             $result;
751             }
752             else {
753 4         41 $optargs->{$name} = $result;
754             }
755             }
756             elsif ( $try->required ) {
757 2   50     23 $reason //= ['ArgRequired'];
758             }
759             }
760             }
761              
762 11 50       57 if (@$source) {
    50          
763 0   0     0 $reason //= [
764             'UnexpectedOptArg',
765             "unexpected option(s) or argument(s): @$source"
766             ];
767             }
768             elsif ( my @unexpected = keys %$source_hash ) {
769 0   0     0 $reason //= [
770             'UnexpectedHashOptArg',
771             "unexpected HASH option(s) or argument(s): @unexpected"
772             ];
773             }
774              
775 11         47 $cmd->_values($optargs);
776              
777 11         22 map { $_->[0]->( $cmd, $optargs->{ $_->[1] } ) } @trigger;
  0         0  
778              
779 11 100       54 OptArgs2->throw_usage( $reason->[0],
780             $cmd->usage_string( OptArgs2::STYLE_USAGE, $reason->[1] ) )
781             if $reason;
782              
783 7         20 return ( $cmd->class, $optargs, ( $cmd->class . '.pm' ) =~ s!::!/!gr );
784             }
785              
786             sub _usage_tree {
787 0     0   0 my $self = shift;
788 0   0     0 my $depth = shift || 0;
789              
790             return [
791             $depth, $self->usage_string(OptArgs2::STYLE_HELPSUMMARY),
792             $self->comment
793             ],
794 0         0 map { $_->_usage_tree( $depth + 1 ) }
795 0         0 sort { $a->name cmp $b->name } @{ $self->subcmds };
  0         0  
  0         0  
796             }
797              
798             sub usage_string {
799 8     8 0 13 my $self = shift;
800 8   50     19 my $style = shift || OptArgs2::STYLE_USAGE;
801 8   100     25 my $error = shift // '';
802 8         13 my $usage = '';
803              
804 8 50       12 if ( $style eq OptArgs2::STYLE_HELPTREE ) {
805 0         0 my ( @w1, @w2 );
806             my @items = map {
807 0         0 $_->[0] = ' ' x ( $_->[0] * 3 );
  0         0  
808 0         0 push @w1, length( $_->[1] ) + length( $_->[0] );
809 0         0 push @w2, length $_->[2];
810 0         0 $_
811             } $self->_usage_tree;
812 0         0 my ( $w1, $w2 ) = ( max(@w1), max(@w2) );
813              
814 0         0 my $paged = OptArgs2::rows() < scalar @items;
815 0         0 my $cols = OptArgs2::cols();
816 0         0 my $usage = '';
817 0         0 my $spacew = 3;
818 0         0 my $space = ' ' x $spacew;
819              
820 0         0 foreach my $i ( 0 .. $#items ) {
821 0         0 my $overlap = $w1 + $spacew + $w2[$i] - $cols;
822 0 0 0     0 if ( $overlap > 0 and not $paged ) {
823 0         0 $items[$i]->[2] =
824             sprintf '%-.' . ( $w2[$i] - $overlap - 3 ) . 's%s',
825             $items[$i]->[2], '.' x 3;
826             }
827 0         0 $usage .= sprintf "%-${w1}s${space}%-s\n",
828             $items[$i]->[0] . $items[$i]->[1],
829             $items[$i]->[2];
830             }
831 0         0 return $usage;
832             }
833              
834 8         24 my @parents = $self->parents;
835 8         10 my @args = @{ $self->args };
  8         18  
836             my @opts =
837 8         24 sort { $a->name cmp $b->name } map { @{ $_->opts } } @parents,
  0         0  
  12         18  
  12         23  
838             $self;
839              
840 8         19 my $optargs = $self->_values;
841              
842             # Summary line
843 8 50 66     24 $usage .= join( ' ', map { $_->name } @parents ) . ' '
  0         0  
844             if @parents and $style ne OptArgs2::STYLE_HELPSUMMARY;
845 8         19 $usage .= $self->name;
846              
847 8         20 my ( $red, $grey, $reset ) = ( '', '', '' );
848 8 50       24 if ( $self->show_color ) {
849 0         0 $red = "\e[0;31m";
850 0         0 $grey = "\e[1;30m";
851 0         0 $reset = "\e[0m";
852              
853             # $red = "\e[0;31m";
854             # $yellow = "\e[0;33m";
855             }
856              
857 8 100       32 $error = $red . 'error:' . $reset . ' ' . $error . "\n\n"
858             if length $error;
859              
860 8         16 foreach my $arg (@args) {
861 4         7 $usage .= ' ';
862 4 100       17 $usage .= '[' unless $arg->required;
863 4         15 $usage .= uc $arg->name;
864 4 50       12 $usage .= '...' if $arg->greedy;
865 4 100       11 $usage .= ']' unless $arg->required;
866             }
867              
868 8 100       38 return $usage if $style eq OptArgs2::STYLE_HELPSUMMARY;
869              
870 4 50       7 $usage .= ' [OPTIONS...]' if @opts;
871 4         13 $usage .= "\n";
872              
873             # Synopsis
874 4 50 33     12 $usage .= "\n Synopsis:\n " . $self->comment . "\n"
875             if $style eq OptArgs2::STYLE_HELP and length $self->comment;
876              
877             # Build arguments
878 4         9 my @sargs;
879             my @uargs;
880 4         0 my $have_subcmd;
881              
882 4 50       8 if (@args) {
883 4         5 my $i = 0;
884 4         8 ARG: foreach my $arg (@args) {
885 4 50       8 if ( $arg->isa eq 'SubCmd' ) {
886             my ( $n, undef, undef, $c ) =
887             $arg->name_alias_type_comment(
888             $arg->show_default
889 4 50 0     20 ? eval { $optargs->{ $arg->name } // undef }
  0         0  
890             : ()
891             );
892 4         18 push( @sargs, [ ' ' . ucfirst($n) . ':', $c ] );
893             my @sorted_subs =
894 4         17 map { $_->[1] }
895 0         0 sort { $a->[0] cmp $b->[0] }
896 4         9 map { [ $_->name, $_ ] }
897 4 50       18 grep { $style eq OptArgs2::STYLE_HELP or !$_->hidden }
898 4         7 @{ $arg->cmd->subcmds };
  4         10  
899              
900 4         11 foreach my $subcmd (@sorted_subs) {
901 4         53 push(
902             @sargs,
903             [
904             ' '
905             . $subcmd->usage_string(
906             OptArgs2::STYLE_HELPSUMMARY),
907             $subcmd->comment
908             ]
909             );
910             }
911              
912 4         6 $have_subcmd++;
913 4         10 last ARG;
914             }
915             else {
916 0 0       0 push( @uargs, [ ' Arguments:', '', '', '' ] ) if !$i;
917             my ( $n, $a, $t, $c ) = $arg->name_alias_type_comment(
918             $arg->show_default
919 0 0 0     0 ? eval { $optargs->{ $arg->name } // undef }
  0         0  
920             : ()
921             );
922 0         0 push( @uargs, [ ' ' . uc($n), $a, $t, $c ] );
923             }
924 0         0 $i++;
925             }
926             }
927              
928             # Build options
929 4         7 my @uopts;
930 4 50       9 if (@opts) {
931 4         9 push( @uopts, [ " Options:", '', '', '' ] );
932 4         8 foreach my $opt (@opts) {
933 4 50 33     15 next if $style ne OptArgs2::STYLE_HELP and $opt->hidden;
934             my ( $n, $a, $t, $c ) = $opt->name_alias_type_comment(
935             $opt->show_default
936 4 50 0     16 ? eval { $optargs->{ $opt->name } // undef }
  0         0  
937             : ()
938             );
939 4         23 push( @uopts, [ ' ' . $n, $a, $t, $c ] );
940             }
941             }
942              
943             # Width calculation for args and opts combined
944 4         11 my $w1 = max( 0, map { length $_->[0] } @uargs, @uopts );
  8         22  
945 4         15 my $w2 = max( 0, map { length $_->[1] } @uargs, @uopts );
  8         15  
946 4         7 my $w3 = max( 0, map { length $_->[2] } @uargs, @uopts );
  8         20  
947 4         13 my $w4 = max( 0, map { length $_->[0] } @sargs );
  8         16  
948 4         11 my $w5 = max( $w1 + $w2 + $w3, $w4 );
949              
950 4         9 my $format1 = "%-${w5}s %s\n";
951 4         13 my $format2 = "%-${w1}s %-${w2}s %-${w3}s";
952              
953             # Output Arguments
954 4 50       8 if (@sargs) {
955 4         5 $usage .= "\n";
956 4         7 foreach my $row (@sargs) {
957 8         74 $usage .= sprintf( $format1, @$row ) =~
958             s/^(\s+\w+\s)(.*?)(\s\s)/$1$grey$2$reset$3/r;
959             }
960             }
961              
962 4 50       11 if (@uargs) {
963 0         0 $usage .= "\n";
964 0         0 foreach my $row (@uargs) {
965 0         0 my $l = pop @$row;
966 0         0 $usage .= sprintf( $format1, sprintf( $format2, @$row ), $l );
967             }
968             }
969              
970             # Output Options
971 4 50       10 if (@uopts) {
972 4         5 $usage .= "\n";
973 4         5 foreach my $row (@uopts) {
974 8         15 my $l = pop @$row;
975 8         23 $usage .= sprintf( $format1, sprintf( $format2, @$row ), $l );
976             }
977             }
978              
979 4         39 return $error . 'usage: ' . $usage . "\n";
980             }
981              
982             }
983              
984             package OptArgs2::Cmd {
985             use OptArgs2::Cmd_CI
986             isa => 'OptArgs2::CmdBase',
987             has => {
988             name => {
989             default => sub {
990 4         11 my $x = $_[0]->class;
991              
992             # once legacy code goes move this into optargs()
993 4 50       18 if ( $x eq 'main' ) {
994 0         0 require File::Basename;
995 0         0 File::Basename::basename($0),;
996             }
997             else {
998 4         10 $x =~ s/.*://;
999 4         6 $x =~ s/_/-/g;
1000 4         13 $x;
1001             }
1002             },
1003             },
1004 3         28 no_help => { default => 0 },
1005 3     3   1092 };
  3         8  
1006              
1007             our @CARP_NOT = @OptArgs2::CARP_NOT;
1008              
1009             sub BUILD {
1010 6     6 0 10 my $self = shift;
1011              
1012 6 50 33     24 $self->add_opt(
1013             isa => OptArgs2::STYLE_HELP,
1014             show_default => 0,
1015             )
1016             unless $self->no_help
1017             or 'CODE' eq ref $self->optargs; # legacy interface
1018             }
1019             }
1020              
1021             package OptArgs2::SubCmd {
1022             use OptArgs2::SubCmd_CI
1023             isa => 'OptArgs2::CmdBase',
1024             has => {
1025             name => { # once legacy code goes move this into CmdBase
1026             init_arg => undef,
1027             default => sub {
1028 5         22 my $x = $_[0]->class;
1029 5         26 $x =~ s/.*://;
1030 5         9 $x =~ s/_/-/g;
1031 5         26 $x;
1032             },
1033             },
1034 3         29 parent => { required => 1, },
1035 3     3   1028 };
  3         6  
1036              
1037             our @CARP_NOT = @OptArgs2::CARP_NOT;
1038             }
1039              
1040             1;
1041              
1042             __END__