File Coverage

blib/lib/Getopt/Long/More.pm
Criterion Covered Total %
statement 75 207 36.2
branch 46 126 36.5
condition 13 47 27.6
subroutine 7 17 41.1
pod 8 9 88.8
total 149 406 36.7


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