File Coverage

blib/lib/Getopt/Long/More.pm
Criterion Covered Total %
statement 95 227 41.8
branch 58 142 40.8
condition 13 47 27.6
subroutine 8 18 44.4
pod 8 9 88.8
total 182 443 41.0


line stmt bran cond sub pod time code
1             ## no critic: Modules::ProhibitAutomaticExportation
2              
3             package Getopt::Long::More;
4              
5             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
6             our $DATE = '2020-04-08'; # DATE
7             our $DIST = 'Getopt-Long-More'; # DIST
8             our $VERSION = '0.007'; # VERSION
9              
10 1     1   79335 use strict;
  1         12  
  1         35  
11              
12 1     1   6 use Exporter qw(import);
  1         2  
  1         3193  
13              
14             our @EXPORT = qw(GetOptions optspec OptSpec);
15             our @EXPORT_OK = qw(HelpMessage VersionMessage Configure
16             GetOptionsFromArray GetOptionsFromString
17             OptionsPod);
18              
19             sub optspec {
20 28     28 1 65478 Getopt::Long::More::OptSpec->new(@_);
21             }
22              
23             # synonym for convenience
24             sub OptSpec {
25 0     0 0 0 Getopt::Long::More::OptSpec->new(@_);
26             }
27              
28             sub VersionMessage {
29 0     0 1 0 require Getopt::Long;
30 0         0 goto &Getopt::Long::VersionMessage;
31             }
32              
33             sub Configure {
34 2     2 1 5655 require Getopt::Long;
35 2         10 goto &Getopt::Long::Configure;
36             }
37              
38             # copied verbatim from Getopt::Long, with a bit of modification (add my)
39             sub GetOptionsFromString(@) {
40 0     0 1 0 my ($string) = shift;
41 0         0 require Text::ParseWords;
42 0         0 my $args = [ Text::ParseWords::shellwords($string) ];
43 0   0     0 local $Getopt::Long::caller ||= (caller)[0];
44 0         0 my $ret = GetOptionsFromArray($args, @_);
45 0 0       0 return ( $ret, $args ) if wantarray;
46 0 0       0 if ( @$args ) {
47 0         0 $ret = 0;
48 0         0 warn("GetOptionsFromString: Excess data \"@$args\" in string \"$string\"\n");
49             }
50 0         0 $ret;
51             }
52              
53             # copied verbatim from Getopt::Long
54             sub GetOptions(@) {
55             # Shift in default array.
56 0     0 1 0 unshift(@_, \@ARGV);
57             # Try to keep caller() and Carp consistent.
58 0         0 goto &GetOptionsFromArray;
59             }
60              
61             my $_cur_opts_spec = [];
62              
63             sub GetOptionsFromArray {
64 26     26 1 37226 require Getopt::Long;
65              
66 26         11103 my $ary = shift;
67              
68 26   33     114 local $Getopt::Long::caller ||= (caller)[0]; # grab and set this asap.
69              
70 26         520 my @go_opts_spec;
71              
72 26 100       66 if ( ref($_[0]) ) {
73 6         21 require Scalar::Util;
74 6 50       23 if ( Scalar::Util::reftype ($_[0]) eq 'HASH') {
75 6         12 push @go_opts_spec, shift; # 'hash-storage' is now directly supported
76             }
77             }
78              
79 26         59 my @opts_spec = @_;
80              
81             # provide explicit --help|?, for completion. also, we need to override the
82             # option destination to use our HelpMessage.
83 26 50       55 if ($Getopt::Long::auto_help) {
84             unshift @opts_spec, 'help|?' => optspec(
85 0     0   0 destination => sub { HelpMessage() },
86 0         0 summary => 'Print help message and exit',
87             );
88             }
89 26         35 local $Getopt::Long::auto_help = 0;
90              
91             # provide explicit --version, for completion
92 26 50       53 if ($Getopt::Long::auto_version) {
93             unshift @opts_spec, 'version' => optspec(
94 0     0   0 destination => sub { VersionMessage() },
95 0         0 summary => 'Print program version and exit',
96             );
97             }
98 26         34 local $Getopt::Long::auto_version = 0;
99              
100             # to allow our HelpMessage to generate usage/help based on options spec
101 26         105 $_cur_opts_spec = [@opts_spec];
102              
103             # strip the optspec objects
104 26         64 my $prev;
105             my $has_arg_handler;
106 26         0 my $arg_handler_accessed;
107             MAPPING: # Resulting in the complete EVAPORATION of OptSpec objects, replaced by their destination, if one exists.
108 26         52 for my $e (@opts_spec) {
109 73 100       150 unless ( ref($e) eq 'Getopt::Long::More::OptSpec' ) {
110 51         80 push @go_opts_spec, $e;
111 51         80 next;
112             }
113              
114 22 100       55 next unless exists $e->{destination};
115              
116 14 100       32 if ( $prev eq '<>' ) {
117 3         5 $has_arg_handler++;
118             push @go_opts_spec, sub {
119 2     2   129 $arg_handler_accessed++;
120 2         8 $e->{destination}->(@_);
121 3         14 };
122             } else {
123 11         21 push @go_opts_spec, $e->{destination};
124             }
125             } continue {
126 73         118 $prev = $e;
127             }
128              
129             # if in completion mode, do completion instead of parsing options
130             COMPLETION: {
131 26         39 my $shell;
  26         37  
132 26 50       78 if ($ENV{COMP_SHELL}) {
    50          
133 0         0 ($shell = $ENV{COMP_SHELL}) =~ s!.+/!!;
134             } elsif ($ENV{COMMAND_LINE}) {
135 0         0 $shell = 'tcsh';
136             } else {
137 26         40 $shell = 'bash';
138             }
139              
140 26 50 33     110 if ($ENV{COMP_LINE} || $ENV{COMMAND_LINE}) {
141 0         0 my ($words, $cword);
142 0 0       0 if ($ENV{COMP_LINE}) {
    0          
143 0         0 require Complete::Bash;
144 0         0 ($words,$cword) = @{ Complete::Bash::parse_cmdline(undef, undef, {truncate_current_word=>1}) };
  0         0  
145 0         0 ($words,$cword) = @{ Complete::Bash::join_wordbreak_words($words, $cword) };
  0         0  
146             } elsif ($ENV{COMMAND_LINE}) {
147 0         0 require Complete::Tcsh;
148 0   0     0 $shell //= 'tcsh';
149 0         0 ($words, $cword) = @{ Complete::Tcsh::parse_cmdline() };
  0         0  
150             }
151              
152 0         0 my %opt_completions;
153             my $arg_completion;
154 0         0 for (my $i=0; $i < @opts_spec; $i++) {
155 0 0       0 if ($i % 2 == 0) {
156 0         0 my $o = $opts_spec[$i];
157 0         0 my $os = $opts_spec[$i+1];
158 0 0       0 if (ref($os) eq 'Getopt::Long::More::OptSpec') {
159 0         0 my $completion = $os->{completion};
160 0 0       0 next unless $completion;
161 0 0       0 if (ref $completion eq 'ARRAY') {
162             $completion = sub {
163 0     0   0 require Complete::Util;
164 0         0 my %args = @_;
165             Complete::Util::complete_array_elem(
166             word => $args{word},
167             array => $os->{completion},
168 0         0 );
169 0         0 };
170             }
171 0 0       0 if ($o eq '<>') {
172 0         0 $arg_completion = $completion;
173             } else {
174 0         0 $opt_completions{$o} = $completion;
175             }
176             }
177             }
178             }
179              
180             my $comp = sub {
181 0     0   0 my %args = @_;
182 0 0 0     0 if ($args{type} eq 'optval' && $opt_completions{ $args{ospec} }) {
    0 0        
183 0         0 return $opt_completions{ $args{ospec} }->(%args);
184             } elsif ($args{type} eq 'arg' && $arg_completion) {
185 0         0 return $arg_completion->(%args);
186             }
187 0         0 undef;
188 0         0 };
189              
190 0         0 require Complete::Getopt::Long;
191 0         0 shift @$words; $cword--; # strip program name
  0         0  
192 0         0 my $compres = Complete::Getopt::Long::complete_cli_arg(
193             words => $words, cword => $cword, getopt_spec => {@go_opts_spec},
194             completion => $comp,
195             bundling => $Gteopt::Long::bundling,
196             );
197              
198 0 0       0 if ($shell eq 'bash') {
    0          
    0          
    0          
199 0         0 require Complete::Bash;
200 0         0 print Complete::Bash::format_completion(
201             $compres, {word=>$words->[$cword], workaround_with_wordbreaks=>0});
202             } elsif ($shell eq 'fish') {
203 0         0 require Complete::Fish;
204 0         0 print Complete::Bash::format_completion(
205             $compres, {word=>$words->[$cword]});
206             } elsif ($shell eq 'tcsh') {
207 0         0 require Complete::Tcsh;
208 0         0 print Complete::Tcsh::format_completion($compres);
209             } elsif ($shell eq 'zsh') {
210 0         0 require Complete::Zsh;
211 0         0 print Complete::Zsh::format_completion($compres);
212             } else {
213 0         0 die "Unknown shell '$shell'";
214             }
215              
216 0         0 exit 0;
217             }
218             }
219              
220 26         72 my $res = Getopt::Long::GetOptionsFromArray($ary, @go_opts_spec);
221              
222 26         8617 my $i = -1;
223 26         58 for (@opts_spec) {
224 73         125 $i++;
225 73 100 100     239 if ($i > 0 && ref($_) eq 'Getopt::Long::More::OptSpec') {
226 22         41 my $osname = $opts_spec[$i-1];
227              
228             # check required
229 22 100       51 if ($_->{required}) {
230 8 100       23 if ($osname eq '<>') {
    100          
231 4 100       9 if ($has_arg_handler) {
232 2 100       6 unless ($arg_handler_accessed) {
233 1         9 die "Missing required command-line argument\n";
234             }
235             } else {
236 2 100       5 unless (@{ $ary }) {
  2         7  
237 1         6 die "Missing required command-line argument\n";
238             }
239             }
240             } elsif ( exists $_->{destination} ) {
241 2 100 66     9 if (ref($_->{destination}) eq 'SCALAR'
    50 33        
    50 33        
242 2         21 && !defined(${$_->{destination}})) {
243 1         8 die "Missing required option $osname\n";
244             # XXX doesn't work yet?
245             } elsif (ref($_->{destination}) eq 'ARRAY' &&
246 0         0 !@{$_->{destination}}) {
247 0         0 die "Missing required option $osname\n";
248             # XXX doesn't work yet?
249             } elsif (ref($_->{destination}) eq 'HASH'
250 0         0 && !keys(%{$_->{destination}})) {
251 0         0 die "Missing required option $osname\n";
252             }
253             } else {
254 2         17 die "Can't enforce 'required' status without also knowing the 'destination' for option '$osname'. "
255             . "You need to provide a 'destination' to optspec() in order to benefit from that feature\n";
256             }
257             }
258             # supply default value
259 17 100       39 if (defined $_->{default}) {
260 5 100       35 if ($osname eq '<>') {
    100          
261             # currently ignored
262             } elsif ( exists $_->{destination} ) {
263 2 100 66     13 if (ref($_->{destination}) eq 'SCALAR'
    50 33        
    50 33        
264 2         22 && !defined(${$_->{destination}})) {
265 1         2 ${$_->{destination}} = $_->{default};
  1         4  
266             # XXX doesn't work yet?
267             } elsif (ref($_->{destination}) eq 'ARRAY' &&
268 0         0 !@{$_->{destination}}) {
269 0         0 $_->{destination} = [@{ $_->{default} }]; # shallow copy
  0         0  
270             # XXX doesn't work yet?
271             } elsif (ref($_->{destination}) eq 'HASH' &&
272 0         0 !keys(%{$_->{destination}})) {
273 0         0 $_->{destination} = { %{ $_->{default} } }; # shallow copy
  0         0  
274             }
275             } else {
276 2         20 die "Can't assign 'default' without also knowing the 'destination' for option '$osname'. "
277             . "You need to provide a 'destination' to optspec() in order to benefit from that feature\n";
278             }
279             }
280             }
281             }
282              
283 19         92 $res;
284             }
285              
286             sub HelpMessage {
287 0 0   0 1 0 my $opts_spec = @_ ? [@_] : $_cur_opts_spec;
288 0         0 my $i = -1;
289 0         0 my @entries;
290 0         0 my $max_opt_spec_len = 0;
291 0         0 for (my $i=0; $i < @$opts_spec; $i++) {
292 0 0       0 if ($i % 2 == 0) {
293             # normalize dashes at the front
294 0         0 my $osname = $opts_spec->[$i];
295 0 0       0 next if $osname eq '<>';
296 0         0 $osname =~ s/^-+//;
297 0         0 (my $oname = $osname) =~ s/[=|].*//;
298 0 0       0 $osname = length($oname) > 1 ? "--$osname" : "-$osname";
299              
300 0         0 push @entries, [$osname, "", "", 0, undef]; # [opt, summary, desc, required?, default]
301 0         0 my $len = length($osname);
302 0 0       0 $max_opt_spec_len = $len if $max_opt_spec_len < $len;
303 0         0 my $os = $opts_spec->[$i+1];
304 0 0       0 if (ref($os) eq 'Getopt::Long::More::OptSpec') {
305 0   0     0 $entries[-1][1] ||= $os->{summary};
306 0 0       0 $entries[-1][3] = 1 if $os->{required};
307 0         0 $entries[-1][4] = $os->{default};
308             }
309             }
310             }
311              
312 0         0 my $prog = $0;
313 0         0 $prog =~ s!.+[/\\]!!;
314              
315             print join(
316             "",
317             "Usage: $prog [options]\n",
318             "Options (* marks required option):\n",
319             map {
320 0 0       0 sprintf(" %-${max_opt_spec_len}s%s %s%s\n",
  0 0       0  
321             $_->[0],
322             $_->[3] ? "*" : " ",
323             $_->[1],
324             defined($_->[4]) ? " (default: $_->[4])" : "",
325             )
326             } @entries,
327             );
328 0         0 exit 0;
329             }
330              
331             sub OptionsPod {
332 0 0   0 1 0 my $opts_spec = @_ ? [@_] : $_cur_opts_spec;
333 0         0 my $i = -1;
334 0         0 my @entries;
335 0         0 for (my $i=0; $i < @$opts_spec; $i++) {
336 0 0       0 if ($i % 2 == 0) {
337             # normalize dashes at the front
338 0         0 my $osname = $opts_spec->[$i];
339 0 0       0 next if $osname eq '<>';
340 0         0 $osname =~ s/^-+//;
341 0         0 (my $oname = $osname) =~ s/[=|].*//;
342 0 0       0 $osname = length($oname) > 1 ? "--$osname" : "-$osname";
343              
344 0         0 push @entries, [$osname, "", "", 0, undef]; # [opt, summary, desc, required?, default]
345 0         0 my $os = $opts_spec->[$i+1];
346 0 0       0 if (ref($os) eq 'Getopt::Long::More::OptSpec') {
347 0   0     0 $entries[-1][1] ||= $os->{summary};
348 0   0     0 $entries[-1][2] ||= $os->{description};
349 0 0       0 $entries[-1][3] = 1 if $os->{required};
350 0         0 $entries[-1][4] = $os->{default};
351             }
352             }
353             }
354              
355 0         0 my @res;
356              
357 0         0 push @res, "=head1 OPTIONS\n\n";
358 0         0 for (@entries) {
359 0         0 my @notes;
360 0 0       0 if ($_->[3]) { push @notes, "required" }
  0         0  
361 0 0       0 if (defined $_->[4]) { push @notes, "default: $_->[4]" }
  0         0  
362 0 0       0 push @res, "=head2 $_->[0]", (@notes ? " (".join(", ", @notes).")" : ""), "\n\n";
363 0 0       0 push @res, "$_->[1]\n\n" if length $_->[1];
364 0 0       0 push @res, "$_->[2]\n\n" if length $_->[2];
365             }
366              
367 0         0 join("", @res);
368              
369             }
370              
371             package # hide from PAUSE indexer
372             Getopt::Long::More::Internal::Util;
373              
374             # TAU: Named this ::Internal::Util because ::Util was already taken on CPAN.
375             our @CARP_NOT = qw( Getopt::Long::More Getopt::Long::More::Internal::Util Getopt::Long::More::OptSpec);
376              
377             # The subroutines here (::Util) are intended to be pretty generic
378             # and so could also be used elsewhere later on.
379              
380             sub map_args {
381 28 50   28   40 my %o = %{; shift || {} }; # shallow copy
  28         127  
382 28         80 my %p = (@_);
383             my ($deprecated, $aliases,
384 28 100       72 $deprecated_aliases) = map {; $_ || {} } @p{qw/deprecated aliases deprecated_aliases/};
  84         268  
385              
386 28         86 my %deprecations = ( %$deprecated, %$deprecated_aliases );
387 28         72 my %synonyms = ( %$aliases, %$deprecated_aliases );
388              
389             # Deprecated => warn
390 28         95 while ( my ($k, $canon) = each %deprecations ) {
391 28 100       117 next unless exists $o{$k};
392 2         13 require Carp;
393 2 50       36 Carp::carp( "'$k' is deprecated!",
394             ( defined($canon) ? " You should use '$canon' instead." : () ),
395             "\n"
396             );
397             }
398              
399             # Synonym => map to canonical key.
400 28         1239 while ( my ($k, $canon) = each %synonyms ) {
401 28 100       88 next unless exists $o{$k};
402              
403 2         7 my $v = delete $o{$k};
404 2 50       6 next unless defined $canon; # if $canon key is undefined => disregard
405              
406 2 100       7 if ( exists $o{$canon} ) {
407 1         7 require Carp;
408 1         24 Carp::croak( "'$k' may only be used as a synonym for '$canon'; not alongside it.", "\n" );
409             }
410              
411 1         4 $o{$canon} = $v;
412             }
413 27 50       120 wantarray ? (%o) : \%o;
414             }
415              
416              
417             package # hide from PAUSE indexer
418             Getopt::Long::More::OptSpec;
419              
420             # Poor man's import....
421             *map_args = \&Getopt::Long::More::Internal::Util::map_args;
422              
423             sub new {
424 28     28   52 my $class = shift;
425 28         116 my $obj = map_args( { @_ }, deprecated_aliases => { handler => 'destination' } );
426              
427 27         106 for (keys %$obj) {
428 35 100       126 next if /\A(x|x\..+|_.*)\z/;
429 31 100       124 unless (/\A(required|default|summary|description|destination|completion)\z/) {
430 2         22 die "Unknown optspec property '$_'";
431             }
432             }
433 25         198 bless $obj, $class;
434             }
435              
436             1;
437             # ABSTRACT: Like Getopt::Long, but with more stuffs
438              
439             __END__