File Coverage

blib/lib/Params/Smart.pm
Criterion Covered Total %
statement 160 182 87.9
branch 63 90 70.0
condition 47 62 75.8
subroutine 13 13 100.0
pod 0 6 0.0
total 283 353 80.1


line stmt bran cond sub pod time code
1             package Params::Smart;
2              
3 1     1   1046 use 5.006;
  1         4  
  1         48  
4 1     1   6 use strict;
  1         2  
  1         45  
5 1     1   19 use warnings; # ::register __PACKAGE__;
  1         2  
  1         28  
6              
7 1     1   5 use Carp;
  1         1  
  1         64  
8 1     1   871 use Regexp::Common qw( delimited );
  1         2162  
  1         4  
9              
10             require Exporter;
11              
12             our @ISA = qw( Exporter );
13             our @EXPORT = qw( Params );
14             our @EXPORT_OK = qw( Params ParamsNC );
15             our %EXPORT_TAGS = ( all => \@EXPORT_OK );
16              
17             our $VERSION = '0.08';
18              
19             sub parse_param {
20 71     71 0 72 my $self = shift;
21 71         72 my $param = shift;
22              
23 71         66 local ($_);
24 71 100       155 if (ref($param) eq "HASH") {
    50          
25             # we only want to pass supported parameters
26 17         31 my $info = {
27             _parsed => 0,
28             };
29 17         31 foreach (qw(
30             name type default required name_only slurp
31             callback comment needs
32             )) {
33 153         269 $info->{$_} = $param->{$_};
34             }
35 17         36 return $info;
36             } elsif (!ref($param)) {
37 54         178 $param =~ /^([\?\+\*]+)?([\@\$\%\&])?([\w\|]+)(\=.+)?/;
38 54   100     186 my $mod = $1 || "";
39 54         82 my $type = $2;
40 54         62 my $name = $3;
41 54 50       116 my $def = substr($4,1) if (defined $4);
42              
43 54 50 33     107 if ((defined $def) &&
44             ($def =~ /$RE{quoted}{-keep}/)) {
45 0         0 $def = $3;
46             }
47              
48 54 50       92 unless (defined $name) {
49 0         0 croak "malformed parameter $param";
50             }
51 54 50       103 if ($name =~ /^\_\w+/) {
52 0         0 croak "parameter $name cannot begin with an underscore";
53             }
54              
55 54 50       109 if (exists $self->{names}->{$name}) {
56 0         0 croak "parameter $name already specified";
57             }
58             else {
59 54   100     680 my $info = {
      100        
      100        
60             name => $name,
61             type => $type,
62             default => $def,
63             required => (($mod !~ /\?/) || 0),
64             name_only => (($mod =~ /\+/) || 0),
65             slurp => (($mod =~ /\*/) || 0),
66             callback => undef, # sub { return $_[2]; },
67             comment => $name,
68             needs => undef,
69             _parsed => 1,
70             };
71 54         133 return $info;
72             }
73             } else {
74 0         0 croak "invalid parameter";
75             }
76 0         0 return;
77             }
78              
79             sub set_param {
80 73     73 0 85 my $self = shift;
81 73         70 my $info = shift;
82 73 50       157 croak "invalid parameter" unless (ref($info) eq "HASH");
83              
84             # TODO - name_only should be set if this is dynamic
85              
86 73   66     298 $self->{dynamic} ||= ($self->{lock});
87 73   100     214 $info->{name_only} ||= ($self->{dynamic});
88              
89 73         190 my @names = split /\|/, $info->{name};
90 73         101 $info->{name} = undef;
91              
92 73         70 do {
93 77         99 my $name = shift @names;
94 77 100       178 $info->{name} = $name, unless (defined $info->{name});
95 77 50       141 if (exists $self->{names}->{$name}) {
96 0         0 $self->{names}->{$name} = $info;
97             }
98             else {
99 77         61 my $index = scalar(@{$self->{order}});
  77         113  
100 77 100       147 unless ($info->{name_only}) {
101 67         89 $info->{_index} = $index;
102 67         596 $self->{order}->[$index] = $name;
103             }
104 77         166 $self->{names}->{$name} = $info;
105             }
106 77 100       219 if (@names) {
107 4   100     14 $info->{name_only} ||= 1;
108 4         6 $info->{required} = 0;
109 4         11 delete $info->{default};
110             }
111             } while (@names);
112 73         114 return $info;
113             }
114              
115             sub new {
116 28     28 0 33 my $class = shift;
117 28         113 my $self = {
118             names => { },
119             order => [ ],
120             lock => 0,
121             dynamic => 0,
122             };
123 28         56 bless $self, $class;
124              
125 28         35 my $index = 0;
126 28         29 my $last;
127 28         59 SLURP: while (my $param = shift) {
128              
129 71         130 my $info = $self->parse_param($param);
130 71 50       111 if ($info) {
131 71 100       135 if ($info->{slurp}) {
132 2 50       7 croak "no parameters can follow a slurp" if (@_);
133             }
134 71 50 100     245 if ($last && $info->{required} && (!$last->{required})) {
      66        
135 0         0 croak "a required parameter cannot follow an optional parameter";
136             }
137 71 50 66     153 if ($info->{name_only} && $info->{slurp}) {
138 0         0 croak "a parameter cannot be named_only and a slurp";
139             }
140 71 50 66     214 if ($last && ($info->{_parsed} != $last->{_parsed})) {
141 0         0 croak "cannot mix parsed and non-parsed parameters";
142             }
143 71         123 $self->set_param($info);
144 71         81 $last = $info;
145             }
146             else {
147 0         0 croak "unknown error";
148             }
149 71         165 $index++;
150             }
151              
152 28         35 $self->{lock} = 1;
153 28         163 return $self;
154             }
155              
156             # We have the exported Params() function rather than requiring calls
157             # to Params::Smart->new() so that the code looks a lot cleaner. It's
158             # also a wrapper for a home-grown memoization function. (We cannot use
159             # Memoize because callbacks become problematic.)
160              
161             my %Memoization = ( );
162              
163             sub Params {
164 27 50   27 0 26028 my $key = join $;, map { $_||""} (caller);
  81         238  
165 27   66     184 return $Memoization{$key} ||= __PACKAGE__->new(@_);
166             }
167              
168             sub ParamsNC {
169 2     2 0 775 return __PACKAGE__->new(@_);
170             }
171              
172             # Note: usage does not display aliases, nor named_only parameters
173              
174             sub _usage {
175 4     4   5 my $self = shift;
176 4         6 my $error = shift;
177 4   50     11 my $named = shift || 0;
178              
179 4         6 local($_);
180              
181 4   50     18 my $caller = (caller(2))[3] || "";
182              
183 4         10 my $usage = $error . ";\nusage: $caller(";
184              
185             # TODO - handle named parameters etc.
186              
187 8         9 $usage .=
188             join(", ", map {
189 4         7 my $name = $_;
190 8 100       23 $name = "?$name", unless ($self->{names}->{$name}->{required});
191 8 50       25 $name = "*$name", if ($self->{names}->{$name}->{slurp});
192 8         17 $name;
193 4         8 } @{$self->{order}}) . ") ";
194              
195              
196 4         515 croak $usage;
197             }
198              
199             # The callback is expected to coerce the data or return an error
200              
201             sub _run_callback {
202 2     2   3 my $self = $_[0];
203 2         3 my $name = $_[1];
204 2         3 my $callback = $_[0]->{names}->{$name}->{callback};
205 2 50       7 if (ref($callback) eq "CODE") {
206 2         2 return &{$callback}(@_);
  2         5  
207             }
208             else {
209 0         0 croak "expected code reference for callback";
210             }
211             }
212              
213             sub args {
214 29     29 0 33 my $self = shift;
215              
216             # TODO - return a reference to $self in the values
217              
218 29         42 my %vals = ( );
219              
220             # $vals{_args} = [ @_ ];
221              
222 29         52 my $named = !(@_ % 2);
223              
224             # For even number positional parameter with undef in them.
225 29   100     109 for (my $i=0; ($named && ($i < @_)); $i += 2) {
226 41 100       180 if (!defined $_[$i]) { $named = 0 }
  1         5  
227             }
228              
229 29 100       45 if ($named) {
230 20         22 my %unknown = ( );
231 20         24 my $i = 0;
232 20   66     70 while ($named && ($i < @_)) {
233 40         55 my $n = $_[$i];
234 40 100       77 $n = substr($n,1) if ($n =~ /^\-/);
235 40 100       77 if (exists $self->{names}->{$n}) {
236 35         55 my $truename = $self->{names}->{$n}->{name};
237 35         53 $vals{$truename} = $_[$i+1];
238 35 100       83 if ($self->{names}->{$truename}->{callback}) {
239 2         3 $@ = undef;
240 2         2 eval {
241 2         7 $vals{$truename} =
242             $self->_run_callback($truename, $vals{$truename}, \%vals);
243             };
244 2 50       12 $self->_usage($@,$named) if ($@);
245             }
246             } else {
247 5         12 $unknown{$n} = $i;
248             }
249 40         152 $i += 2;
250             }
251              
252             # As long as there are unknown keys and dynamically-added
253             # parameters, we'll keep re-checking.
254              
255 20         39 while ($self->{dynamic}) {
256 2         3 $self->{dynamic} = 0;
257 2 100 66     15 if ($named && (keys %unknown)) {
258 1         3 foreach my $n (keys %unknown) {
259 1 50       3 if (exists $self->{names}->{$n}) {
260 1         4 my $truename = $self->{names}->{$n}->{name};
261 1         2 $vals{$truename} = $_[$unknown{$n}+1];
262 1 50       6 if ($self->{names}->{$truename}->{callback}) {
263 0         0 $@ = undef;
264 0         0 eval {
265 0         0 $vals{$truename} =
266             $self->_run_callback($truename, $vals{$truename}, \%vals);
267             };
268 0 0       0 $self->_usage($@,$named) if ($@);
269             }
270 1         8 delete $unknown{$n};
271             }
272             }
273             }
274             }
275              
276 20 100 66     149 if ($named && (keys %unknown) && (keys %vals)) {
    100 100        
      66        
277 2         12 $self->_usage("unrecognized parameters: " .
278 2         5 join(" ", map { "\"$_\"" } keys %unknown), $named);
279             }
280             elsif ($named && (keys %unknown)) {
281 2         2 $named = 0;
282 2         6 %vals = ( );
283             }
284             }
285              
286 27 100       45 unless ($named) {
287 11         11 my $i = 0;
288 11         23 while ($i < @_) {
289 21         34 my $n = $self->{order}->[$i];
290 21 50       41 unless (defined $n) {
291 0         0 $self->_usage("too many arguments",$named);
292             }
293 21         40 my $truename = $self->{names}->{$n}->{name};
294 21 100       40 if ($self->{names}->{$truename}->{slurp}) {
295 1         6 $vals{$truename} = [ @_[$i..$#_] ];
296 1         3 $i = $#_; # we don't want to use 'last'
297             } else {
298 20         36 $vals{$truename} = $_[$i];
299             }
300 21 50       73 if ($self->{names}->{$truename}->{callback}) {
301 0         0 $@ = undef;
302 0         0 eval {
303 0         0 $vals{$truename} =
304             $self->_run_callback($truename, $vals{$truename}, \%vals);
305             };
306 0 0       0 $self->_usage($@,$named) if ($@);
307             }
308 21         52 $i++;
309             }
310             }
311              
312             # validation stage
313              
314 27         24 foreach my $name (keys %{ $self->{names} }) {
  27         91  
315 74         101 my $info = $self->{names}->{$name};
316 74 100       132 unless (exists($vals{$name})) {
317 20 50 66     79 $vals{$name} = $info->{default},
318             if (($name eq $info->{name}) && (defined $info->{default}));
319             }
320 74 50 66     178 if ($info->{required} && !exists($vals{$name})) {
321 0         0 $self->_usage("missing required parameter \"$name\"", $named);
322             }
323 74 100       170 if (defined $info->{needs}) {
324             # convert a scalar into a list with one element
325 4 100       11 if (!ref $info->{needs}) { $info->{needs} = [ $info->{needs} ] }
  2         5  
326              
327 4         5 foreach my $dep (@{ $info->{needs} }) {
  4         9  
328 5 100       15 unless (exists($vals{$dep})) {
329 2         9 $self->_usage("missing required parameter \"$dep\" (needed by \"$name\")", $named);
330             }
331             }
332              
333             }
334             }
335              
336 25         58 $vals{_named} = $named;
337              
338 25         177 return %vals;
339             }
340              
341              
342             1;
343              
344             __END__