File Coverage

blib/lib/MooseX/Params.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package MooseX::Params;
2             {
3             $MooseX::Params::VERSION = '0.010';
4             }
5              
6             # ABSTRACT: Subroutine signature declaration via attributes
7              
8 7     7   605412 use strict;
  7         16  
  7         233  
9 7     7   37 use warnings;
  7         14  
  7         172  
10 7     7   167 use 5.010;
  7         25  
  7         238  
11 7     7   4147 use MooseX::Params::Util;
  0            
  0            
12             use MooseX::Params::TypeConstraints;
13             use Carp qw(croak);
14              
15             sub import
16             {
17             my @attrs = qw(Args BuildArgs CheckArgs Returns ReturnsScalar);
18              
19             my @handlers;
20             foreach my $attribute (@attrs)
21             {
22             push @handlers, "CODE:$attribute",
23             MooseX::Params::Util::prepare_attribute_handler($attribute);
24             }
25              
26             require Attribute::Lexical;
27             Attribute::Lexical->import(@handlers);
28             }
29              
30             ### ATTRIBUTES ###
31              
32             sub Args
33             {
34             my ($method, $data) = @_;
35             my $parameters = MooseX::Params::Util::inflate_parameters($method->package_name, $data);
36             $method->parameters($parameters);
37             }
38              
39             sub BuildArgs
40             {
41             my ($method, $data) = @_;
42             $data = "_buildargs_" . $method->name unless $data;
43             $method->buildargs($data);
44             }
45              
46             sub CheckArgs
47             {
48             my ($method, $data) = @_;
49             $data = "_checkargs_" . $method->name unless $data;
50             $method->checkargs($data);
51             }
52              
53             sub Returns
54             {
55             my ($method, $data) = @_;
56             croak "Empty return value constraint not allowed" unless $data;
57             $method->returns($data);
58             }
59              
60             sub ReturnsScalar
61             {
62             my ($method, $data) = @_;
63             $method->returns_scalar($data);
64             }
65              
66             1;
67              
68              
69             __END__
70             =pod
71              
72             =for :stopwords Peter Shangov TODO invocant isa metaroles metarole multimethods sourcecode
73             backwards buildargs checkargs slurpy preprocess
74              
75             =head1 NAME
76              
77             MooseX::Params - Subroutine signature declaration via attributes
78              
79             =head1 VERSION
80              
81             version 0.010
82              
83             =head1 SYNOPSIS
84              
85             # use Moose types for validation
86             # positional arguments are by default required
87             sub add :Args(Int first, Int second) {
88             return $_{first} + $_{second};
89             }
90              
91             say add(2, 3); # 5
92             say add(2); # error
93              
94             # @_ still works: you can ignore %_ if you want to
95             sub add2 :Args(Int first, Int second) {
96             my ($first, $second) = @_;
97             return $first + $second;
98             }
99              
100             say add2(2, 3); # 5
101              
102             # '&' before a type constraint enables coercion
103             subtype 'HexNum', as 'Str', where { /[a-f0-9]/i };
104             coerce 'Int', from 'HexNum', via { hex $_ };
105              
106             sub add3 :Args(&Int first, &Int second) {
107             return $_{first} + $_{second};
108             }
109              
110             say add3('A', 'B'); # 21
111              
112             # slurpy arguments consume the remainder of @_
113             sub sum :Args(ArrayRef *values) {
114             my $sum = 0;
115             my @values = @{$_{values}};
116              
117             foreach my $value (@values) {
118             $sum += $value;
119             }
120             return $sum;
121             }
122              
123             say sum(2, 3, 4, 5); # 14
124              
125             # 'all' is optional:
126             # if not present search the text within a file and return 1 if found, 0 if not
127             # if present search the text and return number of lines in which text is found
128             sub search :Args(text, fh, all?) {
129             my $cnt = 0;
130              
131             while (my $line = $_{fh}->getline) {
132             if ( index($line, $_{text}) > -1 ) {
133             return 1 if not $_{all};
134             $cnt++;
135             }
136             }
137              
138             return $cnt;
139             }
140              
141             # named arguments
142             sub foo :Args(a, :b) {
143             return $_{a} + $_{b} * 2;
144             }
145              
146             # say foo( 3, b => 2 ); # 7
147             # say foo(4, 9); # error
148             # say foo(2); # error
149             # say foo(2, 3, 4); # error
150              
151             # parameters are immutable, assign to a variable to edit
152             sub trim :Args(Str string) {
153             my $string = $_{string};
154             $string =~ s/^\s*//;
155             $string =~ s/\s*$//;
156             return $string;
157             }
158              
159             # parameters can have simple defaults
160             sub find_clothes :Args(:size = 'medium', :color = 'white') { ... }
161              
162             # or builders for more complex tasks
163             sub find_clothes :Args(
164             :size = _build_param_size,
165             :color = _build_param_color,
166             :height = 170 )
167             { ... }
168              
169             sub _build_param_color {
170             return (qw(red green blue))[ int( rand 3 ) ];
171             }
172              
173             # you can access all other parameters within a builder
174             sub _build_param_size {
175             return $_{height} > 200 ? 'large' : 'medium';
176             }
177              
178             # preprocess @_ with buildargs
179             sub process_template
180             :Args(input, output, params)
181             :BuildArgs(_buildargs_process_template)
182             {
183             say "open $_{input}";
184             say "replace " . Dumper $_{params};
185             say "save $_{output}";
186             }
187              
188             # if 'output' is not provided, deduct it from input filename
189             sub _buildargs_process_template {
190             if (@_ == 2) {
191             my ($input, $params) = @_;
192             my $output = $input;
193             substr($output, -4, 4, "html");
194             return $input, $output, $params;
195             } else {
196             return @_;
197             }
198             }
199              
200             my %data = (
201             fname => "Foo",
202             lname => "Bar",
203             );
204              
205             process_template("index.tmpl", \%data);
206             # open index.tmpl
207             # replace {"lname" => "Bar", "fname" => "Foo"}
208             # save index.html
209              
210             process_template("from.tmpl", "to.html", \%data);
211             # open from.tmpl
212             # replace {"lname" => "Bar", "fname" => "Foo"}
213             # save to.html
214              
215             # additional validation with checkargs
216             sub process_person
217             :Args(:first_name!, :last_name!, :country!, :ssn?)
218             :CheckArgs # shortcut for :CheckArgs(_checkargs_${subname})
219             { ... }
220              
221             sub _checkargs_process_person {
222             if ( $_{country} eq 'USA' ) {
223             die 'All US residents must have an SSN' unless $_{ssn};
224             }
225             }
226              
227             # return value validation
228             sub sum :Args(a, b) :Returns(Num) { ... }
229              
230             # validate non-scalar return values
231             sub get_data :Returns(Array) { qw(foo bar baz) }
232             my ($foo, $bar, $baz) = get_data();
233              
234             # force special behavior in sclar context
235             sub get_winners :Returns(Array) :ReturnsScalar(First) {
236             my @ordered_winners = ...;
237             return @ordered_winners;
238             }
239              
240             my $first_place = get_winners();
241              
242             # in a class
243             package User;
244              
245             use Moose;
246             use MooseX::Params;
247             use DateTime;
248              
249             extends 'Person';
250              
251             has 'password' => (
252             is => 'rw',
253             isa => 'Str',
254             );
255              
256             has 'last_login' => (
257             is => 'rw',
258             isa => 'DateTime',
259             );
260              
261             # note the shortcut invocant syntax
262             sub login :Args(self: Str pw) :Returns(Bool) {
263             return 0 if $_{pw} ne $_{self}->password;
264              
265             $_{self}->last_login( DateTime->now() );
266              
267             return 1;
268             }
269              
270             =head1 DESCRIPTION
271              
272             This module provides an attributes-based interface for parameter processing in Perl 5. For the original rationale see L<http://mechanicalrevolution.com/blog/parameter_apocalypse.html>.
273              
274             The proposed interface is based on three cornerstone propositions:
275              
276             =over 4
277              
278             =item *
279              
280             Parameters are first-class entities that deserve their own meta protocol. A common meta protocol may be used by different implementations (e.g. this library, L<MooseX::Params::Validate>, L<MooseX::Method::Sigantures>) and allow them to coexist better. It is also the necessary foundation for more advanced features such as multimethods and extended role validation.
281              
282             =item *
283              
284             Parameters should benefit from the same power and flexibility that L<Moose> attributes have. This module implements most of this functionality, including laziness.
285              
286             =item *
287              
288             The global variable C<%_> is used as a placeholder for processed parameters. It is considered by the author of this module as an intuitive alternative to manual unpacking of C<@_> while staying within the limits of traditional Perl syntax.
289              
290             =back
291              
292             =head1 USE WITH CARE
293              
294             This is still an experimental module and is subject to backwards incompatible changes. It is barely tested and most certainly has serious lurking bugs, has a lot of room for performance optimizations, and its error reporting could be vastly improved.
295              
296             =head1 BACKWARDS INCOMPATIBLE CHANGES
297              
298             Version 0.005 removes the interface based on the C<method> keyword, and retains only the attributes-based interface. Also, C<$self> is no longer localized inside methods, you must use C<$_{self}> instead.
299              
300             =head1 SIGNATURE SYNTAX
301              
302             Signatures are declared with the C<:Args> attribute. All parsed parameters are made available inside your subroutine within the special C<%_> hash. All elements of C<%_> are read-only, and an attempt to modify them will throw an exception. An attempt to use a hash element which is not a valid parameter name for this subroutine will also throw an exception. C<@_> is not affected by the use of signatures, so you can still use it to manually unpack arguments if you want to.
303              
304             =head2 Parameter names
305              
306             Parameter names can by any valid perl identifiers, and they are separated by commas.
307              
308             sub rank :Args(first, second, third) {
309             say "$_{first} is first, $_{second} is second, and $_{third} is third";
310             }
311              
312             =head2 Invocant
313              
314             Method signatures can specify their invocant as the first parameter, followed by a colon:
315              
316             sub rank :Args(self: first, second, third) {
317             my $competition = $_{self}->competition;
318             ...
319             }
320              
321             =head2 Type constraints
322              
323             Moose type constraints may be used for validation.
324              
325             sub rank :Args(Str first, Str second, Str third) { ... }
326              
327             An ampersand before a type enables coercion for this type.
328              
329             subtype 'Name' ...;
330              
331             coerce 'Name', from 'Str', via { ... };
332              
333             sub rank :Args(&Name first, &Name second, &Name third) { ... }
334              
335             =head2 Positional and named parameters
336              
337             Parameters are by default positional.
338              
339             sub rank :Args(first, second, third) { ... }
340             # rank('Peter', 'George', 'John')
341              
342             Named parameters are prefixed by a colon.
343              
344             sub rank :Args(:first, :second, :third) { ... }
345             # rank( first => 'Peter', second => 'George', third => 'John')
346              
347             Named parameters may be passed by one name and accessed by another.
348              
349             sub rank :Args(:gold(first), :silver(second), :bronze(third)) {
350             say "$_{first} is first, $_{second} is second, and $_{third} is third";
351             }
352             # rank( gold => 'Peter', silver => 'George', bronze => 'John')
353              
354             Positional and named parameters may be mixed, but positional parameters must come first.
355              
356             sub rank :Args(first, :second, :third) {
357             say "$_{first} is first, $_{second} is second, and $_{third} is third";
358             }
359             # rank( 'Peter', second => 'George', third => 'John')
360              
361             =head2 Required parameters
362              
363             An exclamation mark (C<!>) after the name denotes a required parameter, and a question mark (C<?>) denotes an optional parameter.
364              
365             sub rank :Args(first!, second?, third?) { ... }
366              
367             Positional parameters are by default required, and named parameters are by default optional.
368              
369             =head2 Slurpy parameters
370              
371             A parameter prefixed by an asterisk (C<*>) is slurpy, i.e. it consumes the remainder of the argument list. Slurpy parameters must come last in the signature.
372              
373             sub rank :Args(ArrayRef *winners) {
374             say "$_{winners}[0] is first, $_{winners}[1] is second, and $_{winners}[2] is third";
375             }
376              
377             =head2 Default values
378              
379             A parameter may be given a simple default value, which can be either a quoted string or an unsigned integer.
380              
381             sub rank :Args(first = 'Peter', second = 'George', third = 'John') { ... }
382              
383             You may use either single or double quotes to quote a string, but they will always be interpreted as if single quotes were used.
384              
385             =head2 Builders
386              
387             Where a default value is not sufficient, parameters may specify builders instead. A builder is a subroutine whose return value will be used as default value for the parameter.
388              
389             sub rank :Args(ArrayRef *winners = calculate_winners) { ... }
390              
391             sub calculate_winners { ... }
392              
393             The name of the builder may be optionally followed by a pair of parenthesis.
394              
395             sub rank :Args(ArrayRef *winners = calculate_winners()) { ... }
396              
397             All builders are executed lazily, i.e. the first time the parameter is accessed. If a parameter name is followed by an equal sign, but neither a default value nor a builder is specified, it is assumed that the parameter has a builder named C<_build_param_${name}>. In this case the equal sign may also be placed before the name of the parameter.
398              
399             sub rank :Args(ArrayRef *winners=) { ... }
400             # is equivalent to
401             sub rank :Args(ArrayRef =*winners) { ... }
402             # is equivalent to
403             sub rank :Args(ArrayRef *winners = _build_param_winners) { ... }
404              
405             sub _build_param_winners { ... }
406              
407             Within a parameter builder, you can access all other parameters in the C<%_> hash.
408              
409             sub connect :Args(=:dbh, :host, :port, :database) { ... }
410              
411             sub _build_param_dbh {
412             return DBI->connect("dbi:mysql:host=$_{host};port=$_{port};database=$_{database}");
413             }
414              
415             =head1 BUILDARGS AND CHECKARGS
416              
417             =head2 BuildArgs
418              
419             The C<BuildArgs> attribute allows you to specify a subroutine that will be used to preprocess your arguments before they are validated against the supplied signature. It can be used as to create poor man's multimethods by coercing different types of arguments to a single signature. It is somewhat similar to what Moose's C<BUILDARGS> does for class constructors.
420              
421             sub rank
422             :Args(:first :second :third)
423             :BuildArgs(_buildargs_rank)
424             { ... }
425              
426             # allow positional parameters as well
427             sub _buildargs_rank {
428             if (@_ == 3) {
429             return first => $_[0], second => $_[1], third => $_[2];
430             } else {
431             return @_;
432             }
433             }
434              
435             If C<BuildArgs> is specified without a subroutine name, C<_buildargs_${subname}> will be assumed.
436              
437             sub rank :Args(...) :BuildArgs { ... }
438             # is equivalent to
439             sub rank :Args(...) :BuildArgs(_buildargs_rank) { ... }
440              
441             =head2 CheckArgs
442              
443             The C<CheckArgs> attribute allows you to specify a subroutine that will be used to perform additional validation after the arguments are validated against the supplied signature. It can be used to perform more complex validations that cannot be expressed in a simple signature. It is somewhat similar to what Moose's C<BUILD> does for class constructors. Inside a C<CheckArgs> subroutine you can access the processed parameters in the C<%_> hash.
444              
445             sub rank
446             :Args(:first :second :third)
447             :CheckArgs(_checkargs_rank)
448             { ... }
449              
450             # make sure names do not repeat
451             sub _checkargs_rank {
452             if (
453             ($_{first} eq $_{second}) or
454             ($_{first} eq $_{third} ) or
455             ($_{second} eq $_{third} )
456             ) { die "One player can only take one place!"; }
457             }
458              
459             If C<CheckArgs> is specified without a subroutine name, C<_checkargs_${subname}> will be assumed.
460              
461             sub rank :Args(...) :CheckArgs { ... }
462             # is equivalent to
463             sub rank :Args(...) :CheckArgs(_checkargs_rank) { ... }
464              
465             =head1 RETURN VALUE VALIDATION
466              
467             =head2 Returns
468              
469             C<MooseX::Params> provids a basic mechanism for return value validation via the C<Returns> attribute.
470              
471             sub add :Args(a, b) :Returns(Num) { return $_{a} + $_{b} }
472             my $five = add(2,3);
473              
474             Any Moose type name may be used as an arbument to C<Returns>. If your subroutine returns a list of values, you will need to use the special parametric types C<Array> and C<Hash>. They behave identically to C<ArrayRef> and C<HashRef>, except that they work with lists instead of references:
475              
476             sub myreverse :Args(*items) :Returns(Array) { return reverse @{ $_{items} } }
477             my @list = qw(foo bar baz);
478             my @reversed = myreverse(@list);
479              
480             Note that C<wantarray> inside subroutines that use C<Returns> will always return true (see below).
481              
482             =head2 ReturnsScalar
483              
484             Return value validation does not play well with context magic. If you return different values depending on context, validation will break. Therefore, subroutines that use C<Returns> are always evaluated in list context to obrain their return value. The C<ResultScalar> attribute allows you to explicitly change how your subroutine will behave in scalar context. It accepts one of four options:
485              
486             =over
487              
488             =item Count (default)
489              
490             In scalar context return the number of items in the return value list.
491              
492             =item First
493              
494             In scalar context return the first item in the return value list.
495              
496             =item Last
497              
498             In scalar context return the last item in the return value list.
499              
500             =item ArrayRef
501              
502             In scalar context return a reference to the return value list.
503              
504             =back
505              
506             sub results :Returns(Array[MyApp::Object]) :ReturnsScalar(ArrayRef) { ... }
507              
508             =head1 META CLASSES
509              
510             C<MooseX::Params> provides method and parameter metaroles, please see their sourcecode for details:
511              
512             =over 4
513              
514             =item *
515              
516             L<MooseX::Params::Meta::Method>
517              
518             =item *
519              
520             L<MooseX::Params::Meta::Parameter>
521              
522             =back
523              
524             =head1 TODO
525              
526             =over 4
527              
528             =item *
529              
530             return value validation (C<Returns> and C<ReturnsScalar>)
531              
532             =item *
533              
534             subroutine traits (C<Does>)
535              
536             =item *
537              
538             better error checking and reporting
539              
540             =item *
541              
542             improved performance
543              
544             =item *
545              
546             lightweight implementation without meta and magic
547              
548             =back
549              
550             Whether or not these features will be implemented depends mostly on the community response to the proposed API. Currently the best way to contribute to this module would be to provide feedback and commentary - the L<Moose> mailing list will be a good place for this.
551              
552             =head1 BUGS
553              
554             Plenty. Some of the known ones are:
555              
556             =over 4
557              
558             =item *
559              
560             No checking for surplus arguments
561              
562             =item *
563              
564             C<foreach my $value (@{$_{arrayref}})> attempts to modify C<$_{arrayref}> and triggers an exception
565              
566             =item *
567              
568             May be incompatible with other modules that provide attributes, including L<Perl6::Export::Attrs>
569              
570             =item *
571              
572             C<MooseX::Params::Meta::Method> is a class, should be a role
573              
574             =back
575              
576             =head1 SEE ALSO
577              
578             =over 4
579              
580             =item *
581              
582             L<MooseX::Params::Validate>
583              
584             =item *
585              
586             L<MooseX::Method::Signatures>
587              
588             =back
589              
590             =head1 AUTHOR
591              
592             Peter Shangov <pshangov@yahoo.com>
593              
594             =head1 COPYRIGHT AND LICENSE
595              
596             This software is copyright (c) 2012 by Peter Shangov.
597              
598             This is free software; you can redistribute it and/or modify it under
599             the same terms as the Perl 5 programming language system itself.
600              
601             =cut
602