File Coverage

blib/lib/Smart/Options.pm
Criterion Covered Total %
statement 215 224 95.9
branch 104 114 91.2
condition 31 51 60.7
subroutine 29 29 100.0
pod 15 15 100.0
total 394 433 90.9


line stmt bran cond sub pod time code
1             package Smart::Options;
2 20     20   479040 use strict;
  20         43  
  20         6879  
3 20     20   119 use warnings;
  20         41  
  20         995  
4 20     20   561 use 5.010001;
  20         81  
  20         2277  
5             our $VERSION = '0.056';
6              
7             require Exporter;
8             our @ISA = qw(Exporter);
9             our @EXPORT = qw(argv);
10              
11 20     20   27103 use List::MoreUtils qw(uniq);
  20         31043  
  20         15082  
12 20     20   25079 use Text::Table;
  20         793693  
  20         1214  
13 20     20   30757 use File::Slurp;
  20         633512  
  20         102279  
14              
15             sub new {
16 59     59 1 92192     my $pkg = shift;
17 59         152     my %opt = @_;
18              
19 59         816     my $self = bless {
20                     alias => {},
21                     default => {},
22                     boolean => {},
23                     demand => {},
24                     usage => "Usage: $0",
25                     describe => {},
26                     type => {},
27                     subcmd => {},
28                     coerce => {},
29                 }, $pkg;
30              
31 59 100 100     660     if ($opt{add_help} // 1) {
32 56         526         $self->options(h => {
33                             alias => 'help',
34                             describe => 'show help',
35                         });
36 56         174         $self->{add_help} = 1;
37                 }
38              
39 59         1031     $self;
40             }
41              
42             sub argv {
43 12     12 1 54587     Smart::Options->new->parse(@_);
44             }
45              
46             sub _set {
47 222     222   297     my $self = shift;
48 222         468     my $param = shift;
49              
50 222         645     my %args = @_;
51 222         446     for my $option (keys %args) {
52 223         1075         $self->{$param}->{$option} = $args{$option};
53                 }
54              
55 222         936     $self;
56             }
57              
58 86     86 1 278 sub alias { shift->_set('alias', @_) }
59 6     6 1 27 sub default { shift->_set('default', @_) }
60 59     59 1 426 sub describe { shift->_set('describe', @_) }
61 67     67 1 216 sub type { shift->_set('type', @_) }
62 4     4 1 10 sub subcmd { shift->_set('subcmd', @_) }
63              
64             sub _set_flag {
65 12     12   19     my $self = shift;
66 12         18     my $param = shift;
67              
68 12         22     for my $option (@_) {
69 20         63         $self->{$param}->{$option} = 1;
70                 }
71              
72 12         46     $self;
73             }
74              
75 3     3 1 12 sub boolean { shift->_set_flag('boolean', @_) }
76 9     9 1 33 sub demand { shift->_set_flag('demand', @_) }
77              
78             sub options {
79 57     57 1 110     my $self = shift;
80              
81 57         179     my %args = @_;
82 57         269     while (my($opt, $setting) = each %args) {
83 57         245         for my $key (keys %$setting) {
84 115         512             $self->$key($opt, $setting->{$key});
85                     }
86                 }
87              
88 57         126     $self;
89             }
90              
91             sub coerce {
92 32     32 1 79     my ($self, $isa, $type, $generater) = @_;
93              
94 32         249     $self->{coerce}->{$isa} = {
95                     type => $type,
96                     generater => $generater,
97                 };
98             }
99              
100 4     4 1 27 sub usage { $_[0]->{usage} = $_[1]; $_[0] }
  4         14  
101              
102             sub _get_opt_desc {
103 13     13   25     my ($self, $option) = @_;
104              
105 13         30     my @opts = ($option);
106 13         20     while ( my($opt, $val) = each %{$self->{alias}} ) {
  30         96  
107 17 100       52         push @opts, $opt if $val eq $option;
108                 }
109              
110 13 100       28     return join(', ', map { (length($_) == 1 ? '-' : '--') . $_ } sort @opts);
  20         116  
111             }
112              
113             sub _get_describe {
114 13     13   21     my ($self, $option) = @_;
115              
116 13         30     my $desc = $self->{describe}->{$option};
117 13         18     while ( my($opt, $val) = each %{$self->{alias}} ) {
  30         100  
118 17 100 100     76         $desc ||= $self->{describe}->{$opt} if $val eq $option;
119                 }
120              
121 13 100       99     return $desc ? ucfirst($desc) : '';
122             }
123              
124             sub _get_default {
125 14     14   25     my ($self, $option) = @_;
126              
127 14         27     my $value = $self->{default}->{$option};
128 14         20     while ( my($opt, $val) = each %{$self->{alias}} ) {
  32         98  
129 18 100 66     84         $value ||= $self->{default}->{$opt} if $val eq $option;
130                 }
131              
132 14         81     $value;
133             }
134              
135             sub help {
136 6     6 1 18     my $self = shift;
137              
138 6         13     my $alias = $self->{alias};
139 6         11     my $demand = $self->{demand};
140 6         13     my $describe = $self->{describe};
141 6         11     my $default = $self->{default};
142 6         14     my $boolean = $self->{boolean};
143 6         19     my $help = $self->{usage} . "\n";
144              
145 6 50 100     42     if (scalar(keys %$demand) or scalar(keys %$describe)) {
146 6         12         my @opts;
147 6         128         for my $opt (uniq sort keys %$demand, keys %$describe, keys %$default, keys %$boolean, values %$alias) {
148 17 100       53             next if $alias->{$opt};
149 13 100       40             push @opts, [
    100          
    100          
150                             $self->_get_opt_desc($opt),
151                             $self->_get_describe($opt),
152                             $boolean->{$opt} ? '[boolean]' : '',
153                             $demand->{$opt} ? '[required]' : '',
154 1         4                 $self->_get_default($opt) ? "[default: @{[$self->_get_default($opt)]}]" : '',
155                         ];
156                     }
157              
158 6         21         my $sep = \' ';
159 6         38         $help .= "\nOptions:\n";
160 6         62         $help .= Text::Table->new( $sep, '', $sep, '', $sep, '', $sep, '', $sep, '' )
161                                         ->load(@opts)->stringify . "\n";
162 6 100       111674         if (keys %{$self->{subcmd}}) {
  6         73  
163 1         4             $help .= "Implemented commands are:\n";
164 1         2             $help .= " " . join(', ', sort keys %{$self->{subcmd}}) . "\n\n";
  1         14  
165                     }
166                 }
167              
168 6         1178     $help;
169             }
170              
171             sub showHelp {
172 4     4 1 8     my ($self, $fh) = @_;
173 4   33     29     $fh //= *STDERR;
174              
175 4         16     print $fh $self->help;
176              
177             }
178              
179             sub _set_v2a {
180 80     80   190     my ($argv, $key, $value, $k) = @_;
181              
182 80 100       331     if ($k) {
    100          
183 5   100     33         $argv->{$key} //= {};
184 5         22         _set_v2a($argv->{$key}, $k, $value);
185                 }
186                 elsif (exists $argv->{$key}) {
187 8 100       28         if (ref($argv->{$key})) {
188 2         6             push @{$argv->{$key}}, $value;
  2         11  
189                     }
190                     else {
191 6         33             $argv->{$key} = [ $argv->{$key}, $value ];
192                     }
193                 }
194                 else {
195 67         561         $argv->{$key} = $value;
196                 }
197             }
198              
199             sub _get_real_name {
200 169     169   334     my ($self, $opt) = @_;
201              
202 169         713     while (my $name = $self->{alias}->{$opt}) {
203 12         40         $opt = $name;
204                 }
205 169         702     return $opt;
206             }
207              
208             sub _load_config {
209 2     2   4     my ($self, $argv, $file) = @_;
210              
211 2         12     for my $line (read_file($file)) {
212 12 100       398         next if $line =~ /^\[/; # section
213 8 100       19         next if $line =~ /^;/; # comment
214 6 50       17         next if $line !~ /=/; # bad format;
215              
216 6         12         chomp($line);
217 6 50       33         if ($line =~ /^(.+?[^\\])=(.*)$/) {
218 6         29             $argv->{$1} = $2;
219                     }
220                 }
221             }
222              
223             sub parse {
224 61     61 1 2887     my $self = shift;
225 61 100       309     push @_, @ARGV unless @_;
226              
227 61         111     my $argv = {};
228 61         728     my @args;
229 61         135     my $boolean = $self->{boolean};
230              
231 61         93     my $key;
232                 my $nest_key;
233 61         89     my $stop = 0;
234 61         136     for my $arg (@_) {
235 144 100       372         if ($stop) {
236 20         27             push @args, $arg;
237 20         25             next;
238                     }
239 124 100       916         if ($arg =~ /^--((?:\w|-|\.)+)=(.+)$/) {
    100          
    100          
240 49         342             my ($opt, $k) = split(/\./, $1);
241 49         166             my $option = $self->_get_real_name($opt);
242 49 100       122             if ($k) {
243 4         13                 _set_v2a($argv, $option, $2, $k);
244                         } else {
245 45         265                 _set_v2a($argv, $option, $2);
246                         }
247                     }
248                     elsif ($arg =~ /^(-(\w(?:\w|-|\.)*)|--((?:\w|-|\.)+))$/) {
249 37 100       85             if ($key) {
250 2         5                 $argv->{$key} = 1;
251                         }
252 37   66     693             my $opt = $2 // $3;
253 37         130             ($opt, my $k) = split(/\./, $opt);
254 37         117             my $option = $self->_get_real_name($opt);
255 37 100       97             if ($boolean->{$option}) {
256 3 50       9                 if ($k) {
257 0   0     0                     $argv->{$option} //= {};
258 0         0                     $argv->{$option}->{$k} = 1;
259                             } else {
260 3         9                     $argv->{$option} = 1;
261                             }
262                         }
263                         else {
264 34         56                 $key = $option;
265 34         77                 $nest_key = $k;
266                         }
267                     }
268                     elsif ($arg =~ /^--$/) {
269             # stop parsing
270 4         6             $stop = 1;
271 4         6             next;
272                     }
273                     else {
274 34 100       60             if ($key) {
275 26 100       47                 if ($nest_key) {
276 1         3                     _set_v2a($argv, $key, $arg, $nest_key);
277                             } else {
278 25         89                     _set_v2a($argv, $key, $arg);
279                             }
280             # reset
281 26         56                 $key = $nest_key = undef;
282                         }
283                         else {
284 8 100 100     30                 if (!scalar(@args) && keys %{$self->{subcmd}}) {
  4         24  
285 1 50       5                     if ( $self->{subcmd}->{$arg} ) {
286 1         3                         $argv->{command} = $arg;
287 1         2                         $stop = 1;
288 1         3                         next;
289                                 }
290                                 else {
291 0         0                         die "sub command '$arg' not defined.";
292                                 }
293                             }
294              
295 7         21                 push @args, $arg;
296                         }
297                     }
298                 }
299 61 100       183     if ($key) {
300 6 50       16         if ($nest_key) {
301 0   0     0             $argv->{$key} //= {};
302 0         0             $argv->{$key}->{$nest_key} = 1;
303                     } else {
304 6         16             $argv->{$key} = 1;
305                     }
306                 }
307              
308 61 100 100     467     if (my $parser = $self->{subcmd}->{$argv->{command}||''}) {
309 1         11         $argv->{cmd_option} = $parser->parse(@args);
310                 } else {
311 60         148         $argv->{_} = \@args;
312                 }
313              
314 61         108     while (my ($key, $val) = each %{$self->{default}}) {
  67         339  
315 6         21         my $opt = $self->_get_real_name($key);
316 6 100 66     36         if (ref($val) && ref($val) eq 'CODE') {
317 1   33     7             $argv->{$opt} //= $val->();
318                     }
319                     else {
320 5   66     30             $argv->{$opt} //= $val;
321                     }
322                 }
323              
324 61         94     while (my ($key, $val) = each %{$self->{type}}) {
  134         656  
325 73 100       213         next if $val ne 'Config';
326 30 100 66     193         next if !($argv->{$key}) || !(-f $argv->{$key});
327 2         10         $self->_load_config($argv, delete $argv->{$key});
328                 }
329              
330 61         164     for my $key (keys %{$self->{demand}}) {
  61         728  
331 8         22         my $opt = $self->_get_real_name($key);
332 8 100       37         if (!$argv->{$opt}) {
333 3         14             $self->showHelp;
334 3         487             print STDERR "\nMissing required arguments: $opt\n";
335 3         96             die;
336                     }
337                 }
338              
339 58         198     for my $key (keys %{$self->{type}}) {
  58         263  
340 69         314         my $opt = $self->_get_real_name($key);
341 69         154         my $type = $self->{type}->{$key};
342 69 100       851         if (my $c = $self->{coerce}->{$type}) {
343 5         9             $type = $c->{type};
344 5         18             $argv->{$opt} = $c->{generater}->($argv->{$opt});
345                     }
346 69         130         my $check = 0;
347 69 50       417         if ($type eq 'Bool') {
    100          
    100          
    100          
    100          
    100          
348 0   0     0             $argv->{$opt} //= 0;
349 0 0       0             $check = ($argv->{$opt} =~ /^(0|1)$/) ? 1 : 0;
350                     } elsif ($type eq 'Str') {
351 3         4             $check = 1;
352                     } elsif ($type eq 'Int') {
353 19 100       51             if ($argv->{$opt}) {
354 18 100       107                 $check = ($argv->{$opt} =~ /^\-?\d+$/) ? 1 : 0;
355                         } else {
356 1         3                 $check = 1;
357                         }
358                     } elsif ($type eq 'Num') {
359 7 100       23             if ($argv->{$opt}) {
360 5 100       29                 $check = ($argv->{$opt} =~ /^\-?\d+(\.\d+)$/) ? 1 : 0;
361                         } else {
362 2         3                 $check = 1;
363                         }
364                     } elsif ($type eq 'ArrayRef') {
365 10   50     34             $argv->{$opt} //= [];
366 10 100       35             unless (ref($argv->{$opt})) {
367 2         7                 $argv->{$opt} = [$argv->{$opt}];
368                         }
369 10 100       30             $check = (ref($argv->{$opt}) eq 'ARRAY') ? 1 : 0;
370                     } elsif ($type eq 'HashRef') {
371 2   50     9             $argv->{$opt} //= {};
372 2 100       8             $check = (ref($argv->{$opt}) eq 'HASH') ? 1 : 0;
373                     } elsif ('Config') {
374 28 50 33     108             if ($argv->{$opt} && !(-f $argv->{$opt})) {
375 0         0                 die "cannot load config file '@{[$argv->{$opt}]}\n";
  0         0  
376                         }
377 28         46             $check = 1;
378                     } else {
379                         die "cannot find type constraint '$type'\n";
380                     }
381 69 100       595         unless ($check) {
382 6         11             die "Value '@{[$argv->{$opt}]}' invalid for option $opt($type)\n";
  6         168  
383                     }
384                 }
385              
386 52 100 66     217     if ($argv->{help} && $self->{add_help}) {
387 1         3         $self->showHelp;
388 1         51         die;
389                 }
390              
391 51         324     $argv;
392             }
393              
394              
395             1;
396             __END__
397            
398             =encoding utf8
399            
400             =head1 NAME
401            
402             Smart::Options - smart command line options processor
403            
404             =head1 SYNOPSIS
405            
406             use Smart::Options;
407            
408             my $argv = Smart::Options->new->argv;
409            
410             if ($argv->{rif} - 5 * $argv->{xup} > 7.138) {
411             say 'Buy more fiffiwobbles';
412             }
413             else {
414             say 'Sell the xupptumblers';
415             }
416            
417             # $ ./example.pl --rif=55 --xup=9.52
418             # Buy more fiffiwobbles
419             #
420             # $ ./example.pl --rif 12 --xup 8.1
421             # Sell the xupptumblers
422            
423             =head1 DESCRIPTION
424            
425             Smart::Options is a library for option parsing for people tired option parsing.
426             This module is analyzed as people interpret an option intuitively.
427            
428             =head1 METHOD
429            
430             =head2 new()
431            
432             Create a parser object.
433            
434             use Smart::Options;
435            
436             my $argv = Smart::Options->new->parse(qw(-x 10 -y 2));
437            
438             =head2 parse(@args)
439            
440             parse @args. return hashref of option values.
441             if @args is empty Smart::Options use @ARGV
442            
443             =head2 argv(@args)
444            
445             shortcut method. this method auto export.
446            
447             use Smart::Options;
448             say argv(qw(-x 10))->{x};
449            
450             is the same as
451            
452             use Smart::Options ();
453             Smart::Options->new->parse(qw(-x 10))->{x};
454            
455             =head2 alias($alias, $option)
456            
457             set alias for option. you can use "$option" field of argv.
458            
459             use Smart::Options;
460            
461             my $argv = Smart::Options->new->alias(f => 'file')->parse(qw(-f /etc/hosts));
462             $argv->{file} # => '/etc/hosts'
463            
464             =head2 default($option, $default_value)
465            
466             set default value for option.
467            
468             use Smart::Options;
469            
470             my $argv = Smart::Options->new->default(y => 5)->parse(qw(-x 10));
471             $argv->{x} + $argv->{y} # => 15
472            
473             =head2 describe($option, $msg)
474            
475             set option help message.
476            
477             use Smart::Options;
478             my $opt = Smart::Options->new()->alias(f => 'file')->describe('Load a file');
479             say $opt->help;
480            
481             # Usage: ./example.pl
482             #
483             # Options:
484             # -f, --file Load a file
485             #
486            
487             =head2 boolean($option, $option2, ...)
488            
489             interpret 'option' as a boolean.
490            
491             use Smart::Options;
492            
493             my $argv = Smart::Options->new->parse(qw(-x 11 -y 10));
494             $argv->{x} # => 11
495            
496             my $argv2 = Smart::Options->new->boolean('x')->parse(qw(-x 11 -y 10));
497             $argv2->{x} # => true (1)
498            
499             =head2 demand($option, $option2, ...)
500            
501             show usage (showHelp()) and exit if $option wasn't specified in args.
502            
503             use Smart::Options;
504             my $opt = Smart::Options->new()->alias(f => 'file')
505             ->demand('file')
506             ->describe('Load a file');
507             $opt->argv(); # => exit
508            
509             # Usage: ./example.pl
510             #
511             # Options:
512             # -f, --file Load a file [required]
513             #
514            
515             =head2 options($key => $settings, ...)
516            
517             use Smart::Options;
518             my $opt = Smart::Options->new()
519             ->options( f => { alias => 'file', default => '/etc/passwd' } );
520            
521             is the same as
522            
523             use Smart::Options;
524             my $opt = Smart::Options->new()
525             ->alias(f => 'file')
526             ->default(f => '/etc/passwd');
527            
528             =head2 type($option => $type)
529            
530             set type check for option value
531            
532             use Smart::Options;
533             my $opt = Smart::Options->new()->type(foo => 'Int');
534            
535             $opt->parse('--foo=bar') # => fail
536             $opt->parse('--foo=3.14') # => fail
537             $opt->parse('--foo=1') # => ok
538            
539             support type is here.
540            
541             Bool
542             Str
543             Int
544             Num
545             ArrayRef
546             HashRef
547             Config
548            
549             =head3 Config
550            
551             'Config' is special type.
552             The contents will be read into each option if a file name is specified as a Config type option.
553            
554             use Smart::Options;
555             my $opt = Smart::Options->new()->type(conf => 'Config');
556             $opt->parse(qw(--conf=.optrc));
557            
558             config file format is simple. see http://en.wikipedia.org/wiki/INI_file
559            
560             ; this is comment
561             [section]
562             key=value
563             key2=value2
564            
565             =head2 coerce( $newtype => $sourcetype, $generator )
566            
567             define new type and convert logic.
568            
569             use Smart::Options;
570             use Path::Class; # export 'file'
571             my $opt = Smart::Options->new()->coerce(File => 'Str', sub { file($_[0]) })
572             ->type(file => 'File');
573            
574             $opt->parse('--foo=/etc/passwd');
575             $argv->{file} # => Path::Class::File instance
576            
577             =head2 usage
578            
579             set a usage message to show which command to use. default is "Usage: $0".
580            
581             =head2 help
582            
583             return help message string
584            
585             =head2 showHelp($fh)
586            
587             print usage message. default output STDERR.
588            
589             =head2 subcmd($cmd => $parser)
590            
591             set a sub command. $parser is another Smart::Option object.
592            
593             use Smart::Options;
594             my $opt = Smart::Options->new()
595             ->subcmd(add => Smart::Options->new())
596             ->subcmd(minus => Smart::Options->new());
597            
598             =head1 DSL
599            
600             see also L<Smart::Options::Declare>
601            
602             =head1 PARSING TRICKS
603            
604             =head2 stop parsing
605            
606             use '--' to stop parsing.
607            
608             use Smart::Options;
609             use Data::Dumper;
610            
611             my $argv = argv(qw(-a 1 -b 2 -- -c 3 -d 4));
612             warn Dumper($argv);
613            
614             # $VAR1 = {
615             # 'a' => '1',
616             # 'b' => '2',
617             # '_' => [
618             # '-c',
619             # '3',
620             # '-d',
621             # '4'
622             # ]
623             # };
624            
625             =head2 negate fields
626            
627             '--no-key' set false to $key.
628            
629             use Smart::Options;
630             argv(qw(-a --no-b))->{b}; # => 0
631            
632             =head2 duplicates
633            
634             If set flag multiple times it will get arrayref.
635            
636             use Smart::Options;
637             argv(qw(-x 1 -x 2 -x 3))->{x}; # => [1, 2, 3]
638            
639             =head2 dot notation
640            
641             use Smart::Optuions;
642             argv(qw(--foo.x 1 --foo.y 2)); # => { foo => { x => 1, y => 2 } }
643            
644             =head1 AUTHOR
645            
646             Kan Fushihara E<lt>kan.fushihara@gmail.comE<gt>
647            
648             =head1 SEE ALSO
649            
650             https://github.com/substack/node-optimist
651            
652             L<GetOpt::Casual>, L<opts>, L<GetOpt::Compat::WithCmd>
653            
654             =head1 LICENSE
655            
656             Copyright (C) Kan Fushihara
657            
658             This library is free software; you can redistribute it and/or modify
659             it under the same terms as Perl itself.
660            
661             =cut
662