File Coverage

blib/lib/Getopt/Modular.pm
Criterion Covered Total %
statement 282 297 94.9
branch 120 142 84.5
condition 44 65 67.6
subroutine 37 40 92.5
pod 12 12 100.0
total 495 556 89.0


line stmt bran cond sub pod time code
1             package Getopt::Modular;
2             $Getopt::Modular::VERSION = '0.13';
3             #ABSTRACT: Modular access to Getopt::Long
4              
5 10     10   492955 use warnings;
  10         23  
  10         353  
6 10     10   54 use strict;
  10         17  
  10         320  
7              
8 10     10   12920 use Getopt::Long;
  10         154180  
  10         66  
9 10     10   12352 use List::MoreUtils qw(any uniq);
  10         13101  
  10         2026  
10 10     10   76 use Scalar::Util qw(reftype looks_like_number);
  10         20  
  10         1256  
11             use Exception::Class
12 10         151 'Getopt::Modular::Exception' => {
13             description => 'Exception in commandline parsing/handling',
14             fields => [ qw(type option value warning valid expected) ],
15             },
16             'Getopt::Modular::Internal' => {
17             description => 'Internal Exception in commandline parsing/handling',
18             fields => [ qw(type option) ]
19 10     10   10318 };
  10         111534  
20 10     10   8371 use Carp;
  10         22  
  10         34612  
21              
22              
23             sub import
24             {
25 10     10   126 my $class = shift;
26 10         66 while (@_)
27             {
28 10         24 my $opt = shift;
29 10 100       40 if ($opt eq '-namespace')
    50          
30             {
31 9   50     40 my $ns = shift || die "No namespace given";
32              
33             # I could do this without eval, but I'm too lazy today.
34 9 50       849 eval qq{
35             package $ns;
36             \@${ns}::ISA = List::MoreUtils::uniq('Getopt::Modular', \@${ns}::ISA);
37             1; } or die $@;
38             }
39             elsif ($opt eq '-getOpt')
40             {
41 1   50     5 my $package = caller || 'main';
42              
43             # again, too lazy right now.
44 1 50   9   89 eval qq{
  9         181  
45             package $package;
46             sub getOpt { Getopt::Modular->getOpt(\@_) }
47             1; } or die $@;
48             }
49             }
50             }
51              
52              
53             my $global;
54             sub new {
55 9     9 1 42 my $class = shift;
56 9         35 my $self = {};
57 9         25 bless $self, $class;
58              
59             # do we have a global one yet?
60 9   33     131 $global ||= $self;
61              
62 9 50   0   201 if (any {'global' eq lc} @_)
  0         0  
63             {
64 0         0 $global = $self;
65 0         0 @_ = grep { 'global' ne lc } @_;
  0         0  
66             }
67              
68 9         100 $self->setBoolHelp(qw(off on));
69              
70 9         68 $self->init(@_);
71              
72 9         33 $self;
73             }
74              
75             sub _self_or_global
76             {
77 989     989   1328 my $underscore = shift;
78 989         1269 my $self = $underscore->[0];
79              
80             # is it an object? use it.
81 989 100       1127 eval { ref $self && $self->isa(__PACKAGE__) } && return shift @$underscore;
  989 100       6850  
82              
83             # passed in via class method? skip it.
84 126 50 33     193 eval { not ref $self && $self->isa(__PACKAGE__) } && shift @$underscore;
  126         522  
85              
86             # have global? use it.
87 126 100       466 $global ? $global :
88             # otherwise, create new.
89             $self->new();
90             }
91              
92             sub _accepts_opt
93             {
94 83     83   157 my $self = _self_or_global(\@_);
95 83         165 my $opt = shift;
96 83         303 return exists $self->{accept_opts}{$opt};
97             }
98              
99             sub _opt
100             {
101 561     561   1082 my $self = _self_or_global(\@_);
102 561         959 my $opt = shift;
103            
104 561 100       1116 if (@_)
105             {
106 39         91 $self->{accept_opts}{$opt} = shift;
107 39         169 return;
108             }
109              
110 522 50       1406 unless (exists $self->{accept_opts}{$opt})
111             {
112 0         0 Getopt::Modular::Internal->throw(
113             type => 'unknown-option',
114             message => "Unknown option: $opt",
115             option => $opt,
116             );
117             }
118 522         2052 return $self->{accept_opts}{$opt};
119             }
120              
121              
122             sub init
123             {
124 9     9 1 17 my $self = shift;
125 9 50       65 $self->setMode(@_) if @_;
126 9         18 1;
127             }
128              
129              
130             my %_known_modes = map { $_ => 1 } qw(
131             strict
132             );
133              
134             sub setMode
135             {
136 5     5 1 2188 my $self = _self_or_global(\@_);
137              
138 5         16 foreach my $mode (@_)
139             {
140 5 100       18 if ($_known_modes{$mode})
141             {
142 3         24 $self->{mode}{$mode}++;
143             }
144             else
145             {
146 2         57 croak "Unknown mode: $@";
147             }
148             }
149             }
150              
151              
152             sub setBoolHelp
153             {
154 9     9 1 234 my $self = _self_or_global(\@_);
155 9         72 $self->{bool_strings} = [ @_[0,1] ];
156             }
157              
158              
159             sub acceptParam
160             {
161 17     17 1 9459 my $self = _self_or_global(\@_);
162 17         64 while (@_)
163             {
164 42         68 my $param = shift;
165 42         51 my $opts = shift;
166              
167 42 100       154 my $aliases = exists $opts->{aliases} ? ref $opts->{aliases} ? $opts->{aliases} : [ $opts->{aliases} ] : [];
    100          
168              
169 42 100       136 if ($param =~ /\|/)
170             {
171 10         48 ($param, my @aliases) = split /\|/, $param;
172 10         30 unshift @$aliases, @aliases;
173             }
174              
175             # if any of the aliases have pipes, split them up. Needed to provide
176             # the help screen.
177             $opts->{aliases} = [
178             uniq
179 17         141 eval {
180 42 100       151 my $o = $self->_accepts_opt($param) ? $self->_opt($param) : {};
181 42 100       54 @{$o->{aliases} || []};
  42         349  
182             },
183 42         60 map { split /\|/, $_ } @$aliases
184             ];
185              
186             # check if this flag already exists (other than as main name)
187 42         80 for (@{$opts->{aliases}})
  42         100  
188             {
189 24 100 100     105 if (exists $self->{all_opts}{$_} and
190             $self->{all_opts}{$_} ne $param)
191             {
192 1         203 croak "$_ already used by $self->{all_opts}{$_}";
193             }
194             # save this as the owner
195 23         79 $self->{all_opts}{$_} = $param;
196             }
197              
198 41         81 delete $self->{unacceptable}{$param};
199 41 100       92 if ($self->_accepts_opt($param))
200             {
201 2         6 my $opt = $self->_opt($param);
202 2         7 @{$self->_opt($param)}{keys %$opts} = values %$opts;
  2         5  
203             }
204             else
205             {
206             # set some defaults ...
207 39   100     108 $opts->{spec} ||= '';
208              
209 39         138 $self->_opt($param, $opts);
210             }
211             }
212             }
213              
214              
215             sub unacceptParam
216             {
217 2     2 1 1081 my $self = _self_or_global(\@_);
218 2         8 for my $param (@_)
219             {
220 2         8 $self->{unacceptable}{$param} = 1;
221 2         16 my @x =
222 2         5 delete @{$self->{all_opts}}{@{$self->{accept_opts}{$param}{aliases}}};
  2         8  
223             }
224             }
225              
226              
227             sub parseArgs
228             {
229 5     5 1 177 my $self = _self_or_global(\@_);
230              
231             # first, gather up for the call to Getopt::Long.
232 5         10 my %opts;
233 5         10 my $accept = $self->{accept_opts};
234 5         9 my $unaccept = $self->{unacceptable};
235 15         36 my @params = map {
236 15 50       18 my $param = join '|', $_, @{$accept->{$_}{aliases}};
  16         69  
237 15   50     69 $param . ($accept->{$_}{spec} || '');
238             } grep {
239             # skip unaccepted parameters
240 5         22 $unaccept and not $unaccept->{$_}
241             } keys %$accept;
242              
243             # parse them
244 5         10 my $warnings;
245 5         7 my $success = do {
246 5     1   38 local $SIG{__WARN__} = sub { $warnings .= "@_";};
  1         394  
247 5         30 Getopt::Long::Configure("bundling");
248 5         212 GetOptions(\%opts, @params);
249             };
250 5 100       1914 if (not $success)
251             {
252 1         19 Getopt::Modular::Exception->throw(
253             message => "Bad command-line: $warnings",
254             type => 'getopt-long-failure',
255             warning => $warnings,
256             );
257             }
258              
259             # now validate everything that was passed in, and save it.
260 4         12 for my $opt (keys %$accept)
261             {
262 10 100       40 if (exists $opts{$opt})
    100          
263             {
264 6         28 $self->setOpt($opt, $opts{$opt});
265             }
266             # if it's mandatory, get it - that will call the default and
267             # set it.
268             elsif ($accept->{$opt}{mandatory})
269             {
270             # setting via default.
271 1         6 $self->getOpt($opt);
272             }
273             }
274              
275             # if passed in a hash ref to populate, fill it.
276 3 100 66     31 if (@_ && ref $_[0] eq 'HASH')
277             {
278 1         5 my $opts = shift;
279 1         3 for my $opt (keys %{$self->{accept_opts}})
  1         6  
280             {
281 2         6 $opts->{$opt} = $self->getOpt($opt);
282             }
283             }
284             }
285              
286              
287             sub getOpt
288             {
289 65     65 1 3715 my $self = _self_or_global(\@_);
290 65   33     232 my $opt = shift || Getopt::Modular::Exception->throw(
291             message => 'No option given?',
292             type => 'dev-error',
293             );
294              
295 65 100       206 if (not exists $self->{accept_opts}{$opt})
296             {
297 5 100       21 if ($self->{mode}{strict})
298             {
299 3         73 Getopt::Modular::Exception->throw(
300             message => "No such option: $opt",
301             type => 'no-such-option',
302             option => $opt,
303             value => undef,
304             );
305             }
306             }
307              
308             # If we don't have it yet, check if there's a default.
309 62 100 100     407 if (not exists $self->{options}{$opt} and
      100        
310             exists $self->{accept_opts}{$opt} and
311             exists $self->{accept_opts}{$opt}{default})
312             {
313 15         47 my @default = $self->{accept_opts}{$opt}{default};
314 15 100 66     90 if (ref $default[0] and ref $default[0] eq 'CODE')
315             {
316 7         31 @default = $default[0]->();
317             }
318 15         116 $self->setOpt($opt, @default);
319             }
320              
321             # should have one now ... check and return
322 62 100       180 if (exists $self->{options}{$opt})
323             {
324 50 100       110 if (wantarray)
325             {
326             return
327 8         65 ref $self->{options}{$opt} eq 'ARRAY' ? @{$self->{options}{$opt}} :
  0         0  
328 8 0       36 ref $self->{options}{$opt} eq 'HASH' ? %{$self->{options}{$opt}} :
    50          
329             $self->{options}{$opt};
330             }
331 42         326 return $self->{options}{$opt}
332             }
333              
334 12         52 return;
335             }
336              
337             sub _getType
338             {
339 92     92   297 my $self = _self_or_global(\@_);
340 92         166 my $opt = shift;
341              
342 92 100       273 unless (exists $self->_opt($opt)->{_GMTYPE})
343             {
344 32         73 my $type = $self->_opt($opt)->{spec};
345 32         97 $self->_opt($opt)->{_GMTYPE} = ''; #scalar
346 32 100       155 if ($type =~ /\@/)
    100          
347             {
348 6         27 $self->_opt($opt)->{_GMTYPE} = 'ARRAY';
349             }
350             elsif ($type =~ /\%/)
351             {
352 3         10 $self->_opt($opt)->{_GMTYPE} = 'HASH';
353             }
354             }
355 92         207 $self->_opt($opt)->{_GMTYPE}
356             }
357              
358             sub _bool_val
359 8     8   12 {
360             # technically, perl allows anything to be boolean.
361             #my ($opt,$val) = @_;
362             }
363              
364             sub _int_val
365             {
366 24     24   43 my ($opt,$val) = @_;
367 24 100       188 if ($val !~ /^[-+]?\d+$/)
368             {
369 4         56 Getopt::Modular::Exception->throw(
370             message => "Trying to set '$opt' (an integer-only parameter) to '$val'",
371             type => 'set-int-failure',
372             option => $opt,
373             value => $val
374             );
375             }
376             }
377              
378             sub _real_val
379             {
380 17     17   27 my ($opt,$val) = @_;
381              
382 17 100       100 unless (looks_like_number $val)
383             {
384 5         47 Getopt::Modular::Exception->throw(
385             message => "Trying to set '$opt' (a real-number parameter) to '$val'",
386             type => 'set-real-failure',
387             option => $opt,
388             value => $val
389             );
390             }
391             }
392              
393             my %_valtypes = (
394             '!' => { val => \&_bool_val },
395             '+' => { val => \&_int_val },
396             's' => { val => sub {} },
397             'i' => { val => \&_int_val },
398             'o' => { val => \&_int_val },
399             'f' => { val => \&_real_val },
400             );
401              
402             sub _setOpt
403             {
404 67     67   147 my $self = _self_or_global(\@_);
405 67         119 my $opt = shift;
406 67         91 my $val = shift;
407              
408             # check known types before passing on to user-specified validation.
409              
410 67         156 my $type = $self->_opt($opt)->{spec};
411 67 100 66     447 if ($type eq '' || $type eq '!') # boolean
412             {
413 8         35 _bool_val($opt,$val);
414             # extra information should not be stored in a boolean.
415 8         15 $val = !!$val;
416             }
417             else
418             {
419 59         239 for (split //, $type)
420             {
421 138 100       371 if (my $info = $_valtypes{$_})
422             {
423 59 100       197 if ($type =~ /\@/)
    100          
424             {
425 15         55 $info->{val}->($opt,$_) for @$val;
426             }
427             elsif ($type =~ /\%/)
428             {
429 7         38 $info->{val}->($opt,$_) for values %$val;
430             }
431             else
432             {
433 37         99 $info->{val}->($opt,$val);
434             }
435             }
436             }
437             }
438              
439 58 100       163 if ($self->_opt($opt)->{validate})
440             {
441 26         52 local $_ = $val;
442 26 100       63 unless ($self->_opt($opt)->{validate}->())
443             {
444 7 100       93 if (ref $val)
445             {
446 4 100       21 $val = join ',', @$val if ref $val eq 'ARRAY';
447 4 100       22 $val = join ',', map { "$_=$val->{$_}" } sort keys %$val if ref $val eq 'HASH';
  1         6  
448             }
449             Getopt::Modular::Exception->throw(
450 7         72 message => "'$val' is an invalid value for $opt",
451             type => 'validate-failure',
452             option => $opt,
453             value => $val,
454             );
455             }
456             }
457              
458 50 100       325 if (my $valid = $self->_opt($opt)->{valid_values})
459             {
460 1 50       4 if (ref $valid eq 'CODE')
461             {
462 0         0 my @valid = $valid->();
463 0         0 $valid = \@valid;
464 0         0 $self->_opt($opt)->{valid_values} = $valid; # cache for next time.
465             }
466              
467 1 50       10 if (ref $valid eq 'ARRAY')
468             {
469 0 0   0   0 unless (any { $_ eq $val } @$valid)
  0         0  
470             {
471 0         0 Getopt::Modular::Exception->throw(
472             message => "'$val' is an invalid value for $opt",
473             type => 'validate-failure',
474             option => $opt,
475             value => $val,
476             valid => $valid,
477             );
478             }
479             }
480             else
481             {
482 1         15 Getopt::Modular::Exception->throw(
483             message => "'valid_values requires either an array ref or a code ref to generate the list of valid values.",
484             type => 'valid-values-error',
485             option => $opt,
486             );
487             }
488             }
489              
490 49         373 $self->{options}{$opt} = $val;
491             }
492              
493              
494             sub setOpt
495             {
496 70     70 1 25907 my $self = _self_or_global(\@_);
497 70         116 my $opt = shift;
498 70         93 my $val = do {
499 70 100       200 if (ref $_[0])
500             {
501 9 100 100     32 Getopt::Modular::Exception->throw(
      50        
      100        
502             type => 'wrong-type',
503             message => "Wrong type of data for $opt. Expected: " .
504             ($self->_getType($opt) || 'SCALAR') .
505             " got: " . (reftype $_[0] || 'SCALAR'),
506             expected => ($self->_getType($opt) || 'SCALAR'),
507             option => $opt,
508             value => $_[0],
509             )
510             unless $self->_getType($opt) eq reftype $_[0];
511              
512             # if it's a reference, pass it in unchanged.
513 6         18 shift;
514             }
515             else
516             {
517             # scalars get passed in, but hashes and arrays need to
518             # be referencised.
519              
520 61 100       246 ! $self->_getType($opt) ? shift :
    100          
521             $self->_getType($opt) eq 'HASH' ? { @_ } : [ @_ ];
522             }
523             };
524              
525 67         535 $self->_setOpt($opt, $val);
526             }
527              
528              
529             sub getHelpRaw
530             {
531 7     7 1 43 my $self = _self_or_global(\@_);
532              
533             # get the list of parameters ...
534 7         37 my $accept = $self->{accept_opts};
535 7         26 my $unaccept = $self->{unacceptable};
536 22 50       162 my @params = sort grep {
537             # skip unaccepted parameters
538 7         39 $unaccept and not $unaccept->{$_}
539             } keys %$accept;
540              
541             # start figuring it out.
542 7         16 my @raw;
543 7         20 for my $param (@params)
544             {
545 22         40 my %opt;
546              
547 22         40 my $param_info = $accept->{$param};
548 22 50       31 my @keys = ($param, @{$param_info->{aliases}||[]});
  22         138  
549              
550             # booleans get the "no" version.
551 22 100       108 if ($param_info->{spec} =~ /!/)
552             {
553 14 100       29 @keys = map { length > 1 ? ($_, "no$_") : $_ } @keys;
  28         127  
554             }
555              
556             # anything with more than one letter gets a double-dash.
557 22 100       44 @keys = map { length > 1 ? "--$_" : "-$_" } @keys;
  57         210  
558 22         61 $opt{param} = \@keys;
559              
560 22 50       82 $opt{help} = ref $param_info->{help} ?
561             $param_info->{help}->() : $param_info->{help};
562              
563             # determine default (or set value)
564 22         28 my $default;
565 22         30 eval {
566 22         75 $opt{default} = $self->getOpt($param);
567              
568 22         488 my $type = $self->_opt($param)->{spec};
569 22 100 66     117 if ($type eq '' || $type eq '!') # boolean
570             {
571 14   66     36 my $bools = ( $self->_opt($param)->{help_bool} or $self->{bool_strings} );
572              
573 14 50       64 $opt{default} = $bools->[$opt{default} ? 1 : 0];
574             }
575             };
576              
577             # determine valid values.
578 22         24 eval {
579 22         51 $opt{valid_values} = $self->_opt($param)->{valid_values};
580              
581 10     10   95 no warnings;
  10         29  
  10         3488  
582             # if it's not a code ref, the eval will exit, but we'll already
583             # have what we want anyway.
584 22         181 $opt{valid_values} = [ $opt{valid_values}->() ];
585             };
586              
587             # is it hidden? It's still part of the raw output.
588 22 100       104 $opt{hidden} = $param_info->{hidden} if exists $param_info->{hidden};
589              
590 22         73 push @raw, \%opt;
591             }
592 7         33 return @raw;
593             }
594              
595              
596             sub getHelp
597             {
598 3     3 1 52579 my $self = _self_or_global(\@_);
599 3         20 my @raw = grep { not $_->{hidden} } $self->getHelpRaw;
  10         27  
600 3   100     22 my $cbs = shift || {};
601              
602 3         1797 require Text::Table;
603              
604 3         21036 my $tb = Text::Table->new();
605 3         482 for my $param (@raw)
606             {
607 9         1968 my $opt = join ",\n ", @{$param->{param}};
  9         33  
608 9         21 my $txt = $param->{help};
609 10     10   62 no warnings 'uninitialized';
  10         19  
  10         5034  
610              
611 9 50 100 5   90 $txt .= "\n " . ($cbs->{current_value} || sub { "Current value: [". shift(). "]" })->($param->{default}) if exists $param->{default};
  5         20  
612              
613 9 100       58 if ($param->{valid_values})
614             {
615             # if it's a code ref, de-ref it. If not, ignore the exception.
616 3         8 eval { $param->{valid_values} = [ $param->{valid_values}->() ] };
  3         41  
617 3   100 1   8 $txt .= "\n " . ($cbs->{valid_values} || sub { "Valid values: [". join(',', @_). "]" })->(@{$param->{valid_values}});
  3         58  
  1         7  
618             }
619              
620 9         45 $tb->add($opt, $txt);
621             }
622 3         332 $tb;
623             }
624              
625              
626             sub getHelpWrap
627             {
628 2     2 1 26033 my $self = _self_or_global(\@_);
629 2 100 66     22 my $width = (@_ && not ref $_[0]) ? shift : 80;
630 2   50     22 my $cbs = shift || {};
631 2         14 my @raw = grep { not $_->{hidden} } $self->getHelpRaw;
  6         17  
632              
633 2         67 require Text::Table;
634              
635             my $wrap = eval {
636             require Text::WrapI18N;
637             sub {
638 6     6   10 local $Text::WrapI18N::columns = shift;
639 6         12 local $Text::WrapI18N::unexpand;
640              
641 6         26 Text::WrapI18N::wrap('', '', @_);
642             };
643 2   33     5 } || do {
644             require Text::Wrap;
645             sub {
646 0     0   0 local $Text::Wrap::columns = shift;
647 0         0 local $Text::Wrap::unexpand;
648              
649 0         0 Text::Wrap::wrap('', '', @_);
650             }
651             };
652              
653 2         18 my $tb = Text::Table->new();
654             my $load_data = sub {
655 12     12   19 my $tb = shift;
656 12         18 my $param = shift;
657              
658 12         15 my $opt = join ",\n ", @{$param->{param}};
  12         49  
659 12         28 my $txt = shift;
660              
661 12         17 my $available = shift;
662              
663 10     10   192 no warnings 'uninitialized';
  10         22  
  10         3880  
664              
665 12 50 50     152 $txt .= "\n " . ($cbs->{current_value} || sub { "Current value: [". shift(). "]" })->($param->{default}) if exists $param->{default};
666 12 100 50     55 $txt .= "\n " . ($cbs->{valid_values} || sub { "Valid values: [". join(',', @_). "]" })->(@{$param->{valid_values}}) if $param->{valid_values};
  4         33  
667              
668             # wrap all lines
669 12 100       66 $txt = $wrap->($available, $txt) if $available;
670              
671 12         21045 $tb->add($opt, $txt);
672 2         246 };
673              
674 2         6 for my $param (@raw)
675             {
676 6         1280 $load_data->($tb, $param, $param->{help});
677             }
678              
679 2 50       224 if ($tb->width > $width)
680             {
681             # rebuild, wrapped.
682 2         17806 my @colrange = $tb->colrange(0);
683 2         307 my $available = $width - $colrange[1] - 1; # 1 for extra space between columns
684              
685 2         10 $tb->clear();
686 2         66 for my $param (@raw)
687             {
688 6         738 $load_data->($tb, $param, $param->{help}, $available);
689             }
690             }
691              
692             # if the current value or valid values are a block of text too long, we don't want all
693             # lines to be too long, so clobber the extra spaces that Text::Table puts in at the end.
694 2         265 (my $txt = "".$tb) =~ s/\s+$//msg;
695 2         22908 $txt .= "\n";
696 2         164 $txt;
697             }
698              
699              
700             1; # End of Getopt::Modular
701              
702             __END__