File Coverage

blib/lib/Sub/Auto.pm
Criterion Covered Total %
statement 111 113 98.2
branch 13 16 81.2
condition n/a
subroutine 27 28 96.4
pod 0 10 0.0
total 151 167 90.4


line stmt bran cond sub pod time code
1             package Sub::Auto;
2              
3             our $VERSION = 0.0202;
4              
5             =head1 NAME
6              
7             Sub::Auto - declare individual handlers for AUTLOADed subs, respecting can and inheritance
8              
9             =head1 SYNOPSIS
10              
11             use Sub::Auto;
12              
13             autosub /^get_(\w+)$/ {
14             my ($what, @pars) = @_;
15             print "Getting $what...\n";
16             }
17              
18             autosub /^set_(\w+)_(\w+)$/ {
19             my ($adjective, $noun, @pars) = @_;
20             print "Setting the $adjective $noun\n";
21             }
22              
23             autosub handle_foo_events /foo$/ {
24             my ($subname, @pars) = @_;
25             print "Called $subname to do something to a foo\n";
26             }
27              
28             get_foo();
29             if (__PACKAGE__->can('set_blue_cat')) { ... }
30              
31             =head1 DESCRIPTION
32              
33             C, like other languages' C features is a useful feature
34             for those situations when you want to handle sub or method calls dynamically, and
35             can't pre-generate the subroutines with accessor generators.
36              
37             To be sure, this is almost never the case, but occasionally, C is convenient.
38              
39             Well, "convenient" is a strong word, writing C handlers is mildly
40             unpleasant, and doesn't handle inheritance and C by default.
41              
42             Using C you can:
43              
44             =over 4
45              
46             =item *
47              
48             Declare multiple handlers, each responding to calls matching a given regular expression.
49              
50             =item *
51              
52             Optionally name your handler, for clarity, or because you want to call it directly.
53              
54             =item *
55              
56             Ensure that unhandled methods get dealt with by the next class in the inheritance chain.
57              
58             =back
59              
60             =head1 USAGE
61              
62             =head2 C
63              
64             autosub [name] /regex/ { ... }
65              
66             If the regex contains capturing parentheses, then each of those items will be prepended
67             to the sub's argument list. For example:
68              
69             autosub /(\w+)_(\w+)/ {
70             my ($verb, $noun, @params) = @_;
71             print "$verb'ing $noun - " . join ','=>@params;
72             }
73              
74             jump_up('one', 'two'); # prints "jump'ing up - one,two"
75              
76             If the matching regex didn't have any capturing parens, the entire method name
77             is passed as the first argument.
78              
79             The name of the sub is optional. It registers a normal subroutine or method with
80             that name in the current package. Nothing will be automatically prepended to a call
81             to this method!
82              
83             autosub foo /(\w+)_(\w+)/ {
84             my ($verb, $noun, $one,$two) = @_;
85             print $one + $two;
86             }
87              
88             foo (undef,undef, 1, 2);
89              
90             =head1 SEE ALSO
91              
92             L by Ben Tilly, does all the heavy lifting.
93              
94             L by Matt Trout provides the tasty syntactic sugar.
95              
96             L
97              
98             L or various other method generators that are a saner solution in general
99             than using AUTOLOAD at all.
100              
101             =head1 AUTHOR AND LICENSE
102              
103             (c) 2008 osfameron@cpan.org
104              
105             This module is released under the same terms as Perl itself.
106              
107             =cut
108              
109 1     1   35881 use strict; use warnings;
  1     1   2  
  1         37  
  1         4  
  1         2  
  1         63  
110              
111 1     1   2640 use Class::AutoloadCAN;
  1         11149  
  1         8  
112 1     1   9032 use Devel::Declare 0.002;
  1         32841  
  1         9  
113 1     1   1186 use Sub::Name;
  1         711  
  1         129  
114 1     1   6864 use Scope::Guard;
  1         541  
  1         44  
115 1     1   7 use vars qw($AUTOLOAD);
  1         2  
  1         136  
116              
117             sub import {
118 1     1   11 my $class = shift;
119 1         2 my $caller = caller;
120              
121 1         3 my $parser = mk_parser($caller);
122 1         15 Devel::Declare->setup_for(
123             $caller => { autosub => { const => $parser }} );
124              
125 1     1   6 no strict 'refs';
  1         2  
  1         152  
126 1     0   27 *{$caller.'::autosub'} = sub (&) {};
  1         6  
  0         0  
127              
128             # trick via mst. See also export_to_level and Sub::Exporter
129 1         4 *{ "${caller}::CAN" } = mk_can($caller);
  1         5  
130 1         6 goto &Class::AutoloadCAN::import;
131             }
132              
133             sub mk_can {
134 1     1 0 2 my $package = shift;
135              
136              
137             return sub {
138 10     10   6267 my ($class, $method, $self, @arguments) = @_;
139             # YUCK!
140 1     1   6 no strict 'refs';
  1         1  
  1         46  
141 1     1   4 no warnings 'once';
  1         3  
  1         480  
142 10         16 for my $can (@{"${package}::CANS"}) {
  10         32  
143 27         37 my ($re, $sub) = @$can;
144 27 100       195 if (my @result = $method =~ /$re/) {
145 9 100       32 @result = $method unless defined $1; # or $& ?
146             return sub {
147 7     7   78 $sub->(@result, @_)
148 9         57 };
149             }
150             }
151 1         4 return;
152 1         5 };
153             }
154              
155             # Following boilerplate is stolen from Devel::Declare's t/method-no-semi.t
156             # Note that, as with Sub::Curried and Method::Signatures, this boilerplate
157             # may well be made into the "official" API shortly, at which point we'll
158             # refactor and clean up!
159              
160             {
161             our ($Declarator, $Offset);
162              
163             sub skip_declarator;
164             sub strip_name;
165             sub strip_proto;
166              
167             sub mk_parser {
168 1     1 0 2 my $package = shift;
169             return sub {
170 5     5   1255 local ($Declarator, $Offset) = @_;
171 5         12 skip_declarator;
172 5         11 my $name = strip_name;
173 5         12 my $re = strip_proto;
174              
175 5 100       14 if (defined $name) {
176 1 50       8 $name = join('::', Devel::Declare::get_curstash_name(), $name)
177             unless ($name =~ /::/);
178             }
179              
180             # we do scope trick even if no name (as the proto is a kind of name)
181 5         9 my $inject = scope_injector_call();
182 5         11 inject_if_block($inject);
183              
184 1     1   6 no strict 'refs';
  1         1  
  1         90  
185             my $installer = sub (&) {
186 5         45 my $f = shift;
187             # YUCK!
188 5         6 push @{"${package}::CANS"}, [qr/$re/, $f];
  5         213  
189             # if we have a name, then install
190 5 100       16 if ($name) {
191 1     1   11 no strict 'refs';
  1         2  
  1         839  
192 1         10 *{$name} = subname $name => $f;
  1         5  
193             }
194 5         23 return $f;
195 5         37 };
196 5         9 shadow($installer);
197 1         6 };
198             }
199              
200             sub skip_declarator {
201 5     5 0 15 $Offset += Devel::Declare::toke_move_past_token($Offset);
202             }
203              
204             sub skipspace {
205 15     15 0 34 $Offset += Devel::Declare::toke_skipspace($Offset);
206             }
207              
208             sub strip_name {
209 5     5 0 8 skipspace;
210 5 100       20 if (my $len = Devel::Declare::toke_scan_word($Offset, 1)) {
211 1         4 my $linestr = Devel::Declare::get_linestr();
212 1         10 my $name = substr($linestr, $Offset, $len);
213 1         3 substr($linestr, $Offset, $len) = '';
214 1         2 Devel::Declare::set_linestr($linestr);
215 1         3 return $name;
216             }
217 4         7 return;
218             }
219              
220             sub strip_proto {
221 5     5 0 8 skipspace;
222            
223 5         13 my $linestr = Devel::Declare::get_linestr();
224 5 50       21 if (substr($linestr, $Offset, 1) =~/^[[:punct:]]$/ ) {
225 5         32 my $length = Devel::Declare::toke_scan_str($Offset);
226 5         15 my $proto = Devel::Declare::get_lex_stuff();
227 5         9 Devel::Declare::clear_lex_stuff();
228 5         11 $linestr = Devel::Declare::get_linestr();
229 5         8 substr($linestr, $Offset, $length) = '';
230 5         10 Devel::Declare::set_linestr($linestr);
231 5         12 return $proto;
232             }
233 0         0 return;
234             }
235              
236             sub shadow {
237 5     5 0 13 my $pack = Devel::Declare::get_curstash_name;
238 5         22 Devel::Declare::shadow_sub("${pack}::${Declarator}", $_[0]);
239             }
240            
241             sub inject_if_block {
242 5     5 0 6 my $inject = shift;
243 5         8 skipspace;
244 5         11 my $linestr = Devel::Declare::get_linestr;
245 5 50       13 if (substr($linestr, $Offset, 1) eq '{') {
246 5         9 substr($linestr, $Offset+1, 0) = $inject;
247 5         13 Devel::Declare::set_linestr($linestr);
248             }
249             }
250              
251             # Set up the parser scoping hacks that allow us to omit the final
252             # semicolon
253             sub scope_injector_call {
254 5     5 0 6 my $pkg = __PACKAGE__;
255 5         14 return " BEGIN { ${pkg}::inject_scope }; ";
256             }
257             sub inject_scope {
258 5     5 0 448 $^H |= 0x120000;
259             $^H{DD_METHODHANDLERS} = Scope::Guard->new(sub {
260 5     5   282 my $linestr = Devel::Declare::get_linestr;
261 5         18 my $offset = Devel::Declare::get_linestr_offset;
262 5         10 substr($linestr, $offset, 0) = ';';
263 5         3557 Devel::Declare::set_linestr($linestr);
264 5         33 });
265             }
266             }
267            
268             1;