File Coverage

blib/lib/opts.pm
Criterion Covered Total %
statement 94 97 96.9
branch 40 46 86.9
condition 8 13 61.5
subroutine 11 11 100.0
pod 0 2 0.0
total 153 169 90.5


line stmt bran cond sub pod time code
1             package opts;
2 13     13   194367 use strict;
  13         25  
  13         536  
3 13     13   65 use warnings;
  13         29  
  13         1079  
4             our $VERSION = '0.08';
5 13     13   153 use Exporter 'import';
  13         28  
  13         563  
6 13     13   7874 use PadWalker qw/var_name/;
  13         12618  
  13         1123  
7 13     13   11079 use Getopt::Long;
  13         255149  
  13         92  
8 13     13   2602 use Carp ();
  13         27  
  13         20869  
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 3881288 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         264 () = caller(1);
37             }
38              
39             # method call
40 29 100 50     306 if(exists $is_invocant{ var_name(1, \$_[0]) || '' }){
41 6         9 $_[0] = shift @DB::args;
42 6         7 shift;
43             # XXX: should we provide ways to check the type of invocant?
44             }
45              
46             # track our coderef defaults
47 29         60 my %default_subs;
48              
49 29         110 my @options = ('help|h!' => \my $help);
50 29         155 my %requireds;
51             my %generaters;
52 29         0 my $usage;
53 29         0 my @option_help;
54 29         109 for(my $i = 0; $i < @_; $i++){
55              
56 37 50       183 (my $name = var_name(1, \$_[$i]))
57             or Carp::croak('usage: opts my $var => TYPE, ...');
58              
59 37         156 $name =~ s/^\$//;
60              
61 37         156 my $rule = _compile_rule($_[$i+1]);
62              
63 37 100       174 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         22 (my $newname = $name) =~ s/_/-/g;
68              
69             $rule->{alias}
70             = $rule->{alias}
71             ? $name . q{|} . $rule->{alias}
72 5 100       43 : $name
73             ;
74              
75 5         40 $name = $newname;
76             }
77              
78 37 100       133 if (exists $rule->{default}) {
79              
80 2 100 66     10 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         3 $_[$i] = $rule->{default};
86             }
87             }
88              
89 37 100       114 if (exists $rule->{required}) {
90 3         8 $requireds{$name} = $i;
91             }
92              
93            
94 37   100     202 my $comment = $rule->{comment} || "";
95 37         160 my @names = (substr($name,0,1), $name);
96 37 100       117 push @names, $rule->{alias} if $rule->{alias};
97 37 100       89 my $optname = join(', ', map { (length($_) > 1 ? '--' : '-').$_ } @names);
  82         360  
98 37         141 push @option_help, [ $optname, ucfirst($comment) ];
99              
100 37 100       159 if (my $gen = $coerce_generater->{$rule->{isa}}) {
101 4         22 $generaters{$name} = { idx => $i, gen => $gen };
102             }
103              
104 37 100       102 $name .= '|' . $rule->{alias} if $rule->{alias};
105 37         133 push @options, $name . $rule->{type} => \$_[$i];
106              
107 37 100       227 $i++ if defined $_[$i+1]; # discard type info
108             }
109            
110             {
111 29         76 my $err;
  29         85  
112 29     2   333 local $SIG{__WARN__} = sub { $err = shift };
  2         789  
113 29 100       150 GetOptions(@options) or Carp::croak($err);
114 27 100       19322 if ($help) {
115 1         7 $usage = "usage: $0 [options]\n\n";
116              
117 1 50       4 if (@option_help) {
118 1         776 require Text::Table;
119 1         25076 push @option_help, ['-h, --help', 'This help message'];
120 1         4 my $sep = \' ';
121 1         4 $usage .= "options:\n";
122 1         10 $usage .= Text::Table->new($sep, '', $sep, '')->load(@option_help)->stringify."\n";
123             }
124              
125 1         7299 die $usage;
126             }
127              
128 1 50       6 do { $_[$_] = $default_subs{$_}->() unless defined $_[$_] }
129 26         90 for keys %default_subs;
130              
131 26         114 while ( my ($name, $idx) = each %requireds ) {
132 3 100       15 unless (defined($_[$idx])) {
133 1         24 Carp::croak("missing mandatory parameter named '\$$name'");
134             }
135             }
136 25         268 while ( my ($name, $val) = each %generaters ) {
137 4         23 $_[$val->{idx}] = $val->{gen}->($_[$val->{idx}]);
138             }
139             }
140             }
141              
142             sub coerce ($$&) { ## no critic
143 1     1 0 394384 my ($isa, $type, $generater) = @_;
144              
145 1         4 $coerce_type_map->{$isa} = $type;
146 1         5 $coerce_generater->{$isa} = $generater;
147             }
148              
149             sub _compile_rule {
150 37     37   86 my ($rule) = @_;
151 37 100       135 if (!defined $rule) {
    100          
152 4         12 return +{ type => "!", isa => 'Bool' };
153             }
154             elsif (!ref $rule) { # single, non-ref parameter is a type name
155             my $tc = _get_type_constraint($rule) ||
156 16 50 66     69 _get_type_constraint($coerce_type_map->{$rule}) or
157             Carp::croak("cannot find type constraint '$rule'");
158 16         80 return +{ type => $tc, isa => $rule };
159             }
160             else {
161 17         29 my %ret;
162 17 50       47 if ($rule->{isa}) {
163 17         49 $ret{isa} = $rule->{isa};
164             my $tc = _get_type_constraint($rule->{isa}) ||
165 17 50 33     51 _get_type_constraint($coerce_type_map->{$rule->{isa}}) or
166 0         0 Carp::croak("cannot find type constraint '@{[$rule->{isa}]}'");
167 17         45 $ret{type} = $tc;
168             } else {
169 0         0 $ret{isa} = 'Bool';
170 0         0 $ret{type} = "!";
171             }
172 17         42 for my $key (qw(alias default required comment)) {
173 68 100       203 if (exists $rule->{$key}) {
174 12         37 $ret{$key} = $rule->{$key};
175             }
176             }
177 17         51 return \%ret;
178             }
179             }
180              
181             sub _get_type_constraint {
182 37     37   97 my $isa = shift;
183              
184 37         206 $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