File Coverage

blib/lib/Getopt/Again.pm
Criterion Covered Total %
statement 212 217 97.7
branch 68 80 85.0
condition 36 54 66.6
subroutine 35 37 94.5
pod 6 9 66.6
total 357 397 89.9


line stmt bran cond sub pod time code
1             package Getopt::Again;
2 1     1   38944 use strict;
  1         3  
  1         45  
3 1     1   5 use warnings;
  1         2  
  1         37  
4              
5 1     1   5 use Carp qw/croak/;
  1         7  
  1         95  
6 1     1   6 use Scalar::Util qw/reftype/;
  1         9  
  1         600  
7              
8             our $VERSION = '0.000003';
9              
10             my %DEFAULTS = (
11             ALL => {default => undef, list => 0, example => '', required => 0, process => undef},
12              
13             bool => {default => 0},
14             string => {example => ' "foo"'},
15             path => {example => ' "/foo/bar/"'},
16             file => {example => ' "/foo/bar.txt"'},
17             regex => {},
18             );
19              
20             my %VALID_FIELDS = (
21             type => 1,
22             list => 1,
23             default => 1,
24             example => 1,
25             process => 1,
26             description => 1,
27             split_on => 1,
28             name => 1,
29             regex => 1,
30             );
31              
32             sub import {
33 1     1   6559 my $class = shift;
34 1         3 my $caller = caller;
35              
36 1         5 my $meta = $class->new(@_);
37              
38             my %exports = (
39 2     2   1193 opt_meta => sub { $meta },
40              
41 1     1   8 opt_bools => sub { $meta->add($_, type => 'bool', default => 0) for @_ },
42 1     1   6 opt_lists => sub { $meta->add($_, type => 'string', list => 1) for @_ },
43              
44 1     1   4 opt_params => sub { $meta->add($_, type => 'string') for @_ },
45 1     1   5 opt_paths => sub { $meta->add($_, type => 'path') for @_ },
46 1     1   4 opt_files => sub { $meta->add($_, type => 'file') for @_ },
47              
48 8     8   21 opt_add => sub { $meta->add(@_) },
49 3     3   841 opt_usage => sub { $meta->usage_string(@_) },
50 3     3   18 opt_help => sub { $meta->help_string(@_) },
51 3     3   5663 opt_parse => sub { $meta->parse(@_) },
52              
53 0     0   0 opt_use_help => sub { $meta->help(@_) },
54 1         33 );
55              
56 1         5 for my $name (keys %exports) {
57 1     1   6 no strict 'refs';
  1         2  
  1         2565  
58 11         19 *{"$caller\::$name"} = $exports{$name};
  11         752  
59             }
60             }
61              
62             sub new {
63 1     1 0 3 my $class = shift;
64 1         3 my %params = @_;
65              
66 1         3 my $self = bless {}, $class;
67              
68 1         5 for my $i (keys %params) {
69 1 50       15 croak "Not an option '$i'" unless $self->can($i);
70 1         4 $self->$i($params{$i});
71             }
72              
73 1         3 return $self;
74             }
75              
76             sub _named_params {
77 45     45   46 my $self = shift;
78 45   100     100 $self->{_named_params} ||= {};
79 45         226 return $self->{_named_params};
80             }
81              
82             sub _pattern_params {
83 8     8   32 my $self = shift;
84 8   100     25 $self->{_pattern_params} ||= [];
85 8         24 return $self->{_pattern_params};
86             }
87              
88             sub _all_params {
89 23     23   28 my $self = shift;
90 23   100     64 $self->{_all_params} ||= [];
91 23         57 return $self->{_all_params};
92             }
93              
94             sub _clear_opt_cache {
95 19     19   25 my $self = shift;
96 19         35 delete $self->{opt_cache};
97 19         27 return;
98             }
99              
100             sub _gen_opt_cache {
101 1     1   2 my $self = shift;
102              
103 1         2 my %seen;
104             my %opts;
105 1         3 for my $config (@{$self->_all_params}) {
  1         3  
106 19         46 my ($f) = split '', $config->{name};
107 19 100       81 if ($seen{$f}++) {
108 7         14 delete $opts{$f}; # More than 1
109             }
110             else {
111 12         27 $opts{$f} = $config;
112             }
113             }
114              
115 1         5 return {%opts, %{$self->_named_params}};
  1         5  
116             }
117              
118             sub _reversed_opt_cache {
119 81     81   150 my $self = shift;
120              
121 81         113 my $out = {};
122 81   33     239 my $cache = $self->{opt_cache} ||= $self->_gen_opt_cache;
123 81         438 for my $key (keys %$cache) {
124 2349         7480 my $opt = $cache->{$key};
125 2349 100       5286 if ($key eq $opt->{name}) {
126 1539         2021 unshift @{$out->{$opt->{name}}} => $key;
  1539         5487  
127             }
128             else {
129 810         886 push @{$out->{$opt->{name}}} => $key;
  810         12420  
130             }
131             }
132              
133 81         412 return $out;
134             }
135              
136             sub opt_spec {
137 148     148 1 236 my $self = shift;
138 148         615 my ($opt) = @_;
139              
140 148   66     369 $self->{opt_cache} ||= $self->_gen_opt_cache;
141              
142 148         227 my $config = $self->{opt_cache}->{$opt};
143 148 100       552 return $config if $config;
144              
145 5         8 for my $config (@{$self->_pattern_params}) {
  5         10  
146 9 100       60 next unless $opt =~ $config->{regex};
147 5         15 return $config;
148             }
149              
150 0         0 return;
151             }
152              
153             sub add {
154 19     19 1 31 my $self = shift;
155 19         49 my ($name, @params) = @_;
156              
157 19 50 0     89 croak "name is required, and must not be a ref (got: " . ($name || '(UNDEF)') . ")"
      33        
158             unless defined $name && !ref $name;
159              
160 19         42 $self->_clear_opt_cache;
161              
162 19 100       81 my %params = @params > 1 ? (@params) : (process => $params[0]);
163 19         61 my $config = $self->process_opt(%params, name => $name);
164 19         47 my $alias = $config->{alias};
165              
166 19 50       62 croak "Alias must be a string, or an array of strings"
167             unless reftype $alias eq 'ARRAY';
168              
169 19         21 push @{$self->_all_params} => $config;
  19         40  
170              
171 19 100       51 if($config->{regex}) {
172 3         4 push @{$self->_pattern_params} => $config;
  3         17  
173             }
174              
175 19         33 for my $name (@$alias) {
176 22 50 33     45 croak "Conflict, option '$name' was already defined by " . $self->_named_params->{$name}->{name}
177             if $self->_named_params->{$name} && $self->_named_params->{$name} != $config;
178              
179 22         44 $self->_named_params->{$name} = $config;
180             }
181             }
182              
183             sub process_opt {
184 19     19 0 23 my $self = shift;
185 19         47 my %params = @_;
186              
187 19         31 my $type = delete $params{type};
188 19 50 33     52 $type ||= ($params{regex} || length($params{name}) > 1)
      66        
189             ? 'string'
190             : 'bool';
191              
192 19         30 my $process = delete $params{process};
193 19 50 33     85 croak "process must either be a coderef or a regexp (got: $process)"
      66        
194             if $process && !(ref $process && reftype($process) =~ m/^(REGEXP|CODE)$/);
195              
196 19   100     80 my $alias = delete $params{alias} || [];
197 19 100       45 $alias = [$alias] unless ref $alias;
198 19 50       62 croak "alias must be a string, or an array strings, got: $alias"
199             unless reftype $alias eq 'ARRAY';
200 19         83 unshift @$alias => $params{name};
201              
202 19         46 for my $field (keys %params) {
203 32 50       95 croak "'$field' is not a valid option field"
204             unless $VALID_FIELDS{$field};
205             }
206              
207             return {
208 19         59 %{$DEFAULTS{ALL}},
  19         184  
209 19         33 %{$DEFAULTS{$type}},
210             %params,
211             type => $type,
212             alias => $alias,
213             process => $process,
214             };
215             }
216              
217             sub parse {
218 3     3 1 6 my $self = shift;
219 3         14 my @in = @_;
220              
221 3         6 my (%out, @out);
222              
223 3         6 my $no_parse = 0;
224 3         14 while(my $item = shift @in) {
225 29 100       64 if ($item eq '--') {
226 1         2 $no_parse = 1;
227 1         3 next;
228             }
229              
230 28 100 100     220 if ($item =~ m/^(-{1,2})([^=]+)(?:=(.*))?$/ && !$no_parse) {
231 24         74 my ($dash, $arg, $val) = ($1, $2, $3);
232 24         27 $dash = length($dash);
233              
234 24 100       60 if($val) {
235 3         8 $val =~ s/^'(.+)'$/$1/;
236 3         12 $val =~ s/^"(.+)"$/$1/;
237             }
238              
239 24         25 my @args;
240 24 100       48 if ($dash > 1) {
241 22         65 @args = ([$arg, $val]);
242             }
243             else {
244 2         12 @args = split '', $arg;
245 2         5 push @args => [ pop(@args), $val ];
246             }
247              
248 24         41 for my $set (@args) {
249 24         77 $self->_register($set, \@in, \%out);
250             }
251             }
252             else {
253 4         13 push @out => $item;
254             }
255             }
256              
257 3         11 $self->_populate(\%out);
258 3         14 $self->_process(\%out);
259              
260 2         23 return (\%out, \@out);
261             }
262              
263             sub _register {
264 24     24   122 my $self = shift;
265 24         41 my ($set, $in, $out) = @_;
266 24         29 my ($name, $val);
267              
268 24 50       57 if (ref $set) {
269 24         62 ($name, $val) = @$set;
270             }
271             else {
272 0         0 $name = $set;
273             }
274              
275 24         57 my $spec = $self->opt_spec($name);
276 24 50       54 croak "Unknown option: '$name'" unless $spec;
277              
278 24 100       1077 if ($spec->{type} eq 'bool') {
279 4 100       41 $out->{$spec->{name}} = !$spec->{default} ? 1 : 0;
280             }
281             else {
282 20 100 100     176 if ( $spec->{type} eq 'regex' && $name =~ $spec->{regex}) {
    100          
283 5   66     20 $val = $1 || $';
284             }
285             elsif (!defined $val) {
286 12         25 $val = shift @$in;
287             }
288              
289 20 100       48 if ($spec->{list}) {
290 9   100     41 $out->{$spec->{name}} ||= [];
291 9         12 push @{$out->{$spec->{name}}} => $val;
  9         74  
292             }
293             else {
294 11         93 $out->{$spec->{name}} = $val;
295             }
296             }
297             }
298              
299             sub _populate {
300 3     3   7 my $self = shift;
301 3         6 my ($out) = @_;
302              
303 3         4 for my $opt (@{$self->_all_params}) {
  3         9  
304 57 100       665 next if defined $out->{$opt->{name}};
305 41         101 $out->{$opt->{name}} = $opt->{default};
306 41 100 50     138 $out->{$opt->{name}} ||= [] if $opt->{list};
307             }
308             }
309              
310             sub _process {
311 3     3   7 my $self = shift;
312 3         5 my ($out) = @_;
313              
314 3         321 for my $opt (keys %$out) {
315 47         145 my $spec = $self->opt_spec($opt);
316 47 100       163 next unless defined $out->{$opt};
317              
318             # Split if requested
319 30 100 100     94 $out->{$opt} = [ map { split $spec->{split_on}, $_ } @{$out->{$opt}} ]
  2         38  
  2         5  
320             if $spec->{list} && defined $spec->{split_on};
321              
322 30 100       82 if ($spec->{process}) {
323 8         33 my $type = reftype $spec->{process};
324 8 100       16 if ($type eq 'CODE') {
325 6         12 local $_ = $out->{$opt};
326 6         38 $out->{$opt} = $spec->{process}->($out->{$opt});
327             }
328             else {
329 2 50       11 my $items = $spec->{list} ? $out->{$opt} : [ $out->{$opt} ];
330 2         7 for my $item (@$items) {
331 2 100       19 next if $item =~ $spec->{process};
332 1         16 die "Invalid value for '$opt', got: '$item'\n";
333             }
334             }
335             }
336             }
337             }
338              
339             sub usage_string {
340 4     4 1 7 my $self = shift;
341 4 100       15 ($self->{usage_string}) = @_ if @_;
342 4   100     17 $self->{usage_string} ||= "[-short_flags] [--long_flag] [--option '...'] [--option=...] arg1, arg2";
343 4         100 return "$0 $self->{usage_string}";
344             }
345              
346             sub opt_help_string {
347 77     77 0 99 my $self = shift;
348 77         95 my ($name) = @_;
349              
350 77         167 my $names = $self->_reversed_opt_cache;
351              
352 77   50     293 my $opt = $self->opt_spec($name) || die "Invalid option: $name";
353 77         216 my $out = " $name ($opt->{type})\n";
354 77         97 for my $alias (@{$names->{$name}}) {
  77         1297  
355 118 100       484 $out .= " " . (length($alias) > 1 ? '--' : '-') . $alias . $opt->{example} . "\n";
356             }
357 77 100       208 if ($opt->{regex}) {
358 12         43 $out .= " --$opt->{regex}\n"
359             }
360 77 100       19575 $out .= $opt->{description} . "\n" if $opt->{description};
361 77         97 $out .= "\n";
362              
363 77         2260 return $out;
364             }
365              
366             sub help_string {
367 4     4 1 10 my $self = shift;
368 4         15 my $names = $self->_reversed_opt_cache;
369              
370 4         219 print "Options:\n";
371              
372 4         10 my $out = "";
373 4         133 for my $name (sort keys %$names) {
374 76         243 $out .= $self->opt_help_string($name);
375             }
376              
377 4         4726 return $out;
378             }
379              
380             # Inject a help parameter that exits 0
381             sub enable_help {
382 1     1 1 2 my $self = shift;
383 1         2 my ($bool) = @_;
384 1 50       4 return unless $bool;
385              
386             $self->add('help' => (
387             type => 'bool',
388             alias => 'h',
389             process => sub {
390 3 100   3   20 return unless $_;
391 1         12 print $self->usage_string;
392 1         266 print "\n";
393 1         6 print $self->help_string;
394 1         18 print "\n";
395 1         11 $self->_exit(0);
396             },
397 1         14 ));
398             }
399              
400 0     0     sub _exit { shift; exit $_[0] };
  0            
401              
402             1;
403              
404             __END__