File Coverage

blib/lib/Getopt/Long/EvenLess.pm
Criterion Covered Total %
statement 106 114 92.9
branch 51 64 79.6
condition 6 6 100.0
subroutine 5 6 83.3
pod 3 3 100.0
total 171 193 88.6


line stmt bran cond sub pod time code
1             package Getopt::Long::EvenLess;
2              
3             our $DATE = '2019-02-02'; # DATE
4             our $VERSION = '0.112'; # VERSION
5              
6             # IFUNBUILT
7             # # use strict 'subs', 'vars';
8             # # use warnings;
9             # END IFUNBUILT
10              
11             our @EXPORT = qw(GetOptions);
12             our @EXPORT_OK = qw(GetOptionsFromArray);
13              
14             my $config = {
15             pass_through => 0,
16             auto_abbrev => 1,
17             };
18              
19             sub Configure {
20 12     12 1 37101 my $old_config = { %$config };
21              
22 12 100       49 if (ref($_[0]) eq 'HASH') {
23 6         10 for (keys %{$_[0]}) {
  6         23  
24 12         25 $config->{$_} = $_[0]{$_};
25             }
26             } else {
27 6         34 for (@_) {
28 6 100       22 if ($_ eq 'pass_through') {
    50          
    50          
    50          
    0          
29 5         12 $config->{pass_through} = 1;
30             } elsif ($_ eq 'no_pass_through') {
31 0         0 $config->{pass_through} = 0;
32             } elsif ($_ eq 'auto_abbrev') {
33 0         0 $config->{auto_abbrev} = 1;
34             } elsif ($_ eq 'no_auto_abbrev') {
35 1         3 $config->{auto_abbrev} = 0;
36             } elsif ($_ =~ /\A(no_ignore_case|no_getopt_compat|gnu_compat|bundling|permute)\z/) {
37             # ignore, already behaves that way
38             } else {
39 0         0 die "Unknown configuration '$_'";
40             }
41             }
42             }
43 12         29 $old_config;
44             }
45              
46             sub import {
47 1     1   8 my $pkg = shift;
48 1         2 my $caller = caller;
49 1 50       5 my @imp = @_ ? @_ : @EXPORT;
50 1         2 for my $imp (@imp) {
51 2 50       4 if (grep {$_ eq $imp} (@EXPORT, @EXPORT_OK)) {
  4         11  
52 2         3 *{"$caller\::$imp"} = \&{$imp};
  2         37  
  2         19  
53             } else {
54 0         0 die "$imp is not exported by ".__PACKAGE__;
55             }
56             }
57             }
58              
59             sub GetOptionsFromArray {
60 25     25 1 121707 my ($argv, %spec) = @_;
61              
62 25         52 my $success = 1;
63              
64 25         43 my %spec_by_opt_name;
65 25         65 for (keys %spec) {
66 37         53 my $orig = $_;
67 37         173 s/=[fios][@%]?\z//;
68 37         92 s/\|.+//;
69 37         117 $spec_by_opt_name{$_} = $orig;
70             }
71              
72             my $code_find_opt = sub {
73 36     36   74 my ($wanted, $short_mode) = @_;
74 36         55 my @candidates;
75             OPT_SPEC:
76 36         81 for my $spec (keys %spec) {
77 51         163 $spec =~ s/=[fios][@%]?\z//;
78 51         133 my @opts = split /\|/, $spec;
79 51         92 for my $o (@opts) {
80 63 100 100     195 next if $short_mode && length($o) > 1;
81 52 100 100     232 if ($o eq $wanted) {
    100          
82             # perfect match, we immediately go with this one
83 22         65 @candidates = ($opts[0]);
84 22         64 last OPT_SPEC;
85             } elsif ($config->{auto_abbrev} && index($o, $wanted) == 0) {
86             # prefix match, collect candidates first
87 8         18 push @candidates, $opts[0];
88 8         20 next OPT_SPEC;
89             }
90             }
91             }
92 36 100       103 if (!@candidates) {
    100          
93 9 100       22 unless ($config->{pass_through}) {
94 5         197 warn "Unknown option: $wanted\n";
95 5         21 $success = 0;
96             }
97 9         25 return undef; # means unknown
98             } elsif (@candidates > 1) {
99 3 100       9 unless ($config->{pass_through}) {
100 1         40 warn "Option $wanted is ambiguous (" .
101             join(", ", @candidates) . ")\n";
102 1         5 $success = 0;
103             }
104 3         10 return ''; # means ambiguous
105             }
106 24         60 return $candidates[0];
107 25         136 };
108              
109             my $code_set_val = sub {
110 22     22   38 my $name = shift;
111              
112 22         78 my $spec_key = $spec_by_opt_name{$name};
113 22         34 my $destination = $spec{$spec_key};
114              
115 22 100       111 $destination->({name=>$name}, @_ ? $_[0] : 1);
116 25         102 };
117              
118 25         45 my $i = -1;
119 25         31 my @remaining;
120             ELEM:
121 25         73 while (++$i < @$argv) {
122 41 100       309 if ($argv->[$i] eq '--') {
    100          
    100          
123              
124 2         7 push @remaining, @{$argv}[$i+1 .. @$argv-1];
  2         5  
125 2         4 last ELEM;
126              
127             } elsif ($argv->[$i] =~ /\A--(.+?)(?:=(.*))?\z/) {
128              
129 28         92 my ($used_name, $val_in_opt) = ($1, $2);
130 28         64 my $opt = $code_find_opt->($used_name);
131 28 100       84 if (!defined($opt)) {
    100          
132             # unknown option
133 8         20 push @remaining, $argv->[$i];
134 8         28 next ELEM;
135             } elsif (!length($opt)) {
136 3         8 push @remaining, $argv->[$i];
137 3         10 next ELEM; # ambiguous
138             }
139              
140 17         34 my $spec = $spec_by_opt_name{$opt};
141             # check whether option requires an argument
142 17 50       63 if ($spec =~ /=[fios][@%]?\z/) {
143 17 100       35 if (defined $val_in_opt) {
144             # argument is taken after =
145 3         6 $code_set_val->($opt, $val_in_opt);
146             } else {
147 14 100       31 if ($i+1 >= @$argv) {
148             # we are the last element
149 1         39 warn "Option $used_name requires an argument\n";
150 1         5 $success = 0;
151 1         3 last ELEM;
152             }
153 13         18 $i++;
154 13         28 $code_set_val->($opt, $argv->[$i]);
155             }
156             } else {
157 0         0 $code_set_val->($opt);
158             }
159              
160             } elsif ($argv->[$i] =~ /\A-(.*)/) {
161              
162 5         16 my $str = $1;
163 5         6 my $remaining_pushed;
164             SHORT_OPT:
165 5         25 while ($str =~ s/(.)//) {
166 8         38 my $used_name = $1;
167 8         13 my $short_opt = $1;
168 8         20 my $opt = $code_find_opt->($short_opt, 'short');
169 8 100       25 if (!defined $opt) {
    50          
170             # unknown short option
171 1 50       5 push @remaining, "-" unless $remaining_pushed++;
172 1         3 $remaining[-1] .= $short_opt;
173 1         5 next SHORT_OPT;
174             } elsif (!length $opt) {
175             # ambiguous short option
176 0 0       0 push @remaining, "-" unless $remaining_pushed++;
177 0         0 $remaining[-1] .= $short_opt;
178             }
179              
180 7         13 my $spec = $spec_by_opt_name{$opt};
181             # check whether option requires an argument
182 7 100       21 if ($spec =~ /=[fios][@%]?\z/) {
183 4 100       11 if (length $str) {
184             # argument is taken from $str
185 2         6 $code_set_val->($opt, $str);
186 2         14 next ELEM;
187             } else {
188 2 100       8 if ($i+1 >= @$argv) {
189             # we are the last element
190 1 50       4 unless ($config->{pass_through}) {
191 1         40 warn "Option $used_name requires an argument\n";
192 1         6 $success = 0;
193             }
194 1         4 last ELEM;
195             }
196             # take the next element as argument
197 1         3 $i++;
198 1         6 $code_set_val->($opt, $argv->[$i]);
199             }
200             } else {
201 3         8 $code_set_val->($opt);
202             }
203             }
204              
205             } else { # argument
206              
207 6         12 push @remaining, $argv->[$i];
208 6         13 next;
209              
210             }
211             }
212              
213             RETURN:
214 25         155 splice @$argv, 0, ~~@$argv, @remaining; # replace with remaining elements
215 25         307 return $success;
216             }
217              
218             sub GetOptions {
219 0     0 1   GetOptionsFromArray(\@ARGV, @_);
220             }
221              
222             1;
223             # ABSTRACT: Like Getopt::Long::Less, but with even less features
224              
225             __END__
226              
227             =pod
228              
229             =encoding UTF-8
230              
231             =head1 NAME
232              
233             Getopt::Long::EvenLess - Like Getopt::Long::Less, but with even less features
234              
235             =head1 VERSION
236              
237             This document describes version 0.112 of Getopt::Long::EvenLess (from Perl distribution Getopt-Long-EvenLess), released on 2019-02-02.
238              
239             =head1 DESCRIPTION
240              
241             This module (GLEL for short) is a reimplementation of L<Getopt::Long> (GL for
242             short), but with much less features. It's an even more stripped down version of
243             L<Getopt::Long::Less> (GLL for short) and is perhaps less convenient to use for
244             day-to-day scripting work.
245              
246             The main goal is minimum amount of code and small startup overhead. This module
247             is an experiment of how little code I can use to support the stuffs I usually do
248             with GL.
249              
250             Compared to GL and GLL, it:
251              
252             =over
253              
254             =item * has minimum Configure() support
255              
256             Only these configurations are known: pass_through, no_pass_through (default).
257              
258             GLEL is equivalent to GL in this mode: bundling, no_ignore_case,
259             no_getopt_compat, gnu_compat, permute.
260              
261             No support for configuring via import options e.g.:
262              
263             use Getopt::Long qw(:config pass_through);
264              
265             =item * does not support increment (C<foo+>)
266              
267             =item * no type checking (C<foo=i>, C<foo=f>, C<foo=s> all accept any string)
268              
269             =item * does not support optional value (C<foo:s>), only no value (C<foo>) or required value (C<foo=s>)
270              
271             =item * does not support desttypes (C<foo=s@>)
272              
273             =item * does not support destination other than coderef (so no C<< "foo=s" => \$scalar >>, C<< "foo=s" => \@ary >>, no C<< "foo=s" => \%hash >>, only C<< "foo=s" => sub { ... } >>)
274              
275             Also, in coderef destination, code will get a simple hash instead of a
276             "callback" object as its first argument.
277              
278             =item * does not support hashref as first argument
279              
280             =item * does not support bool/negation (no C<foo!>, so you have to declare both C<foo> and C<no-foo> manually)
281              
282             =back
283              
284             The result?
285              
286             B<Amount of code>. GLEL 0.07 is about 175 lines of code, while GL is about 1500.
287             Sure, if you I<really> want to be minimalistic, you can use this single line of
288             code to get options:
289              
290             @ARGV = grep { /^--([^=]+)(=(.*))?/ ? ($opts{$1} = $2 ? $3 : 1, 0) : 1 } @ARGV;
291              
292             and you're already able to extract C<--flag> or C<--opt=val> from C<@ARGV> but
293             you also lose a lot of stuffs like autoabbreviation, C<--opt val> syntax support
294             syntax (which is more common, but requires you specify an option spec), custom
295             destination, etc.
296              
297             =head1 FUNCTIONS
298              
299             =head2 Configure(@configs | \%config) => hash
300              
301             Set configuration. Known configurations:
302              
303             =over
304              
305             =item * pass_through
306              
307             Ignore errors (unknown/ambiguous option) and still make GetOptions return true.
308              
309             =item * no_pass_through (default)
310              
311             =item * no_auto_abbrev
312              
313             =item * auto_abbrev (default)
314              
315             =item * no_ignore_case
316              
317             =item * no_getopt_compat
318              
319             =item * gnu_compat
320              
321             =item * bundling
322              
323             =item * permute
324              
325             =back
326              
327             Return old configuration data. To restore old configuration data you can pass it
328             back to C<Configure()>, e.g.:
329              
330             my $orig_conf = Getopt::Long::EvenLess::Configure("pass_through");
331             # ...
332             Getopt::Long::EvenLess::Configure($orig_conf);
333              
334             =head2 GetOptions(%spec) => bool
335              
336             Shortcut for:
337              
338             GetOptionsFromArray(\@ARGV, %spec)
339              
340             =head2 GetOptionsFromArray(\@ary, %spec) => bool
341              
342             Get (and strip) options from C<@ary>. Return true on success or false on failure
343             (unknown option, etc).
344              
345             =head1 HOMEPAGE
346              
347             Please visit the project's homepage at L<https://metacpan.org/release/Getopt-Long-EvenLess>.
348              
349             =head1 SOURCE
350              
351             Source repository is at L<https://github.com/perlancar/perl-Getopt-Long-EvenLess>.
352              
353             =head1 BUGS
354              
355             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Getopt-Long-EvenLess>
356              
357             When submitting a bug or request, please include a test-file or a
358             patch to an existing test-file that illustrates the bug or desired
359             feature.
360              
361             =head1 SEE ALSO
362              
363             L<Getopt::Long>
364              
365             L<Getopt::Long::Less>
366              
367             If you want I<more> features intead of less, try L<Getopt::Long::More>.
368              
369             Benchmarks in L<Bencher::Scenario::GetoptModules>
370              
371             =head1 AUTHOR
372              
373             perlancar <perlancar@cpan.org>
374              
375             =head1 COPYRIGHT AND LICENSE
376              
377             This software is copyright (c) 2019, 2017, 2016, 2015 by perlancar@cpan.org.
378              
379             This is free software; you can redistribute it and/or modify it under
380             the same terms as the Perl 5 programming language system itself.
381              
382             =cut