File Coverage

blib/lib/Syntax/Feature/Sugar/Callbacks.pm
Criterion Covered Total %
statement 107 108 99.0
branch 45 54 83.3
condition 5 6 83.3
subroutine 18 18 100.0
pod 1 1 100.0
total 176 187 94.1


line stmt bran cond sub pod time code
1 1     1   78714 use strictures 1;
  1         12  
  1         35  
2              
3             # ABSTRACT: Add sugar for declarative method callbacks
4              
5             package Syntax::Feature::Sugar::Callbacks;
6             {
7             $Syntax::Feature::Sugar::Callbacks::VERSION = '0.002';
8             }
9             BEGIN {
10 1     1   341 $Syntax::Feature::Sugar::Callbacks::AUTHORITY = 'cpan:PHAYLON';
11             }
12              
13 1     1   7 use Carp qw( croak );
  1         3  
  1         90  
14 1     1   1039 use Params::Classify 0.011 qw( is_ref is_string );
  1         2618  
  1         114  
15 1     1   1106 use Devel::Declare 0.006000 ();
  1         6874  
  1         44  
16 1     1   1391 use Data::Dump qw( pp );
  1         8152  
  1         149  
17 1     1   11 use B::Hooks::EndOfScope 0.09;
  1         37  
  1         10  
18              
19 1     1   1246 use aliased 'Devel::Declare::Context::Simple', 'Context';
  1         1544  
  1         8  
20              
21 1     1   5988 use namespace::clean 0.18;
  1         33  
  1         10  
22              
23             $Carp::Internal{ +__PACKAGE__ }++;
24              
25              
26              
27             sub install {
28 6     6 1 21748 my ($class, %args) = @_;
29 6         10 my $target = $args{into};
30 6         20 my $options = $class->_prepare_options($args{options});
31 4         136 my @names = keys %{ $options->{ -callbacks } };
  4         22  
32 4         9 for my $callback (@names) {
33 7         80 my $callback_options = $options->{ -callbacks }{ $callback };
34 7 100       126 croak qq{Value for $class callback '$callback' needs to be hash ref}
35             unless is_ref $callback_options, 'HASH';
36             croak qq{Option $_ for callback '$callback' needs to be array ref}
37 6         9 for grep {
38 12         28 my $value = $options->{ -callbacks }{ $callback }{ $_ };
39 12 100       50 defined($value) and not is_ref($value, 'ARRAY');
40             } qw( -before -middle );
41 6 100       230 croak qq{Can't setup sugar for non-existant '$callback' in $target}
42             unless $target->can($callback);
43             Devel::Declare->setup_for(
44             $target => { $callback => { const => sub {
45 14     14   1226 my $ctx = Context->new;
46 14         102 $ctx->init(@_);
47 14         129 return $class->_transform(
48             $ctx,
49             $options,
50             $callback_options,
51             );
52             }}},
53 5         55 );
54             }
55 2         47 return 1;
56             }
57              
58              
59             #
60             # private methods
61             #
62              
63             sub _transform {
64 14     14   22 my ($class, $ctx, $options, $cb_options) = @_;
65 14         35 $ctx->skip_declarator;
66 14         682 $ctx->skipspace;
67 14         122 $class->_inject($ctx, '(');
68             my $name = $cb_options->{ -only_anon }
69             ? undef
70 14 100       50 : $class->_strip_name_portion($ctx, $cb_options);
71 14         1031 my ($invocants, $parameters)
72             = $class->_strip_signature($ctx, $options, $cb_options);
73 14         45 my $attrs = $ctx->strip_attrs;
74 14 100       283 if (defined $name) {
75 10         196 $class->_inject($ctx, $name);
76 10         23 $class->_inject($ctx, ',');
77             }
78 14         38 $class->_inject($ctx, ' sub ');
79 14 50       44 $class->_inject($ctx, $attrs)
80             if defined($attrs);
81             $class->_inject($ctx, sprintf('BEGIN { %s->%s(%s) }; my (%s) = @_; (); ',
82             $class,
83             '_handle_scope_end',
84             defined($name) ? 1 : $cb_options->{ -stmt } ? 1 : 0,
85             join(', ',
86 14 100       102 @{ $cb_options->{ -before } || [] },
87             @$invocants,
88 14 100       38 @{ $cb_options->{ -middle } || [] },
  14 100       136  
    50          
89             @$parameters,
90             ),
91             ), 1);
92 14         208 return 1;
93             }
94              
95             sub _handle_scope_end {
96 14     14   848 my ($class, $end_stmt) = @_;
97             on_scope_end {
98 14     14   104 my $linestr = Devel::Declare::get_linestr;
99 14         27 my $offset = Devel::Declare::get_linestr_offset;
100 14 100       37 substr($linestr, $offset, 0) = $end_stmt ? ');' : ')';
101 14         42 Devel::Declare::set_linestr($linestr);
102 14         99 };
103 14         624 return 1;
104             }
105              
106             sub _inject {
107 76     76   115 my ($class, $ctx, $code, $into_block) = @_;
108 76         178 $ctx->skipspace;
109 76         706 my $linestr = $ctx->get_linestr;
110 76         743 my $reststr = substr $linestr, $ctx->offset;
111 76         293 my $skip = 0;
112 76 100       145 if ($into_block) {
113 14 50       62 croak sprintf q{Expected a block for '%s', not: %s},
114             $ctx->declarator,
115             $reststr,
116             unless $reststr =~ m{ \A \{ }x;
117 14         18 $skip = 1;
118             }
119 76         106 substr($reststr, $skip, 0) = $code;
120 76         166 substr($linestr, $ctx->offset) = $reststr;
121 76         393 $ctx->set_linestr($linestr);
122 76         489 $ctx->inc_offset($skip + length $code);
123 76         278 return 1;
124             }
125              
126             sub _strip_signature {
127 14     14   26 my ($class, $ctx, $options, $cb_options) = @_;
128 14         36 $ctx->skipspace;
129 14         123 my $invocant_option = $options->{ -invocant };
130 14 100       38 my @invocants = length($invocant_option)
131             ? ($invocant_option)
132             : ();
133 14 100       19 my @default = @{ $cb_options->{ -default } || [] };
  14         64  
134 14         40 my $signature = $ctx->strip_proto;
135 14 100 100     744 return [@invocants], [@default]
136             unless defined $signature and length $signature;
137 10         41 my @parts =
138 8         31 map { [ split m{ \s* , \s* }x, $_ ] }
139             split m{ \s* : \s* }x, $signature;
140 8 0       42 return @parts == 1 ? ([@invocants], @parts)
    50          
    100          
141             : @parts == 2 ? (@parts)
142             : @parts == 0 ? ([@invocants], [])
143             : croak q{Only expected to find a single ':' in signature};
144             }
145              
146             sub _strip_name_portion {
147 12     12   17 my ($class, $ctx, $options) = @_;
148 12         29 my $linestr = $ctx->get_linestr;
149 12 100       77 if (my $name = $ctx->strip_name) {
150 9         272 return pp($name);
151             }
152 3 100 66     67 if (
153             substr($linestr, $ctx->offset) =~ m{ \A " }x
154             and Devel::Declare::toke_scan_str $ctx->offset
155             ) {
156 1         21 my $string = Devel::Declare::get_lex_stuff;
157 1         3 Devel::Declare::clear_lex_stuff;
158 1         4 substr($linestr, $ctx->offset, 2 + length $string) = '';
159 1         8 $ctx->set_linestr($linestr);
160 1         7 return qq{"$string"};
161             }
162             else {
163             return undef
164 2 50       25 if $options->{-allow_anon};
165 0         0 croak sprintf q{Expected a name after '%s' keyword},
166             $ctx->declarator;
167             }
168             }
169              
170             sub _prepare_options {
171 6     6   9 my ($class, $options) = @_;
172 6 50       19 $options = {}
173             unless defined $options;
174 6 50       18 croak qq{Expected options for $class to be a hash ref}
175             unless is_ref $options, 'HASH';
176             $options->{ -invocant } = '$self'
177 6 100       23 unless defined $options->{ -invocant };
178             croak qq{Option -invocant for $class has to be filled string}
179 6 100       215 unless is_string $options->{ -invocant };
180             croak qq{Option -callbacks for $class has to be a hash ref}
181 5 100       135 unless is_ref $options->{ -callbacks };
182 4         8 return $options;
183             }
184              
185             1;
186              
187              
188              
189             =pod
190              
191             =head1 NAME
192              
193             Syntax::Feature::Sugar::Callbacks - Add sugar for declarative method callbacks
194              
195             =head1 VERSION
196              
197             version 0.002
198              
199             =head1 SYNOPSIS
200              
201             use AnythingExportingMethodModifiers;
202             use syntax 'sugar/callbacks' => {
203             -callbacks => {
204             after => {},
205             before => {},
206             around => { -before => ['$orig'] },
207             },
208             };
209              
210             after foo ($n) { $self->something($n) }
211             before bar ($n) { $self->something($n) }
212             around baz ($n) { $self->something($self->$orig($n)) }
213              
214             =head1 DESCRIPTION
215              
216             You probably won't use this extension directly. That's why it doesn't even
217             have an C method. Its main reasoning is the ability to provide
218             on-the-fly sugar for method declarators, most commonly C, C
219             and C. This extension will directly dispatch to the original
220             subroutine, and requires these to be setup before-hand. Currently, all
221             callbacks will first receive the name of the declared method, followed by
222             the code reference.
223              
224             Note that no cleanup of the original handlers will be performed. This is
225             up to the exporting library or the user.
226              
227             =head1 METHODS
228              
229             =head2 install
230              
231             $class->install( %arguments )
232              
233             Called by L (or others) to install this extension into a namespace.
234              
235             =head1 SYNTAX
236              
237             All declarations must currently be in one of the forms
238              
239             () { }
240             { }
241              
242             The C is the name of the declared callback. The C can either
243             be an identifier like you'd give to C, or a double-quoted string if
244             you want the name to be dynamic:
245              
246             after "$name" ($arg) { ... }
247              
248             The signature, if specified, should be in one of the following forms:
249              
250             ($foo)
251             ($foo, $bar)
252             ($class:)
253             ($class: $foo, $bar)
254              
255             Variables before C<:> will be used as replacement for the invocant.
256             Parameters specified via C<-before> and C<-middle> will always be included.
257              
258             The statement will automatically terminate after the block. The return
259             value will be whatever the original callback returns.
260              
261             You can supply subroutine attributes right before the block.
262              
263             =head1 OPTIONS
264              
265             =head2 -invocant
266              
267             Defaults to C<$self>, but you might want to change this for very specialized
268             classes.
269              
270             =head2 -callbacks
271              
272             This is the set of callbacks that should be setup. It should be a hash
273             reference using callback names as keys and hash references of options as
274             values. Possible per-callback options are
275              
276             =over
277              
278             =item C<-before>
279              
280             An array reference of variable names that come before the invocant. A
281             typical example would be the original code reference in C method
282             modifiers.
283              
284             =item C<-middle>
285              
286             An array reference of variable names that come after the invocants, but
287             before the parameters specified in the signature. Use this if the code
288             reference declared with the construct will receive a constant parameter.
289             There is no current way to override this in the signature on a per-construct
290             basis.
291              
292             =item C<-default>
293              
294             An array reference of variable names that are used when no signature was
295             provided. An empty signature will not lead to the defaults being used.
296              
297             =item C<-stmt>
298              
299             By default, anonymous constructs will not automatically terminate the
300             statement after the code block. If this option is set to a true value, all
301             uses of the construct will be terminated.
302              
303             =item C<-allow_anon>
304              
305             If set to a true value, anonymous versions of this construct can be
306             declared. If no name was specified, only the code reference will be passed
307             on to the callback.
308              
309             =item C<-only_anon>
310              
311             If set to a true value, a name will not be expected after the keyword and
312             before the signature.
313              
314             =back
315              
316             =head1 SEE ALSO
317              
318             =over
319              
320             =item * L
321              
322             =item * L
323              
324             =back
325              
326             =head1 BUGS
327              
328             Please report any bugs or feature requests to bug-syntax-feature-sugar-callbacks@rt.cpan.org or through the web interface at:
329             http://rt.cpan.org/Public/Dist/Display.html?Name=Syntax-Feature-Sugar-Callbacks
330              
331             =head1 AUTHOR
332              
333             Robert 'phaylon' Sedlacek
334              
335             =head1 COPYRIGHT AND LICENSE
336              
337             This software is copyright (c) 2011 by Robert 'phaylon' Sedlacek.
338              
339             This is free software; you can redistribute it and/or modify it under
340             the same terms as the Perl 5 programming language system itself.
341              
342             =cut
343              
344              
345             __END__