File Coverage

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