File Coverage

blib/lib/Smart/Args.pm
Criterion Covered Total %
statement 107 109 98.1
branch 46 52 88.4
condition 9 13 69.2
subroutine 11 11 100.0
pod 2 2 100.0
total 175 187 93.5


line stmt bran cond sub pod time code
1             package Smart::Args;
2 16     16   3836268 use strict;
  16         28  
  16         491  
3 16     16   79 use warnings;
  16         243  
  16         366  
4 16     16   361 use 5.008001;
  16         55  
  16         787  
5             our $VERSION = '0.12';
6 16     16   87 use Exporter 'import';
  16         40  
  16         690  
7 16     16   14934 use PadWalker qw/var_name/;
  16         14549  
  16         1078  
8 16     16   98 use Carp ();
  16         25  
  16         240  
9 16     16   16561 use Mouse::Util::TypeConstraints ();
  16         10022125  
  16         24475  
10              
11             *_get_type_constraint = \&Mouse::Util::TypeConstraints::find_or_create_isa_type_constraint;
12              
13             our @EXPORT = qw/args args_pos/;
14              
15             my %is_invocant = map{ $_ => undef } qw($self $class);
16              
17             sub args {
18             {
19 44     44 1 46910 package DB;
20             # call of caller in DB package sets @DB::args,
21             # which requires list context, but we don't need return values
22 44         412 () = CORE::caller(1);
23             }
24              
25 44 50       208 if(@_) {
26 44   50     293 my $name = var_name(1, \$_[0]) || '';
27 44 100       192 if(exists $is_invocant{ $name }){ # seems method call
28 8         16 $_[0] = shift @DB::args; # set the invocant
29 8 100       31 if(defined $_[1]) { # has rule?
30 2         7 $name =~ s/^\$//;
31             # validate_pos($value, $exists, $name, $basic_rule, $used_ref)
32 2         22 $_[0] = _validate_by_rule($_[0], 1, $name, $_[1]);
33 1         2 shift;
34             }
35 7         17 shift;
36             }
37             }
38              
39 43 100 66     297 my $args = ( @DB::args == 1 && ref($DB::args[0]) )
40             ? $DB::args[0] # must be hash
41             : +{ @DB::args }; # must be key-value list
42              
43             ### $args
44             ### @_
45              
46             # args my $var => RULE
47             # ~~~~ ~~~~
48             # undef defined
49              
50 43         75 my $used = 0;
51 43         144 for(my $i = 0; $i < @_; $i++){
52              
53 57 50       275 (my $name = var_name(1, \$_[$i]))
54             or Carp::croak('usage: args my $var => TYPE, ...');
55 57         400 $name =~ s/^\$//;
56              
57             # with rule (my $foo => $rule, ...)
58 57 100       172 if(defined $_[ $i + 1 ]) {
59             # validate_pos($value, $exists, $name, $basic_rule, $used_ref)
60 50         232 $_[$i] = _validate_by_rule($args->{$name}, exists($args->{$name}), $name, $_[$i + 1], \$used);
61 37         121 $i++;
62             }
63             # without rule (my $foo, my $bar, ...)
64             else {
65 7 100       29 if(!exists $args->{$name}) { # parameters are mandatory by default
66 1         7 @_ = ("missing mandatory parameter named '\$$name'");
67 1         264 goto \&Carp::confess;
68             }
69 6         18 $_[$i] = $args->{$name};
70 6         21 $used++;
71             }
72             }
73              
74 29 100 66     46 if( $used < keys %{$args} && warnings::enabled('void') ) {
  29         885  
75             # hack to get unused argument names
76 4         10 my %vars;
77 4         9 foreach my $slot(@_) {
78 16 100       91 my $name = var_name(1, \$slot) or next;
79 8         5374 $name =~ s/^\$//;
80 8         102 $vars{$name} = undef;
81             }
82 4         12 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
83 13         2004 warnings::warn( void =>
84             'unknown arguments: '
85 4         9 . join ', ', sort grep{ not exists $vars{$_} } keys %{$args} );
  4         15  
86             }
87 27         123 return;
88             }
89              
90             sub args_pos {
91             {
92 11     11 1 26498 package DB;
93             # call of caller in DB package sets @DB::args,
94             # which requires list context, but we don't need return values
95 11         115 () = CORE::caller(1);
96             }
97 11 50       43 if(@_) {
98 11   50     65 my $name = var_name(1, \$_[0]) || '';
99 11 50       41 if(exists $is_invocant{ $name }){ # seems method call
100 11         18 $_[0] = shift @DB::args; # set the invocant
101 11 100       30 if(defined $_[1]) { # has rule?
102 2         9 $name =~ s/^\$//;
103             # validate_pos($value, $exists, $name, $basic_rule, $used_ref)
104 2         7 $_[0] = _validate_by_rule($_[0], 1, $name, $_[1]);
105 1         2 shift;
106             }
107 10         17 shift;
108             }
109             }
110              
111 10         23 my @args = @DB::args;
112              
113             ### $args
114             ### @_
115              
116             # args my $var => RULE
117             # ~~~~ ~~~~
118             # undef defined
119              
120 10         32 for(my $i = 0; $i < @_; $i++){
121 15 50       54 (my $name = var_name(1, \$_[$i]))
122             or Carp::croak('usage: args my $var => TYPE, ...');
123              
124             # with rule (my $foo => $rule, ...)
125 15 100       40 if (defined $_[ $i + 1 ]) {
126 6         22 $_[$i] = _validate_by_rule($args[0], @args>0, $name, $_[$i + 1]);
127 6         10 shift @args;
128 6         16 $i++;
129             }
130             # without rule (my $foo, my $bar, ...)
131             else {
132 9 50       22 if (@args == 0) { # parameters are mandatory by default
133 0         0 @_ = ("missing mandatory parameter named '\$$name'");
134 0         0 goto \&Carp::confess;
135             }
136 9         30 $_[$i] = shift @args;
137             }
138             }
139              
140             # too much arguments
141 10 100       24 if ( scalar(@args) > 0 ) {
142             # hack to get unused argument names
143 4         9 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
144 4         777 Carp::croak( void =>
145             'too much arguments. This function requires only ' . scalar(@_) . ' arguments.' );
146             }
147 6         18 return;
148             }
149              
150             # rule: $type or +{ isa => $type, optional => $bool, default => $default }
151             sub _validate_by_rule {
152 60     60   148 my ($value, $exists, $name, $basic_rule, $used_ref) = @_;
153              
154             # compile the rule
155 60         83 my $rule;
156             my $type;
157 60         79 my $mandatory = 1; # all the arguments are mandatory by default
158 60 100       147 if(ref($basic_rule) eq 'HASH') {
159 15         19 $rule = $basic_rule;
160 15 100       45 if (defined $basic_rule->{isa}) {
161 9         37 $type = _get_type_constraint($basic_rule->{isa});
162             }
163 15         156 $mandatory = !$rule->{optional};
164             }
165             else {
166             # $rule is a type constraint name or type constraint object
167 45         195 $type = _get_type_constraint($basic_rule);
168             }
169              
170             # validate the value by the rule
171 60 100       2989 if ($exists){
172 54 100       392 if(defined $type ){
173 50 100       416 if(!$type->check($value)){
174 15         920 $value = _try_coercion_or_die($name, $type, $value);
175             }
176             }
177 41 100       1715 ${$used_ref}++ if defined $used_ref;
  35         73  
178             }
179             else {
180 6 100 100     74 if(defined($rule) and exists $rule->{default}){
    100          
181 2         9 $value = $rule->{default};
182             }
183             elsif($mandatory){
184 2         25 @_ = ("missing mandatory parameter named '\$$name'");
185 2         415 goto \&Carp::confess;
186             }
187             else{
188             # no default, and not mandatory; noop
189             }
190             }
191 45         113 return $value;
192             }
193              
194             sub _try_coercion_or_die {
195 15     15   41 my($name, $tc, $value) = @_;
196 15 100       172 if($tc->has_coercion) {
197 4         29 $value = $tc->coerce($value);
198 4 100       571 $tc->check($value) and return $value;
199             }
200 13         770 @_ = ("'$name': " . $tc->get_message($value));
201 13         81093 goto \&Carp::confess;
202             }
203             1;
204             __END__
205              
206             =head1 NAME
207              
208             Smart::Args - argument validation for you
209              
210             =head1 SYNOPSIS
211              
212             use Smart::Args;
213              
214             sub func2 {
215             args my $p => 'Int',
216             my $q => { isa => 'Int', optional => 1 };
217             }
218             func2(p => 3, q => 4); # p => 3, q => 4
219             func2(p => 3); # p => 3, q => undef
220              
221             sub func3 {
222             args my $p => {isa => 'Int', default => 3},
223             }
224             func3(p => 4); # p => 4
225             func3(); # p => 3
226              
227             package F;
228             use Moose;
229             use Smart::Args;
230              
231             sub method {
232             args my $self,
233             my $p => 'Int';
234             }
235             sub class_method {
236             args my $class => 'ClassName',
237             my $p => 'Int';
238             }
239              
240             sub simple_method {
241             args_pos my $self, my $p;
242             }
243              
244             my $f = F->new();
245             $f->method(p => 3);
246              
247             F->class_method(p => 3);
248              
249             F->simple_method(3);
250              
251             =head1 DESCRIPTION
252              
253             Smart::Args is yet another argument validation library.
254              
255             This module makes your module more readable, and writable =)
256              
257             =head1 FUNCTIONS
258              
259             =head2 C<args my $var [, $rule], ...>
260              
261             Checks parameters and fills them into lexical variables. All the parameters
262             are mandatory by default, and unknown parameters (i.e. possibly typos) are
263             reported as C<void> warnings.
264              
265             The arguments of C<args()> consist of lexical <$var>s and optional I<$rule>s.
266              
267             I<$vars> must be a declaration of a lexical variable.
268              
269             I<$rule> can be a type name (e.g. C<Int>), a HASH reference (with
270             C<type>, C<default>, and C<optional>), or a type constraint object.
271              
272             Note that if the first variable is named I<$class> or I<$self>, it
273             is dealt as a method call.
274              
275             See the SYNOPSIS section for examples.
276              
277             =head2 C<args_pos my $var[, $rule, ...>
278              
279             Check parameters and fills them into lexical variables. All the parameters
280             are mandatory by default.
281              
282             The arguments of C<args()> consist of lexical <$var>s and optional I<$rule>s.
283             I<$vars> must be a declaration of a lexical variable.
284              
285             I<$rule> can be a type name (e.g. C<Int>), a HASH reference (with
286             C<type>, C<default>, and C<optional>), or a type constraint object.
287              
288             Note that if the first variable is named I<$class> or I<$self>, it
289             is dealt as a method call.
290              
291             See the SYNOPSIS section for examples.
292              
293             =head1 TYPES
294              
295             The types that C<Smart::Args> uses are type constraints of C<Mouse>.
296             That is, you can define your types in the way Mouse does.
297              
298             In addition, C<Smart::Args> also allows Moose type constraint objects,
299             so you can use any C<MooseX::Types::*> libraries on CPAN.
300              
301             Type coercions are automatically tried if validations fail.
302              
303             See L<Mouse::Util::TypeConstraints> for details.
304              
305             =head1 AUTHOR
306              
307             Tokuhiro Matsuno E<lt>tokuhirom@gmail.comE<gt>
308              
309             =head1 SEE ALSO
310              
311             L<Params::Validate>
312              
313             =head1 LICENSE
314              
315             This library is free software; you can redistribute it and/or modify
316             it under the same terms as Perl itself.
317              
318             =cut