File Coverage

blib/lib/Getopt/Long/Less.pm
Criterion Covered Total %
statement 152 182 83.5
branch 91 126 72.2
condition 55 81 67.9
subroutine 9 11 81.8
pod 0 4 0.0
total 307 404 75.9


line stmt bran cond sub pod time code
1             ## no critic: TestingAndDebugging::RequireUseStrict
2             package Getopt::Long::Less;
3              
4             # IFUNBUILT
5             # use strict 'subs', 'vars';
6             # use warnings;
7             # END IFUNBUILT
8              
9             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
10             our $DATE = '2023-05-04'; # DATE
11             our $DIST = 'Getopt-Long-Less'; # DIST
12             our $VERSION = '0.091'; # VERSION
13              
14             our @EXPORT = qw(GetOptions);
15             our @EXPORT_OK = qw(Configure GetOptionsFromArray);
16              
17             my $Opts = {};
18              
19             sub import {
20 1     1   10 my $pkg = shift;
21 1         4 my $caller = caller;
22 1 50       26 my @imp = @_ ? @_ : @EXPORT;
23 1         3 for my $imp (@imp) {
24 3 50       5 if (grep {$_ eq $imp} (@EXPORT, @EXPORT_OK)) {
  9         20  
25 3         3 *{"$caller\::$imp"} = \&{$imp};
  3         3024  
  3         7  
26             } else {
27 0         0 die "$imp is not exported by ".__PACKAGE__;
28             }
29             }
30             }
31              
32             sub Configure {
33 0     0 0 0 my $old_opts = {%$Opts};
34              
35 0 0       0 if (ref($_[0]) eq 'HASH') {
36 0         0 $Opts->{$_} = $_[0]{$_} for keys %{$_[0]};
  0         0  
37             } else {
38 0         0 for (@_) {
39 0 0       0 if ($_ eq 'no_ignore_case') { next }
  0 0       0  
    0          
    0          
    0          
    0          
    0          
40 0         0 elsif ($_ eq 'bundling') { next }
41 0         0 elsif ($_ eq 'auto_abbrev') { next }
42 0         0 elsif ($_ eq 'gnu_compat') { next }
43 0         0 elsif ($_ eq 'no_getopt_compat') { next }
44 0         0 elsif ($_ eq 'permute') { next }
45 0         0 elsif (/\Ano_?require_order\z/) { next }
46             #elsif (/\A(no_?)?pass_through\z/) { $Opts->{pass_through} = $1 ?0:1 }
47 0         0 else { die "Unknown or erroneous config parameter \"$_\"\n" }
48             }
49             }
50 0         0 $old_opts;
51             }
52              
53             sub GetOptionsFromArray {
54 39     39 0 162891 my $argv = shift;
55              
56 39         78 my $vals;
57             my $spec;
58              
59             # if next argument is a hashref, it means user wants to store values in this
60             # hash. and the spec is a list.
61 39 100       112 if (ref($_[0]) eq 'HASH') {
62 35         49 $vals = shift;
63 35     33   72 $spec = {map { $_ => sub { $vals->{ $_[0]->name } = $_[1] } } @_};
  51         244  
  33         75  
64             } else {
65 4         9 $spec = {@_};
66             }
67              
68             # parse option spec
69 39         77 my %parsed_spec;
70 39         116 for my $k (keys %$spec) {
71 55 50       113 my $parsed = parse_getopt_long_opt_spec($k)
72             or die "Error in option spec: $k\n";
73 55 50       119 if (defined $parsed->{max_vals}) {
74 0         0 die "Cannot repeat while bundling: $k\n";
75             }
76 55         104 $parsed->{_orig} = $k;
77 55         146 $parsed_spec{$parsed->{opts}[0]} = $parsed;
78             }
79 39         149 my @parsed_spec_opts = sort keys %parsed_spec;
80              
81 39         59 my $success = 1;
82              
83             my $code_find_opt = sub {
84 53     53   108 my ($wanted, $short_mode) = @_;
85 53         79 my @candidates;
86             OPT_SPEC:
87 53         99 for my $opt (@parsed_spec_opts) {
88 81         128 my $s = $parsed_spec{$opt};
89 81         106 for my $o0 (@{ $s->{opts} }) {
  81         139  
90 92 100 66     233 for my $o ($s->{is_neg} && length($o0) > 1 ?
91             ($o0, "no$o0", "no-$o0") : ($o0)) {
92 95         152 my $is_neg = $o0 ne $o;
93 95 100 100     251 next if $short_mode && length($o) > 1;
94 84 100       203 if ($o eq $wanted) {
    100          
95             # perfect match, we immediately go with this one
96 47         138 @candidates = ([$opt, $is_neg]);
97 47         113 last OPT_SPEC;
98             } elsif (index($o, $wanted) == 0) {
99             # prefix match, collect candidates first
100 3         7 push @candidates, [$opt, $is_neg];
101 3         8 next OPT_SPEC;
102             }
103             }
104             }
105             }
106 53 100       162 if (!@candidates) {
    100          
107 4         158 warn "Unknown option: $wanted\n";
108 4         18 $success = 0;
109 4         18 return (undef, undef);
110             } elsif (@candidates > 1) {
111             warn "Option $wanted is ambiguous (" .
112 1         4 join(", ", map {$_->[0]} @candidates) . ")\n";
  2         48  
113 1         6 $success = 0;
114 1         17 return (undef, undef, 1);
115             }
116 48         72 return @{ $candidates[0] };
  48         185  
117 39         228 };
118              
119             my $code_set_val = sub {
120 46     46   85 my $is_neg = shift;
121 46         59 my $name = shift;
122              
123 46         67 my $parsed = $parsed_spec{$name};
124 46         68 my $spec_key = $parsed->{_orig};
125 46         65 my $destination = $spec->{$spec_key};
126 46         84 my $ref = ref $destination;
127              
128 46         62 my $val;
129 46 100       85 if (@_) {
130 28         43 $val = shift;
131             } else {
132 18 100 100     211 if ($parsed->{is_inc} && $ref eq 'SCALAR') {
    100 66        
    100 33        
    100 100        
    100 33        
      33        
      100        
      33        
      33        
      66        
      33        
133 3 50       8 $val = (defined($$destination) ? $$destination : 0) + 1;
134             } elsif ($parsed->{is_inc} && $vals) {
135 1 50       5 $val = (defined $vals->{$name} ? $vals->{$name} : 0) + 1;
136             } elsif ($parsed->{type} && $parsed->{type} eq 'i' ||
137             $parsed->{opttype} && $parsed->{opttype} eq 'i') {
138 2         4 $val = 0;
139             } elsif ($parsed->{type} && $parsed->{type} eq 'f' ||
140             $parsed->{opttype} && $parsed->{opttype} eq 'f') {
141 1         3 $val = 0;
142             } elsif ($parsed->{type} && $parsed->{type} eq 's' ||
143             $parsed->{opttype} && $parsed->{opttype} eq 's') {
144 2         5 $val = '';
145             } else {
146 9 100       20 $val = $is_neg ? 0 : 1;
147             }
148             }
149              
150             # type checking
151 46 100 100     352 if ($parsed->{type} && $parsed->{type} eq 'i' ||
    100 100        
      100        
      100        
      100        
      66        
152             $parsed->{opttype} && $parsed->{opttype} eq 'i') {
153 8 100       36 unless ($val =~ /\A[+-]?\d+\z/) {
154 3         121 warn qq|Value "$val" invalid for option $name (number expected)\n|;
155 3         25 return 0;
156             }
157             } elsif ($parsed->{type} && $parsed->{type} eq 'f' ||
158             $parsed->{opttype} && $parsed->{opttype} eq 'f') {
159 11 100       53 unless ($val =~ /\A[+-]?(\d+(\.\d+)?|\.\d+)([Ee][+-]?\d+)?\z/) {
160 4         184 warn qq|Value "$val" invalid for option $name (number expected)\n|;
161 4         32 return 0;
162             }
163             }
164              
165 39 100       82 if ($ref eq 'CODE') {
    100          
166 34         94 my $cb = Getopt::Long::Less::Callback->new(
167             name => $name,
168             );
169 34         71 $destination->($cb, $val);
170             } elsif ($ref eq 'SCALAR') {
171 4         8 $$destination = $val;
172             } else {
173             # no nothing
174             }
175 39         172 1;
176 39         149 };
177              
178 39         62 my $i = -1;
179 39         51 my @remaining;
180             ELEM:
181 39         95 while (++$i < @$argv) {
182 54 100       315 if ($argv->[$i] eq '--') {
    100          
    100          
183              
184 2         10 push @remaining, @{$argv}[$i+1 .. @$argv-1];
  2         5  
185 2         6 last ELEM;
186              
187             } elsif ($argv->[$i] =~ /\A--(.+?)(?:=(.*))?\z/) {
188              
189 46         163 my ($used_name, $val_in_opt) = ($1, $2);
190 46         106 my ($opt, $is_neg, $is_ambig) = $code_find_opt->($used_name);
191 46 100       104 unless (defined $opt) {
192 5 100       17 push @remaining, $argv->[$i] unless $is_ambig;
193 5         20 next ELEM;
194             }
195              
196 41         75 my $spec = $parsed_spec{$opt};
197             # check whether option requires an argument
198 41 100 66     158 if ($spec->{type} ||
      100        
      100        
199             $spec->{opttype} &&
200             (defined($val_in_opt) && length($val_in_opt) || ($i+1 < @$argv && $argv->[$i+1] !~ /\A-/))) {
201 26 100       59 if (defined($val_in_opt)) {
202             # argument is taken after =
203 2 50       7 unless ($code_set_val->($is_neg, $opt, $val_in_opt)) {
204 0         0 $success = 0;
205 0         0 next ELEM;
206             }
207             } else {
208 24 100       51 if ($i+1 >= @$argv) {
209             # we are the last element
210 1         40 warn "Option $used_name requires an argument\n";
211 1         5 $success = 0;
212 1         3 last ELEM;
213             }
214             # take the next element as argument
215 23 50 66     62 if ($spec->{type} || $argv->[$i+1] !~ /\A-/) {
216 23         27 $i++;
217 23 100       46 unless ($code_set_val->($is_neg, $opt, $argv->[$i])) {
218 7         13 $success = 0;
219 7         27 next ELEM;
220             }
221             }
222             }
223             } else {
224 15 50       33 unless ($code_set_val->($is_neg, $opt)) {
225 0         0 $success = 0;
226 0         0 next ELEM;
227             }
228             }
229              
230             } elsif ($argv->[$i] =~ /\A-(.*)/) {
231              
232 4         12 my $str = $1;
233             SHORT_OPT:
234 4         18 while ($str =~ s/(.)//) {
235 7         15 my $used_name = $1;
236 7         16 my ($opt, $is_neg) = $code_find_opt->($1, 'short');
237 7 50       20 next SHORT_OPT unless defined $opt;
238              
239 7         13 my $spec = $parsed_spec{$opt};
240             # check whether option requires an argument
241 7 100 0     38 if ($spec->{type} ||
      33        
      66        
242             $spec->{opttype} &&
243             (length($str) || ($i+1 < @$argv && $argv->[$i+1] !~ /\A-/))) {
244 4 100       13 if (length $str) {
245             # argument is taken from $str
246 2 50       6 if ($code_set_val->($is_neg, $opt, $str)) {
247 2         8 next ELEM;
248             } else {
249 0         0 $success = 0;
250 0         0 next SHORT_OPT;
251             }
252             } else {
253 2 100       11 if ($i+1 >= @$argv) {
254             # we are the last element
255 1         41 warn "Option $used_name requires an argument\n";
256 1         5 $success = 0;
257 1         4 last ELEM;
258             }
259             # take the next element as argument
260 1 50 33     7 if ($spec->{type} || $argv->[$i+1] !~ /\A-/) {
261 1         1 $i++;
262 1 50       4 unless ($code_set_val->($is_neg, $opt, $argv->[$i])) {
263 0         0 $success = 0;
264 0         0 next ELEM;
265             }
266             }
267             }
268             } else {
269 3 50       19 unless ($code_set_val->($is_neg, $opt)) {
270 0         0 $success = 0;
271 0         0 next SHORT_OPT;
272             }
273             }
274             }
275              
276             } else { # argument
277              
278 2         5 push @remaining, $argv->[$i];
279 2         5 next;
280              
281             }
282             }
283              
284             RETURN:
285 39         101 splice @$argv, 0, ~~@$argv, @remaining; # replace with remaining elements
286 39         934 return $success;
287             }
288              
289             sub GetOptions {
290 0     0 0 0 GetOptionsFromArray(\@ARGV, @_);
291             }
292              
293             # IFBUILT
294             sub parse_getopt_long_opt_spec {
295 55     55 0 76 my $optspec = shift;
296 55 50       163 return {is_arg=>1, dash_prefix=>'', opts=>[]}
297             if $optspec eq '<>';
298 55 50       691 $optspec =~ qr/\A
299             (?P-{0,2})
300             (?P[A-Za-z0-9_][A-Za-z0-9_-]*)
301             (?P (?: \| (?:[^:|!+=:-][^:|!+=:]*) )*)?
302             (?:
303             (?P!) |
304             (?P\+) |
305             (?:
306             =
307             (?P[siof])
308             (?P|[%@])?
309             (?:
310             \{
311             (?: (?P\d+), )?
312             (?P\d+)
313             \}
314             )?
315             ) |
316             (?:
317             :
318             (?P[siof])
319             (?P|[%@])?
320             ) |
321             (?:
322             :
323             (?P-?\d+)
324             (?P|[%@])?
325             ) |
326             (?:
327             :
328             (?P\+)
329             (?P|[%@])?
330             )
331             )?
332             \z/x
333             or return;
334 1     1   77533 my %res = %+;
  1         506  
  1         265  
  55         1018  
335              
336 55 50       247 if (defined $res{optnum}) {
337 0         0 $res{type} = 'i';
338             }
339              
340 55 100       129 if ($res{aliases}) {
341 8         10 my @als;
342 8         26 for my $al (split /\|/, $res{aliases}) {
343 16 100       32 next unless length $al;
344 8 50       17 next if $al eq $res{name};
345 8 50       20 next if grep {$_ eq $al} @als;
  0         0  
346 8         20 push @als, $al;
347             }
348 8         24 $res{opts} = [$res{name}, @als];
349             } else {
350 47         115 $res{opts} = [$res{name}];
351             }
352 55         100 delete $res{name};
353 55         115 delete $res{aliases};
354              
355 55 100       113 $res{is_neg} = 1 if $res{is_neg};
356 55 100       105 $res{is_inc} = 1 if $res{is_inc};
357              
358 55         158 \%res;
359             }
360              
361             # END IFBUILT
362             # IFUNBUILT
363             # require Getopt::Long::Util; *parse_getopt_long_opt_spec = \&Getopt::Long::Util::parse_getopt_long_opt_spec;
364             # END IFUNBUILT
365              
366             package Getopt::Long::Less::Callback;
367              
368             sub new {
369 34     34   56 my $class = shift;
370 34         101 bless {@_}, $class;
371             }
372              
373             sub name {
374 33     33   145 shift->{name};
375             }
376              
377             1;
378             # ABSTRACT: Like Getopt::Long, but with less features
379              
380             __END__