File Coverage

blib/lib/OptArgs2/CmdBase.pm
Criterion Covered Total %
statement 275 348 79.0
branch 104 168 61.9
condition 53 115 46.0
subroutine 38 43 88.3
pod 0 19 0.0
total 470 693 67.8


line stmt bran cond sub pod time code
1             package OptArgs2::CmdBase;
2 6     6   3538 use strict;
  6         18  
  6         231  
3 6     6   33 use warnings;
  6         12  
  6         550  
4              
5             use overload
6 36     36   89 bool => sub { 1 },
7 0     0   0 '""' => sub { shift->class },
8 6     6   1557 fallback => 1;
  6         4873  
  6         70  
9              
10 6     6   5234 use Getopt::Long qw/GetOptionsFromArray/;
  6         81541  
  6         28  
11 6     6   1362 use List::Util qw/max/;
  6         11  
  6         628  
12 6     6   3054 use OptArgs2::Arg;
  6         24  
  6         255  
13 6     6   3012 use OptArgs2::Opt;
  6         18  
  6         241  
14 6     6   3517 use OptArgs2::SubCmd;
  6         17  
  6         9752  
15              
16             ### START Class::Inline ### v0.0.1 Wed Dec 3 10:44:51 2025
17             require Scalar::Util;
18             require Carp;
19             our ( @_CLASS, $_FIELDS, %_NEW );
20              
21             sub _NEW {
22 16     16   30 CORE::state $fix_FIELDS = do {
23 6 50       38 $_FIELDS = { @_CLASS > 1 ? @_CLASS : %{ $_CLASS[0] } };
  0         0  
24 6 50       44 $_FIELDS = $_FIELDS->{'FIELDS'} if exists $_FIELDS->{'FIELDS'};
25             };
26 16 100       34 if ( my @missing = grep { not exists $_[0]->{$_} } 'class', 'comment' ) {
  32         101  
27 1         235 Carp::croak( 'OptArgs2::CmdBase required initial argument(s): '
28             . join( ', ', @missing ) );
29             }
30             Scalar::Util::weaken( $_[0]{'parent'} )
31 15 100 66     80 if exists $_[0]{'parent'} && ref $_[0]{'parent'};
32 15         31 map { delete $_[1]->{$_} } '_subcmds', '_values', 'abbrev', 'args',
  195         345  
33             'class', 'comment', 'hidden', 'optargs', 'opts', 'parent', 'show_color',
34             'show_default', 'subcmds';
35             }
36              
37             sub __RO {
38 0     0   0 my ( undef, undef, undef, $sub ) = caller(1);
39 0         0 Carp::confess("attribute $sub is read-only");
40             }
41              
42             sub _subcmds {
43 21 50   21   47 __RO() if @_ > 1;
44 21   66     71 $_[0]{'_subcmds'} //= $_FIELDS->{'_subcmds'}->{'default'}->( $_[0] );
45             }
46              
47             sub _values {
48 23 100   23   82 if ( @_ > 1 ) { $_[0]{'_values'} = $_[1] }
  14         34  
49 23   100     70 $_[0]{'_values'} // undef;
50             }
51              
52             sub abbrev {
53 12 50   12 0 25 if ( @_ > 1 ) { $_[0]{'abbrev'} = $_[1] }
  0         0  
54 12   100     72 $_[0]{'abbrev'} // undef;
55             }
56              
57             sub args {
58 38 50   38 0 84 __RO() if @_ > 1;
59 38   66     162 $_[0]{'args'} //= $_FIELDS->{'args'}->{'default'}->( $_[0] );
60             }
61 28 50 50 28 0 59 sub class { __RO() if @_ > 1; $_[0]{'class'} // undef }
  28         145  
62 4 50 50 4 0 26 sub comment { __RO() if @_ > 1; $_[0]{'comment'} // undef }
  4         18  
63 4 50 50 4 0 10 sub hidden { __RO() if @_ > 1; $_[0]{'hidden'} // undef }
  4         23  
64              
65             sub optargs {
66 51 50   51 0 119 if ( @_ > 1 ) { $_[0]{'optargs'} = $_[1] }
  0         0  
67 51   33     361 $_[0]{'optargs'} //= $_FIELDS->{'optargs'}->{'default'}->( $_[0] );
68             }
69              
70             sub opts {
71 45 50   45 0 127 __RO() if @_ > 1;
72 45   66     211 $_[0]{'opts'} //= $_FIELDS->{'opts'}->{'default'}->( $_[0] );
73             }
74 23 50 50 23 0 60 sub parent { __RO() if @_ > 1; $_[0]{'parent'} // undef }
  23         130  
75              
76             sub show_color {
77 9 50   9 0 18 __RO() if @_ > 1;
78 9   33     46 $_[0]{'show_color'} //= $_FIELDS->{'show_color'}->{'default'}->( $_[0] );
79             }
80              
81             sub show_default {
82 26 50   26 0 87 __RO() if @_ > 1;
83 26   66     274 $_[0]{'show_default'} //= $_FIELDS->{'show_default'}->{'default'};
84             }
85              
86             sub subcmds {
87 11 50   11 0 27 __RO() if @_ > 1;
88 11   66     59 $_[0]{'subcmds'} //= $_FIELDS->{'subcmds'}->{'default'}->( $_[0] );
89             }
90             @_CLASS = grep 1, ### END Class::Inline ###
91             abstract => 1,
92             FIELDS => {
93             abbrev => { is => 'rw', },
94             args => { default => sub { [] }, },
95             class => { required => 1, },
96             comment => { required => 1, },
97             hidden => {},
98             optargs => {
99             is => 'rw',
100             default => sub { [] }
101             },
102             opts => { default => sub { [] }, },
103             parent => { weaken => 1, },
104             _subcmds => {
105             default => sub { {} }
106             },
107             show_default => { default => 0, },
108             show_color => { default => sub { -t STDERR }, },
109             subcmds => { default => sub { [] }, },
110             _values => { is => 'rw' },
111             },
112             ;
113              
114             our @CARP_NOT = @OptArgs2::CARP_NOT;
115              
116             sub BUILD {
117 15     15 0 30 my $self = shift;
118              
119             # legacy interface
120 15 50       87 if ( 'CODE' eq ref $self->optargs ) {
121 0         0 local $OptArgs2::CURRENT = $self;
122 0         0 $self->optargs->();
123 0         0 return;
124             }
125              
126 15         29 my %aliases;
127 15         28 while ( my ( $name, $args ) = splice @{ $self->optargs }, 0, 2 ) {
  27         62  
128 13 100       81 if ( $args->{isa} =~ s/^--// ) {
129 6 100 100     47 if ( length( my $alias = $args->{alias} //= undef ) ) {
130             OptArgs2::croak( 'DuplicateAlias',
131             "duplicate '-$alias' alias by --$name" )
132 2 100       14 if $aliases{$alias}++;
133             }
134              
135             $self->add_opt(
136 5         32 name => $name,
137             %$args,
138             );
139             }
140             else {
141 7         55 $self->add_arg(
142             name => $name,
143             %$args,
144             );
145             }
146             }
147             }
148              
149             my %usage_why = (
150             ArgRequired => undef,
151             GetOptError => undef,
152             Help => undef,
153             HelpSummary => undef,
154             HelpTree => undef,
155             OptRequired => undef,
156             OptUnknown => undef,
157             SubCmdRequired => undef,
158             SubCmdUnknown => undef,
159             UnexpectedOptArg => undef,
160             );
161              
162             sub throw {
163 5     5 0 8 my $self = shift;
164 5   33     17 my $type = shift // OptArgs2::croak( 'Usage', 'throw($TYPE,$why,$info)' );
165 5   33     13 my $why = shift // $type;
166 5   100     15 my $info = shift // '';
167 5         12 my $pkg = 'OptArgs2::Usage::' . $why;
168              
169             OptArgs2::croak( 'Usage', "unknown usage why: $why" )
170 5 50       18 unless exists $usage_why{$why};
171              
172 6     6   56 no strict 'refs';
  6         11  
  6         26441  
173 5         15 *{ $pkg . '::ISA' } = ['OptArgs2::Status'];
  5         93  
174              
175 5         29 my $usage = $self->usage_string( $type, $info );
176 5         46 OptArgs2::die_paged( bless \$usage, $pkg );
177             }
178              
179             sub add_arg {
180 7     7 0 11 my $self = shift;
181 7         36 my $arg = OptArgs2::Arg->new(
182             cmd => $self,
183             show_default => $self->show_default,
184             @_,
185             );
186              
187 7         18 push( @{ $self->args }, $arg );
  7         60  
188 7         23 $arg;
189             }
190              
191             sub add_cmd {
192 5     5 0 8 my $self = shift;
193 5         18 my $subcmd = OptArgs2::SubCmd->new(
194             abbrev => $self->abbrev,
195             show_default => $self->show_default,
196             @_,
197             parent => $self,
198             );
199              
200             OptArgs2::croak( 'CmdExists', 'cmd exists' )
201 5 50       20 if exists $self->_subcmds->{ $subcmd->name };
202              
203 5         17 $self->_subcmds->{ $subcmd->name } = $subcmd;
204 5         14 push( @{ $self->subcmds }, $subcmd );
  5         15  
205              
206 5         16 return $subcmd;
207             }
208              
209             sub add_opt {
210 14     14 0 25 my $self = shift;
211 14         51 my $opt = OptArgs2::Opt->new_from(
212             show_default => $self->show_default,
213             @_,
214             );
215              
216 14         28 push( @{ $self->opts }, $opt );
  14         67  
217 14         74 $opt;
218             }
219              
220             sub parents {
221 27     27 0 53 my $self = shift;
222 27 100       78 return unless $self->parent;
223 4         8 return ( $self->parent->parents, $self->parent );
224             }
225              
226             package OptArgs2::CODEREF {
227             our @CARP_NOT = @OptArgs2::CARP_NOT;
228              
229             sub TIESCALAR {
230 2     2   4 my $class = shift;
231 2 50       5 ( 3 == @_ )
232             or OptArgs2::croak( 'Usage', 'args: optargs,name,sub' );
233 2         13 return bless [@_], $class;
234             }
235              
236             sub FETCH {
237 2     2   25 my $self = shift;
238 2         7 my ( $optargs, $name, $sub ) = @$self;
239 2         4 untie $optargs->{$name};
240 2         4 $optargs->{$name} = $sub->($optargs);
241             }
242              
243             }
244              
245             sub parse {
246 14     14 0 24 my $self = shift;
247 14         82 my $source = \@_;
248              
249             map {
250 14 50       41 OptArgs2::croak( 'UndefOptArg', 'optargs argument undefined!' )
  13         59  
251             if !defined $_
252             } @$source;
253              
254 14         50 my $source_hash = { map { %$_ } grep { ref $_ eq 'HASH' } @$source };
  0         0  
  13         40  
255 14         29 $source = [ grep { ref $_ ne 'HASH' } @$source ];
  13         38  
256              
257 14         64 Getopt::Long::Configure(qw/pass_through no_auto_abbrev no_ignore_case/);
258              
259 14         946 my $reason;
260 14         59 my $optargs = {};
261 14         67 my @trigger;
262              
263 14         26 my $cmd = $self;
264              
265             # Start with the parents options
266 14         120 my @opts = map { @{ $_->opts } } $cmd->parents, $cmd;
  14         24  
  14         32  
267 14         24 my @args = @{ $cmd->args };
  14         62  
268              
269 14   66     53 OPTARGS: while ( @opts or @args ) {
270 14         54 while ( my $opt = shift @opts ) {
271 18         32 my $result;
272 18         91 my $name = $opt->name;
273              
274 18 50       101 if ( exists $source_hash->{$name} ) {
275 0         0 $result = delete $source_hash->{$name};
276             }
277             else {
278 18         29 my @errors;
279 18     1   129 local $SIG{__WARN__} = sub { push @errors, $_[0] };
  1         876  
280              
281 18         52 my $ok = eval {
282 18         102 GetOptionsFromArray( $source, $opt->getopt => \$result );
283             };
284 18 100       4574 if ( !$ok ) {
285 1 50       10 my $error =
    50          
286             length $@ ? $@
287             : @errors ? join( "\n", @errors )
288             : 'unknown';
289              
290 1   50     14 $reason //= [ GetOptError => $error ];
291             }
292             }
293              
294 18 50 66     85 if ( defined($result) and my $t = $opt->trigger ) {
295 0         0 push @trigger, [ $t, $name ];
296             }
297              
298 18 100 100     98 if ( defined( $result //= $opt->default ) ) {
    50          
299              
300 2 50       13 if ( 'CODE' eq ref $result ) {
    100          
301 0         0 tie $optargs->{$name}, 'OptArgs2::CODEREF', $optargs,
302             $name,
303             $result;
304             }
305             elsif ( $opt->isa eq 'Input' ) {
306 1         5 my $enc = $opt->encoding;
307             tie $optargs->{$name}, 'OptArgs2::CODEREF', $optargs,
308             $name, $result eq '-'
309             ? sub {
310 0     0   0 binmode STDIN, $enc;
311 0         0 local $/;
312 0         0 ;
313             }
314             : sub {
315 1 50   1   42 open my $fh, '<', $result
316             or die sprintf "open(%s): %s\n", $result, $!;
317 1         9 binmode $fh, $enc;
318 1         48 local $/;
319 1         30 <$fh>;
320             }
321 1 50       10 }
322             else {
323 1         6 $optargs->{$name} = $result;
324             }
325             }
326             elsif ( $opt->required ) {
327 0         0 $name =~ s/_/-/g;
328 0   0     0 $reason //=
329             [ 'OptRequired', qq{missing required option "--$name"} ];
330             }
331             }
332              
333 14         46 while ( my $arg = shift @args ) {
334 12         18 my $result;
335 12         41 my $name = $arg->name;
336 12         42 my $isa = $arg->isa;
337              
338 12 100       34 if (@$source) {
    50          
339              
340             # TODO: do this check for every element in
341             # @$source, which means moving this down
342             # somewhere...
343 9 50 0     57 if (
      33        
      33        
344             ( $source->[0] =~ m/^--\S/ )
345             or (
346             $source->[0] =~ m/^-\S/
347             and !(
348             $source->[0] =~ m/^-\d/ and ( $arg->isa ne 'Num'
349             or $arg->isa ne 'Int' )
350             )
351             )
352             )
353             {
354 0         0 my $o = shift @$source;
355 0   0     0 $reason //= [ 'OptUnknown', qq{unknown option "$o"} ];
356 0         0 last OPTARGS;
357             }
358              
359             # if ( $arg->greedy ) {
360             #
361             # # Interesting feature or not? "GREEDY... LATER"
362             # # my @later;
363             # # if ( @args and @$source > @args ) {
364             # # push( @later, pop @$source ) for @args;
365             # # }
366             # # Should also possibly check early for post-greedy arg,
367             # # except they might be wanted for display
368             # # purposes
369             #
370             # if ( $arg->isa eq 'ArrayRef' )
371             # {
372             # $result = [@$source];
373             # }
374             # elsif ( $arg->isa eq 'HashRef' ) {
375             # $result = {
376             # map { split /=/, $_ }
377             # split /,/, @$source
378             # };
379             # }
380             # else {
381             # $result = "@$source";
382             # }
383             #
384             # $source = [];
385             #
386             # # $source = \@later;
387             # }
388 9 100       30 if ( $isa eq 'SubCmd' ) {
    50          
    50          
389 7         15 my $test = $source->[0];
390              
391 7 100 66     37 if ( $cmd->abbrev
392 2         7 and my @subcmds = @{ $cmd->subcmds } )
393             {
394 2         1052 require Text::Abbrev;
395             my %abbrev =
396 2         927 Text::Abbrev::abbrev( map { $_->name } @subcmds );
  2         9  
397 2   33     104 $test = $abbrev{$test} // $test;
398             }
399              
400 7 100       18 if ( exists $cmd->_subcmds->{$test} ) {
401 4         9 shift @$source;
402 4         56 $cmd = $cmd->_subcmds->{$test};
403 4         9 push( @opts, @{ $cmd->opts } );
  4         107  
404              
405             # Replace rest of current cmd arguments with new
406 4         7 @args = @{ $cmd->args };
  4         32  
407 4 50 33     9 if ( @{ $cmd->args }
  4         8  
408             && $cmd->args->[0]->isa ne 'SubCmd' )
409             {
410             # Add a fake Arg to the list to check
411             # for subcommands.
412 0         0 unshift @args,
413             OptArgs2::Arg->new(
414             isa => 'SubCmd',
415             name => '__internal',
416             comment => '__internal',
417             );
418             }
419 4         28 next OPTARGS;
420             }
421 3 50       9 next OPTARGS if $arg->name eq '__internal';
422              
423 3         7 $result = shift @$source;
424 3 100       10 if ( $arg->fallthru ) {
425 1         4 $optargs->{$name} = $result;
426             }
427             else {
428 2   50     17 $reason //=
429             [ 'SubCmdUnknown', "unknown $name: $result" ];
430             }
431             }
432             elsif ( $isa eq 'ArrayRef' ) {
433 0 0       0 $result = [ $arg->greedy ? @$source : shift @$source ];
434             }
435             elsif ( $isa eq 'HashRef' ) {
436             $result = {
437 0 0       0 map { split /=/, $_ } split /,/,
  0         0  
438             $arg->greedy ? @$source : shift @$source
439             };
440             }
441             else {
442 2 50       8 $result = $arg->greedy ? "@$source" : shift @$source;
443             }
444              
445 5 50       15 $source = [] if $arg->greedy;
446              
447             }
448             elsif ( exists $source_hash->{$name} ) {
449 0         0 $result = delete $source_hash->{$name};
450             }
451              
452             # TODO: type check using Param::Utils?
453              
454 8 100 66     33 if ( defined( $result //= $arg->default ) ) {
    100          
455 5 50       41 if ( 'CODE' eq ref $result ) {
    100          
456 0         0 tie $optargs->{$name}, 'OptArgs2::CODEREF', $optargs,
457             $name,
458             $result;
459             }
460             elsif ( $isa eq 'Input' ) {
461 1         4 my $enc = $arg->encoding;
462             tie $optargs->{$name}, 'OptArgs2::CODEREF', $optargs,
463             $name, $result eq '-'
464             ? sub {
465 0     0   0 binmode STDIN, $enc;
466 0         0 local $/;
467 0         0 ;
468             }
469             : sub {
470 1 50   1   56 open my $fh, '<', $result
471             or die sprintf "open(%s): %s\n", $result, $!;
472 1     1   33 binmode $fh, $enc;
  1         704  
  1         13  
  1         4  
473 1         891 local $/;
474 1         41 <$fh>;
475             }
476 1 50       11 }
477             else {
478 4         31 $optargs->{$name} = $result;
479             }
480             }
481             elsif ( $arg->required ) {
482 2   50     31 $reason //= ['ArgRequired'];
483             }
484             }
485             }
486              
487 14 50       92 if (@$source) {
    50          
488 0   0     0 $reason //= [
489             'UnexpectedOptArg', "unexpected option(s) or argument(s): @$source"
490             ];
491             }
492             elsif ( my @unexpected = keys %$source_hash ) {
493 0   0     0 $reason //= [
494             'UnexpectedHashOptArg',
495             "unexpected HASH option(s) or argument(s): @unexpected"
496             ];
497             }
498              
499 14         67 $cmd->_values($optargs);
500              
501 14         20 map { $_->[0]->( $cmd, $optargs->{ $_->[1] } ) } @trigger;
  0         0  
502              
503 14 100       69 $cmd->throw( OptArgs2::USAGE_USAGE(), @$reason )
504             if $reason;
505              
506 9         52 return ( $cmd->class, $optargs, ( $cmd->class . '.pm' ) =~ s!::!/!gr );
507             }
508              
509             sub _usage_tree {
510 0     0   0 my $self = shift;
511 0   0     0 my $depth = shift || 0;
512              
513             return [
514             $depth, $self->usage_string( OptArgs2::USAGE_HELPSUMMARY() ),
515             $self->comment
516             ],
517 0         0 map { $_->_usage_tree( $depth + 1 ) }
518 0         0 sort { $a->name cmp $b->name } @{ $self->subcmds };
  0         0  
  0         0  
519             }
520              
521             sub usage_string {
522 9     9 0 17 my $self = shift;
523 9   33     27 my $style = shift || OptArgs2::USAGE_USAGE();
524 9   100     40 my $error = shift // '';
525 9         14 my $usage = '';
526              
527 9 50       27 if ( $style eq OptArgs2::USAGE_HELPTREE() ) {
528 0         0 my ( @w1, @w2 );
529             my @items = map {
530 0         0 $_->[0] = ' ' x ( $_->[0] * 3 );
  0         0  
531 0         0 push @w1, length( $_->[1] ) + length( $_->[0] );
532 0         0 push @w2, length $_->[2];
533 0         0 $_
534             } $self->_usage_tree;
535 0         0 my ( $w1, $w2 ) = ( max(@w1), max(@w2) );
536              
537 0         0 my $paged = OptArgs2::rows() < scalar @items;
538 0         0 my $cols = OptArgs2::cols();
539 0         0 my $usage = '';
540 0         0 my $spacew = 3;
541 0         0 my $space = ' ' x $spacew;
542              
543 0         0 foreach my $i ( 0 .. $#items ) {
544 0         0 my $overlap = $w1 + $spacew + $w2[$i] - $cols;
545 0 0 0     0 if ( $overlap > 0 and not $paged ) {
546 0         0 $items[$i]->[2] =
547             sprintf '%-.' . ( $w2[$i] - $overlap - 3 ) . 's%s',
548             $items[$i]->[2], '.' x 3;
549             }
550 0         0 $usage .= sprintf "%-${w1}s${space}%-s\n",
551             $items[$i]->[0] . $items[$i]->[1],
552             $items[$i]->[2];
553             }
554 0         0 return $usage;
555             }
556              
557 9         25 my @parents = $self->parents;
558 9         13 my @args = @{ $self->args };
  9         22  
559             my @opts =
560 9         23 sort { $a->name cmp $b->name } map { @{ $_->opts } } @parents,
  1         4  
  13         16  
  13         25  
561             $self;
562              
563 9         19 my $optargs = $self->_values;
564              
565             # Summary line
566 9 50 66     32 $usage .= join( ' ', map { $_->name } @parents ) . ' '
  0         0  
567             if @parents and $style ne OptArgs2::USAGE_HELPSUMMARY();
568 9         28 $usage .= $self->name;
569              
570 9         26 my ( $red, $grey, $reset ) = ( '', '', '' );
571 9 50       29 if ( $self->show_color ) {
572 0         0 $red = "\e[0;31m";
573 0         0 $grey = "\e[1;30m";
574 0         0 $reset = "\e[0m";
575              
576             # $red = "\e[0;31m";
577             # $yellow = "\e[0;33m";
578             }
579              
580 9 100       28 $error = $red . 'error:' . $reset . ' ' . $error . "\n\n"
581             if length $error;
582              
583 9         27 foreach my $arg (@args) {
584 4         5 $usage .= ' ';
585 4 100       12 $usage .= '[' unless $arg->required;
586 4         9 $usage .= uc $arg->name;
587 4 50       10 $usage .= '...' if $arg->greedy;
588 4 100       9 $usage .= ']' unless $arg->required;
589             }
590              
591 9 100       56 return $usage if $style eq OptArgs2::USAGE_HELPSUMMARY();
592              
593 5 50       12 $usage .= ' [OPTIONS...]' if @opts;
594 5         12 $usage .= "\n";
595              
596             # Synopsis
597 5 50 33     19 $usage .= "\n Synopsis:\n " . $self->comment . "\n"
598             if $style eq OptArgs2::USAGE_HELP()
599             and length $self->comment;
600              
601             # Build arguments
602 5         13 my @sargs;
603             my @uargs;
604 5         0 my $have_subcmd;
605              
606 5 100       19 if (@args) {
607 4         6 my $i = 0;
608 4         6 ARG: foreach my $arg (@args) {
609 4 50       10 if ( $arg->isa eq 'SubCmd' ) {
610             my ( $n, undef, undef, $c ) = $arg->name_alias_type_comment(
611             $arg->show_default
612 4 50 0     22 ? eval { $optargs->{ $arg->name } // undef }
  0         0  
613             : ()
614             );
615 4         19 push( @sargs, [ ' ' . ucfirst($n) . ':', $c ] );
616             my @sorted_subs =
617 4         12 map { $_->[1] }
618 0         0 sort { $a->[0] cmp $b->[0] }
619 4         11 map { [ $_->name, $_ ] }
620 4 50       34 grep { $style eq OptArgs2::USAGE_HELP() or !$_->hidden }
621 4         7 @{ $arg->cmd->subcmds };
  4         10  
622              
623 4         11 foreach my $subcmd (@sorted_subs) {
624 4         64 push(
625             @sargs,
626             [
627             ' '
628             . $subcmd->usage_string(
629             OptArgs2::USAGE_HELPSUMMARY()
630             ),
631             $subcmd->comment
632             ]
633             );
634             }
635              
636 4         12 $have_subcmd++;
637 4         11 last ARG;
638             }
639             else {
640 0 0       0 push( @uargs, [ ' Arguments:', '', '', '' ] ) if !$i;
641             my ( $n, $a, $t, $c ) = $arg->name_alias_type_comment(
642             $arg->show_default
643 0 0 0     0 ? eval { $optargs->{ $arg->name } // undef }
  0         0  
644             : ()
645             );
646 0         0 push( @uargs, [ ' ' . uc($n), $a, $t, $c ] );
647             }
648 0         0 $i++;
649             }
650             }
651              
652             # Build options
653 5         9 my @uopts;
654 5 50       14 if (@opts) {
655 5         12 push( @uopts, [ " Options:", '', '', '' ] );
656 5         11 foreach my $opt (@opts) {
657 6 50 33     52 next if $style ne OptArgs2::USAGE_HELP() and $opt->hidden;
658             my ( $n, $a, $t, $c ) = $opt->name_alias_type_comment(
659             $opt->show_default
660 6 50 0     31 ? eval { $optargs->{ $opt->name } // undef }
  0         0  
661             : ()
662             );
663 6         28 push( @uopts, [ ' ' . $n, $a, $t, $c ] );
664             }
665             }
666              
667             # Width calculation for args and opts combined
668 5         17 my $w1 = max( 0, map { length $_->[0] } @uargs, @uopts );
  11         47  
669 5         10 my $w2 = max( 0, map { length $_->[1] } @uargs, @uopts );
  11         22  
670 5         10 my $w3 = max( 0, map { length $_->[2] } @uargs, @uopts );
  11         21  
671 5         11 my $w4 = max( 0, map { length $_->[0] } @sargs );
  8         21  
672 5         14 my $w5 = max( $w1 + $w2 + $w3, $w4 );
673              
674 5         15 my $format1 = "%-${w5}s %s\n";
675 5         20 my $format2 = "%-${w1}s %-${w2}s %-${w3}s";
676              
677             # Output Arguments
678 5 100       14 if (@sargs) {
679 4         7 $usage .= "\n";
680 4         8 foreach my $row (@sargs) {
681 8         88 $usage .= sprintf( $format1, @$row ) =~
682             s/^(\s+\w+\s)(.*?)(\s\s)/$1$grey$2$reset$3/r;
683             }
684             }
685              
686 5 50       17 if (@uargs) {
687 0         0 $usage .= "\n";
688 0         0 foreach my $row (@uargs) {
689 0         0 my $l = pop @$row;
690 0         0 $usage .= sprintf( $format1, sprintf( $format2, @$row ), $l );
691             }
692             }
693              
694             # Output Options
695 5 50       14 if (@uopts) {
696 5         20 $usage .= "\n";
697 5         9 foreach my $row (@uopts) {
698 11         21 my $l = pop @$row;
699 11         60 $usage .= sprintf( $format1, sprintf( $format2, @$row ), $l );
700             }
701             }
702              
703 5         33 return $error . 'usage: ' . $usage . "\n";
704             }
705              
706             1;
707              
708             __END__