File Coverage

blib/lib/opts.pm
Criterion Covered Total %
statement 92 97 94.8
branch 40 46 86.9
condition 8 13 61.5
subroutine 11 11 100.0
pod 0 2 0.0
total 151 169 89.3


line stmt bran cond sub pod time code
1             package opts;
2 13     13   27086 use strict;
  13         25  
  13         401  
3 13     13   71 use warnings;
  13         21  
  13         511  
4             our $VERSION = '0.07';
5 13     13   66 use Exporter 'import';
  13         31  
  13         514  
6 13     13   11503 use PadWalker qw/var_name/;
  13         13405  
  13         1144  
7 13     13   18395 use Getopt::Long;
  13         212256  
  13         91  
8 13     13   2478 use Carp ();
  13         29  
  13         16298  
9              
10             our @EXPORT = qw/opts/;
11              
12             our $TYPE_CONSTRAINT = {
13             'Bool' => '!',
14             'Str' => '=s',
15             'Int' => '=i',
16             'Num' => '=f',
17             'ArrayRef' => '=s@',
18             'HashRef' => '=s%',
19             };
20              
21             my %is_invocant = map{ $_ => undef } qw($self $class);
22              
23             my $coerce_type_map = {
24             Multiple => 'ArrayRef',
25             };
26              
27             my $coerce_generater = {
28             Multiple => sub { [ split(qr{,}, join(q{,}, @{ $_[0] })) ] },
29             };
30              
31             sub opts {
32             {
33 29     29 0 16159 package DB;
34             # call of caller in DB package sets @DB::args,
35             # which requires list context, but does not use return values
36 29         298 () = caller(1);
37             }
38              
39             # method call
40 29 100 50     287 if(exists $is_invocant{ var_name(1, \$_[0]) || '' }){
41 6         40 $_[0] = shift @DB::args;
42 6         10 shift;
43             # XXX: should we provide ways to check the type of invocant?
44             }
45              
46             # track our coderef defaults
47 29         52 my %default_subs;
48              
49 29         87 my @options = ('help|h!' => \my $help);
50 29         56 my %requireds;
51             my %generaters;
52 0         0 my $usage;
53 0         0 my @option_help;
54 29         139 for(my $i = 0; $i < @_; $i++){
55              
56 37 50       191 (my $name = var_name(1, \$_[$i]))
57             or Carp::croak('usage: opts my $var => TYPE, ...');
58              
59 37         197 $name =~ s/^\$//;
60              
61 37         132 my $rule = _compile_rule($_[$i+1]);
62              
63 37 100       153 if ($name =~ /_/) {
64              
65             # Name has underscores in it, which is annoying for command line
66             # arguments. Swap them and create / add to alias.
67 5         20 (my $newname = $name) =~ s/_/-/g;
68              
69 5 100       18 $rule->{alias}
70             = $rule->{alias}
71             ? $name . q{|} . $rule->{alias}
72             : $name
73             ;
74              
75 5         8 $name = $newname;
76             }
77              
78 37 100       119 if (exists $rule->{default}) {
79              
80 2 100 66     15 if (ref $rule->{default} && ref $rule->{default} eq 'CODE') {
81 1         4 $default_subs{$i} = $rule->{default};
82 1         2 $_[$i] = undef;
83             }
84             else {
85 1         4 $_[$i] = $rule->{default};
86             }
87             }
88              
89 37 100       101 if (exists $rule->{required}) {
90 3         7 $requireds{$name} = $i;
91             }
92              
93            
94 37   100     192 my $comment = $rule->{comment} || "";
95 37         120 my @names = (substr($name,0,1), $name);
96 37 100       124 push @names, $rule->{alias} if $rule->{alias};
97 37 100       72 my $optname = join(', ', map { (length($_) > 1 ? '--' : '-').$_ } @names);
  82         360  
98 37         133 push @option_help, [ $optname, ucfirst($comment) ];
99              
100 37 100       179 if (my $gen = $coerce_generater->{$rule->{isa}}) {
101 4         18 $generaters{$name} = { idx => $i, gen => $gen };
102             }
103              
104 37 100       152 $name .= '|' . $rule->{alias} if $rule->{alias};
105 37         108 push @options, $name . $rule->{type} => \$_[$i];
106              
107 37 100       257 $i++ if defined $_[$i+1]; # discard type info
108             }
109            
110             {
111 29         67 my $err;
  29         70  
112 29     2   260 local $SIG{__WARN__} = sub { $err = shift };
  2         631  
113 29 100       226 GetOptions(@options) or Carp::croak($err);
114 27 100       10258 if ($help) {
115 1         6 $usage = "usage: $0 [options]\n\n";
116              
117 1 50       4 if (@option_help) {
118 1         1849 require Text::Table;
119 1         19436 push @option_help, ['-h, --help', 'This help message'];
120 1         4 my $sep = \' ';
121 1         3 $usage .= "options:\n";
122 1         6 $usage .= Text::Table->new($sep, '', $sep, '')->load(@option_help)->stringify."\n";
123             }
124              
125 1         4826 die $usage;
126             }
127              
128 1 50       8 do { $_[$_] = $default_subs{$_}->() unless defined $_[$_] }
129 26         82 for keys %default_subs;
130              
131 26         119 while ( my ($name, $idx) = each %requireds ) {
132 3 100       14 unless (defined($_[$idx])) {
133 1         26 Carp::croak("missing mandatory parameter named '\$$name'");
134             }
135             }
136 25         254 while ( my ($name, $val) = each %generaters ) {
137 4         15 $_[$val->{idx}] = $val->{gen}->($_[$val->{idx}]);
138             }
139             }
140             }
141              
142             sub coerce ($$&) { ## no critic
143 1     1 0 17 my ($isa, $type, $generater) = @_;
144              
145 1         6 $coerce_type_map->{$isa} = $type;
146 1         4 $coerce_generater->{$isa} = $generater;
147             }
148              
149             sub _compile_rule {
150 37     37   72 my ($rule) = @_;
151 37 100       141 if (!defined $rule) {
    100          
152 4         15 return +{ type => "!", isa => 'Bool' };
153             }
154             elsif (!ref $rule) { # single, non-ref parameter is a type name
155 16 50 66     46 my $tc = _get_type_constraint($rule) ||
156             _get_type_constraint($coerce_type_map->{$rule}) or
157             Carp::croak("cannot find type constraint '$rule'");
158 16         76 return +{ type => $tc, isa => $rule };
159             }
160             else {
161 17         27 my %ret;
162 17 50       39 if ($rule->{isa}) {
163 17         168 $ret{isa} = $rule->{isa};
164 17 50 33     49 my $tc = _get_type_constraint($rule->{isa}) ||
165             _get_type_constraint($coerce_type_map->{$rule->{isa}}) or
166 0         0 Carp::croak("cannot find type constraint '@{[$rule->{isa}]}'");
167 17         44 $ret{type} = $tc;
168             } else {
169 0         0 $ret{isa} = 'Bool';
170 0         0 $ret{type} = "!";
171             }
172 17         45 for my $key (qw(alias default required comment)) {
173 68 100       163 if (exists $rule->{$key}) {
174 12         31 $ret{$key} = $rule->{$key};
175             }
176             }
177 17         54 return \%ret;
178             }
179             }
180              
181             sub _get_type_constraint {
182 37     37   62 my $isa = shift;
183              
184 37         219 $TYPE_CONSTRAINT->{$isa};
185             }
186              
187             1;
188             __END__
189              
190             =head1 NAME
191              
192             opts - (DEPRECATED) simple command line option parser
193              
194             =head1 DESCRIPTION
195              
196             B<THIS MODULE WAS DEPRECATED. USE Smart::Options INSTEAD.>
197              
198             =head1 AUTHOR
199              
200             Kan Fushihara E<lt>kan.fushihara at gmail.comE<gt>
201              
202             =head1 SEE ALSO
203              
204             L<Smart::Options>, L<Smart::Args>, L<Getopt::Long>
205              
206             =cut