File Coverage

blib/lib/OptArgs2.pm
Criterion Covered Total %
statement 316 437 72.3
branch 91 182 50.0
condition 47 133 35.3
subroutine 38 48 79.1
pod 7 22 31.8
total 499 822 60.7


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