File Coverage

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