File Coverage

blib/lib/Getopt/O2.pm
Criterion Covered Total %
statement 256 270 94.8
branch 129 138 93.4
condition 29 35 82.8
subroutine 32 32 100.0
pod 5 12 41.6
total 451 487 92.6


line stmt bran cond sub pod time code
1             ##------------------------------------------------------------------------------
2             ## $Id: O2.pm 887 2016-08-29 12:57:34Z schieche $
3             ##------------------------------------------------------------------------------
4             package Getopt::O2;
5            
6 2     2   106588 use 5.010;
  2         11  
7 2     2   11 use strict;
  2         3  
  2         39  
8 2     2   10 use warnings;
  2         4  
  2         106  
9            
10 2     2   12 no if $] >= 5.017011, warnings => 'experimental::smartmatch';
  2         4  
  2         14  
11            
12             our $VERSION = '1.0.20';
13             ##------------------------------------------------------------------------------
14 2     2   1156 use English '-no_match_vars';
  2         3196  
  2         12  
15 2     2   1854 use Readonly;
  2         7816  
  2         204  
16             Readonly my $USAGE_MARGIN => 80;
17             Readonly my $USAGE_OPTIONS_LENGTH => 29;
18            
19 2     2   16 use Carp 'confess';
  2         4  
  2         97  
20 2     2   15 use Scalar::Util 'looks_like_number';
  2         4  
  2         4752  
21             ##------------------------------------------------------------------------------
22             sub new
23             {
24 27 100   27 0 28571 my $class = ref $_[0] ? ref $_[0] : $_[0];
25 27         106 my $self = bless {
26             shortOptions => {},
27             longOptions => {},
28             options => {}
29             }, $class;
30            
31 27         60 return $self
32             }
33             ##------------------------------------------------------------------------------
34             sub getopt ## no critic (Subroutines::ProhibitExcessComplexity)
35             {
36 27     27 1 8675 my $self = shift;
37 27         40 my $dest = shift;
38 27         32 my $args = shift;
39 27         42 my ($arg,$key,$rule,%context,@arguments);
40            
41 27         150 $self->{'options'} = {%$dest};
42 27         126 $self->parseRules();
43            
44 22         48 PROCESS_ARGUMENTS: while (@ARGV) {
45 46         70 $arg = shift @ARGV;
46            
47 46 100 100     388 if (!defined $arg || !length $arg || '-' eq $arg || $arg !~ /^-/) {
    100 100        
      100        
48 5         9 push @arguments, $arg;
49 5         9 next PROCESS_ARGUMENTS;
50             } elsif ('--' eq $arg) {
51 2         5 push @arguments, @ARGV;
52 2         5 last PROCESS_ARGUMENTS;
53             }
54            
55 39 100       92 if ($arg !~ /^--/) {
56 14         28 $key = (substr $arg, 1, 1);
57 14         25 $rule = $self->{'shortOptions'}->{$key};
58 14 100       37 $self->error('No such option "-%s"', $key)
59             unless defined $rule;
60 13         24 $rule = $self->{'longOptions'}->{$rule};
61            
62 13 100       25 if (length $arg > 2) {
63 5 100       12 if ($rule->type) { ## no critic (ControlStructures::ProhibitDeepNests)
64 1         2 unshift @ARGV, (substr $arg, 2);
65             } else {
66 4         13 unshift @ARGV, '-'.(substr $arg, 2);
67             }
68             }
69             } else {
70 25         47 $key = (substr $arg, 2);
71            
72 25 100       59 if (~(index $key, '=')) {
73 1         4 ($key,$arg) = (split /=/, $key, 2);
74 1         3 unshift @ARGV, $arg;
75             }
76            
77 25         44 $rule = $self->{'longOptions'}->{$key};
78 25 100       51 unless (defined $rule) {
79 5 100       24 $self->error('No such option "--%s"', $key)
80             if 0 != (index $key, 'no-');
81 3         6 $key = (substr $key, 3);
82 3         5 $rule = $self->{'longOptions'}->{$key};
83            
84 3 100 100     13 $self->error('No such option "--no-%s" or negatable "--%s"', $key, $key)
85             unless defined $rule && $rule->negatable;
86 1         3 $rule->{'_negate'} = 1;
87             }
88             }
89            
90 34 100       62 if (defined $rule->context) {
91 1         2 foreach (@{$rule->context->{'need'}}) {
  1         12  
92             $self->error('Option "--%s" cannot be used in this context.', $rule->long)
93 1 50       17 unless exists $context{$_};
94             }
95            
96 0         0 delete $context{$_} foreach @{$rule->context->{'clear'}};
  0         0  
97 0         0 $context{$_} = 1 foreach @{$rule->context->{'set'}};
  0         0  
98             }
99            
100 33 100       53 if ($rule->multiple) {
    100          
101             $self->{'options'}->{$rule->long} = 0
102 3 100       6 unless exists $self->{'options'}->{$rule->long};
103 3         7 ++$self->{'options'}->{$rule->long};
104 3         9 next PROCESS_ARGUMENTS;
105             } elsif(!defined $rule->type) {
106 6         10 $arg = undef;
107             } else {
108 24         123 $arg = $self->getValue();
109 24 100       50 $self->error('Option "--%s" needs a mandatory value.', $rule->long)
110             unless defined $arg;
111            
112 22 100       39 delete $self->{'options'}->{$rule->long}
113             if $rule->is_unused;
114 22         58 $rule->mark_used;
115            
116             $self->{'options'}->{$rule->long} = []
117 22 100 100     38 if $rule->is_list && !defined $self->{'options'}->{$rule->long};
118            
119 22         41 given($rule->type) {
120 22         53 when('s') {
121             }
122            
123 11         16 when('i') {
124 2 100       11 $self->error('Argument "%s" to "--%s" isn\'t numeric', $arg, $rule->long)
125             unless looks_like_number($arg);
126 1         3 $arg = int $arg;
127             }
128            
129 9         13 when('?') {
130             $self->error('Value "%s" to argument "--%s" is invalid.', $arg, $rule->long)
131 9 100       11 unless $arg ~~ @{$rule->values || []};
  9 100       17  
132             }
133             }
134            
135 19 100       37 if ($rule->is_list) {
136 13 100       22 if ('?' ne $rule->type) { ## no critic (ControlStructures::ProhibitDeepNests)
137 7         9 push @{$self->{'options'}->{$rule->long}}, $arg;
  7         16  
138             } else {
139 5         23 push @{$self->{'options'}->{$rule->long}}, $arg
140 6 100 100     10 unless ($rule->keep_unique && $arg ~~ @{$self->{'options'}->{$rule->long}});
  4         9  
141             }
142 13         37 next PROCESS_ARGUMENTS;
143             }
144             }
145            
146 12 100       21 if (defined $rule->action) {
147 1         2 $arg = $rule->action->($arg, $key, $rule);
148             } else {
149 11 100       26 $arg = $rule->{'_negate'} ? '' : 1
    100          
150             unless defined $arg;
151             }
152            
153 11         21 $self->{'options'}->{$rule->long} = $arg;
154             }
155            
156 10         12 %$dest = %{$self->{'options'}};
  10         36  
157 10 100       26 @$args = @arguments if ref $args;
158 10         28 $self->{'options'} = {};
159 10         29 return $self
160             }
161             ##------------------------------------------------------------------------------
162             sub error
163             {
164 1     1 1 5 return shift->usage(1, shift(), @_);
165             }
166             ##------------------------------------------------------------------------------
167             sub getProgram
168             {
169 5     5 0 11 my $program = $ENV{_};
170 5         36 $program =~ s{.*/([^/]+)$}{$1};
171 5 50       26 $program = $PROGRAM_NAME if 'perl' eq $program;
172 5         70 return $program;
173             }
174             ##------------------------------------------------------------------------------
175             sub getProgramDescription
176             {
177 2     2 1 6 my $class = ref $_[0];
178 2         14 return qq{another example of this programmer's lazyness: it forgot the description (and should implement ${class}::getProgramDescription())}
179             }
180             ##------------------------------------------------------------------------------
181             sub getValue
182             {
183 24 100   24 0 51 return unless @ARGV;
184 23         35 my $value = $ARGV[0];
185             return shift @ARGV
186 23 50 100     139 if !defined $value || !length $value || '-' eq $value || $value !~ /^-/;
      100        
      66        
187 0 0       0 return if $value ne '--';
188 0         0 shift @ARGV;
189 0 0       0 return unless @ARGV;
190 0         0 $value = shift @ARGV;
191 0         0 unshift @ARGV, '--';
192 0         0 return $value;
193             }
194             ##------------------------------------------------------------------------------
195             sub getOptionRules
196             {
197 7     7 1 187 my $self = shift;
198            
199             return
200 7     1   99 'h|help' => ['Display this help message', sub {$self->usage(0)}],
  1         9  
201             'v|verbose+' => 'Increase program verbosity',
202             undef
203             }
204             ##------------------------------------------------------------------------------
205             sub parseRules ## no critic (Subroutines::ProhibitExcessComplexity)
206             {
207 30     30 0 45 my $self = shift;
208 30         68 my @rules = $self->getOptionRules();
209            
210             ## Perl Critic false positive on "$}" at the end of the reg-ex
211             ## no critic (Variables::ProhibitPunctuationVars)
212 30         1665 state $pattern = qr{^
213             (?:(?P!))?
214             (?:(?P[[:alpha:]])[|])?
215             (?P[[:alpha:]](?:[[:alpha:]-]*)[[:alpha:]])
216             (?:
217             (?:=(?P[si?]@?))
218             |
219             (?P[+])
220             )?
221             $}x;
222             ## use critic
223            
224 30         49 my ($arg,$opt,@parsed);
225            
226 30         72 while (@rules) {
227 68         115 $arg = shift @rules;
228 68 100       152 unless (defined $arg) {
229 9 100       18 push @parsed, undef if wantarray;
230 9         20 next;
231             }
232 59         86 $opt = $arg;
233 59 100       283 confess('Not enough rules') unless @rules;
234 58         87 $arg = shift @rules;
235            
236 58 100       128 $arg = [$arg] unless ref $arg;
237 58 100       637 confess("Invalid rule pattern '$opt'") if $opt !~ $pattern;
238 57         485 my $rule = Getopt::O2::Rule->new($arg, %LAST_PAREN_MATCH);
239            
240             confess(sprintf q{Option spec '%s' redefines long option '%s'}, $opt, $rule->long)
241 56 100       178 if exists $self->{'longOptions'}->{$rule->long};
242            
243 55 100       112 if (defined $rule->short) {
244             confess(sprintf q{Option spec '%s' redefines short option '%s'}, $opt, $rule->short)
245 34 100       59 if exists $self->{'shortOptions'}->{$rule->short};
246 33         52 $self->{'shortOptions'}->{$rule->short} = $rule->long;
247             }
248            
249 54 100       91 if (defined $rule->default) {
250 4         8 $self->{'options'}->{$rule->long} = $rule->default;
251             }
252            
253 54         99 $self->{'longOptions'}->{$rule->long} = $rule;
254 54 100       152 push @parsed, $rule if wantarray
255             }
256            
257 25 100       61 return $self unless wantarray;
258 3         11 return @parsed;
259             }
260             ##------------------------------------------------------------------------------
261             sub showOptionDefaultValues
262             {
263 3     3 0 6 return;
264             }
265             ##------------------------------------------------------------------------------
266             sub usage ## no critic (Subroutines::ProhibitExcessComplexity)
267             {
268 3     3 1 1704 my $self = shift;
269 3         8 my ($exitCode,$message,@args) = @_;
270            
271 3 100       7 if (defined $message) {
272 1         6 $message = sprintf "Error: $message", @args;
273             } else {
274 2         9 $message = sprintf '%s - %s', $self->getProgram(), $self->getProgramDescription();
275             }
276            
277             print STDERR "$_\n"
278 3         8 foreach wrapString($message, 0, 8, $USAGE_MARGIN);
279 3         21 printf STDERR "\nUsage: %s [options...]\n\nValid options:\n\n", $self->getProgram();
280            
281             ## no critic (Variables::ProhibitLocalVars)
282 3         18 local $self->{'longOptions'} = undef;
283 3         6 local $self->{'shortOptions'} = undef;
284             ## use critic
285            
286 3         8 my @rules = $self->parseRules();
287 3         6 my ($rule,$line,$long,$len,$show_default);
288            
289 3         15 $show_default = $self->showOptionDefaultValues();
290            
291 3         7 PROCESS_RULES: while (@rules) {
292             #@type Getopt::O2::Rule
293 17         37 $rule = shift @rules;
294            
295 17 100       32 unless (defined $rule) {
296 4         38 print STDERR "\n";
297 4         19 next PROCESS_RULES;
298             }
299            
300 13         21 $line = ' ';
301 13         28 $long = $rule->long;
302 13 100       41 $long = "(no-)$long" if $rule->negatable;
303            
304 13 100       19 unless (defined $rule->short) {
305 2         6 $long = "--$long";
306             } else {
307 11         40 $long = " [--$long]";
308 11         24 $line .= '-'.$rule->short;
309             }
310            
311 13         34 $line = "$line$long";
312 13 100       25 $line .= ' ARG' if defined $rule->type;
313            
314 13 100       52 $line .= ' ' x ($USAGE_OPTIONS_LENGTH - $len)
315             if $USAGE_OPTIONS_LENGTH > ($len = length($line) + 2);
316 13         123 $line = "$line: ";
317 13         145 print STDERR $line;
318            
319             print STDERR "$_\n"
320 13         46 foreach wrapString($rule->help($show_default), length $line, $USAGE_OPTIONS_LENGTH, $USAGE_MARGIN);
321             }
322            
323 3         27 print STDERR "\n";
324 3         14 exit $exitCode;
325             }
326             ##------------------------------------------------------------------------------
327             sub wrapString
328             {
329 16     16 0 60 my ($string,$firstIndent,$leftIndent,$wrapAt) = @_;
330 16         126 my (@lines,$len,$pos,$nChars);
331            
332 16         42 for ($nChars = $wrapAt - $firstIndent; length $string; $nChars = $wrapAt - $leftIndent) {
333 22         26 $len = length $string;
334            
335 22 100       38 if ($len < $nChars) {
336 16         28 push @lines, $string;
337 16         22 last;
338             }
339            
340 6         21 $pos = strrpos((substr $string, 0, $nChars), ' ');
341 6 100       16 if (-1 == $pos) {
342 1         3 push @lines, (substr $string, 0, $nChars);
343 1         3 $string = (substr $string, $nChars);
344             } else {
345 5         11 push @lines, (substr $string, 0, $pos);
346 5         22 $string = (substr $string, $pos + 1);
347             }
348             }
349            
350 16 100       32 if (@lines > 1) {
351 3         7 my $indent = ' ' x $leftIndent;
352 3         19 $lines[$_] = "$indent$lines[$_]" foreach (1..$#lines);
353             }
354            
355             return @lines
356 16         334 }
357             ##------------------------------------------------------------------------------
358             sub strrpos
359             {
360 6     6 0 19 my ($string,$find) = @_;
361 6         11 my ($length) = length $find;
362            
363 6         14 for (my $pos = length($string) - 1; $pos >= 0; --$pos) {
364 93 100       198 return $pos if $find eq (substr $string, $pos, $length);
365             }
366            
367 1         3 return -1
368             }
369             ##------------------------------------------------------------------------------
370             package Getopt::O2::Rule; ## no critic (Modules::ProhibitMultiplePackages)
371            
372 2     2   18 use strict;
  2         10  
  2         67  
373 2     2   13 use warnings;
  2         4  
  2         58  
374 2     2   10 use feature ':5.10';
  2         3  
  2         608  
375            
376 2     2   28 use Carp 'confess';
  2         5  
  2         125  
377            
378             BEGIN {
379             ## no critic (TestingAndDebugging::ProhibitNoStrict)
380 2     2   12 no strict 'refs';
  2         23  
  2         226  
381 2     2   8 foreach my $method (qw(action context default is_list keep_unique long multiple negatable short type values)) {
382 22     674   1937 *{__PACKAGE__."::$method"} = sub {shift->{$method}}
  674         1759  
383 22         72 }
384             ## use critic
385             }
386            
387             sub new ## no critic (Subroutines::ProhibitExcessComplexity)
388             {
389 57     57   104 my $class = shift;
390 57         346 my ($arg, %options) = @_;
391 57         104 my (%rule);
392            
393 57         116 $rule{'long'} = $options{'long'};
394 57 100       133 $rule{'short'} = $options{'short'} if exists $options{'short'};
395            
396 57 100       120 $rule{'negatable'} = 1 if $options{'negatable'};
397 57 100       126 if ($options{'multiple'}) {
    100          
398 8         11 $rule{'multiple'} = 1
399             } elsif ($options{'type'}) {
400 22         46 $rule{'type'} = (substr $options{'type'}, 0, 1);
401 22         50 $rule{'is_list'} = ~(index $options{'type'}, '@');
402             $rule{'keep_unique'} = $options{'keep_unique'} // 1
403 22 100 50     55 if $rule{'is_list'};
404             }
405            
406 57         100 $rule{'help'} = shift @$arg;
407 57         319 $rule{'help'} =~ s/^\s+|\s+$//g;
408 57         265 $rule{'help'} =~ s/\s+/ /g;
409 57 100       185 $rule{'help'} .= '.' if $rule{'help'} !~ /[.]$/;
410            
411 57 100       118 if (@$arg) {
412 22 100       55 $rule{'action'} = shift @$arg
413             if 'CODE' eq ref $arg->[0];
414 22 100       181 confess('Invalid rule options; the remainder is a list with uneven members')
415             if 0 != (@$arg % 2);
416 21         135 %rule = (%rule, @$arg);
417             }
418            
419 56 100       125 if (defined $rule{'context'}) {
420 2         7 $rule{'context'} = [split /,/, $rule{'context'}];
421             $rule{'context'} = {
422 1         4 set => [map {(substr $_, 1)} grep {/^[+]/} @{$rule{'context'}}],
  2         7  
  2         6  
423 0         0 clear => [map {(substr $_, 1)} grep {/^-/} @{$rule{'context'}}],
  2         7  
  2         5  
424 2         5 need => [grep {/^[^+-]/} @{$rule{'context'}}],
  2         10  
  2         4  
425             };
426             }
427            
428 56         84 $rule{'_used'} = 0;
429            
430 56         166 return bless \%rule, $class
431             }
432             ##------------------------------------------------------------------------------
433             sub is_unused
434             {
435 22     22   70 return !shift->{'_used'};
436             }
437             ##------------------------------------------------------------------------------
438             sub mark_used
439             {
440 22     22   29 my $self = shift;
441 22         37 $self->{'_used'} = 1;
442 22         37 return $self;
443             }
444             ##------------------------------------------------------------------------------
445             sub help
446             {
447 13     13   24 my $self = shift;
448 13         16 my $show_default = shift;
449            
450 13 100       33 unless (defined $self->{'type'}) { # flags
    100          
451 10         34 return $self->{'help'};
452 0         0 } elsif ('?' ne $self->{'type'}) { # anything but ENUM
453 2         5 my $helpstr = $self->{'help'};
454            
455 2 50 33     10 return $helpstr unless $show_default && defined $self->{'default'};
456            
457 0         0 $helpstr =~ s/\s*[.]\s*$//;
458 0         0 return sprintf '%s (default: "%s").', $helpstr, $self->{'default'};
459             } else {
460 1         3 my @values = map {qq{"$_"}} @{$self->values};
  3         10  
  1         2  
461             my $default_value = ($show_default && defined $self->{'default'})
462 1 50 33     5 ? (sprintf ' [default: "%s"]', $self->{'default'})
463             : '';
464 1         13 return $self->{'help'} . (sprintf ' (ARG must be %s or %s)%s',
465             (join ', ', @values[0..$#values-1]), $values[-1], $default_value);
466             }
467             }
468             ##------------------------------------------------------------------------------
469             1;
470             __END__