File Coverage

blib/lib/Params/Smart.pm
Criterion Covered Total %
statement 153 181 84.5
branch 60 90 66.6
condition 45 62 72.5
subroutine 13 13 100.0
pod 0 6 0.0
total 271 352 76.9


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