File Coverage

blib/lib/Getopt/Again.pm
Criterion Covered Total %
statement 216 221 97.7
branch 70 82 85.3
condition 35 54 64.8
subroutine 35 37 94.5
pod 6 9 66.6
total 362 403 89.8


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