File Coverage

blib/lib/CGI/Application/Plugin/RunmodeParseKeyword.pm
Criterion Covered Total %
statement 208 225 92.4
branch 44 78 56.4
condition 14 22 63.6
subroutine 35 36 97.2
pod 9 9 100.0
total 310 370 83.7


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::RunmodeParseKeyword;
2             $CGI::Application::Plugin::RunmodeParseKeyword::VERSION = '0.15';
3 9     9   2552660 use warnings;
  9         24  
  9         675  
4 9     9   55 use strict;
  9         17  
  9         515  
5              
6             =head1 NAME
7              
8             CGI::Application::Plugin::RunmodeParseKeyword - Declare runmodes using Parse::Keyword
9              
10             =cut
11              
12             our @EXPORT = qw(runmode errormode startmode);
13 9     9   86 use Carp qw(croak);
  9         19  
  9         757  
14 9     9   5145 use Sub::Name 'subname';
  9         6676  
  9         698  
15 9     9   5240 use Data::Dumper;
  9         74618  
  9         795  
16 9     9   4763 use Parse::Keyword {};
  9         40579  
  9         65  
17              
18             $Carp::Internal{ (__PACKAGE__) }++;
19              
20             sub import {
21 14     14   13971 my $caller = caller;
22 14         39 my $class = shift;
23 14         46 my %args = @_;
24 14   66     88 my $into = delete $args{into} || $caller;
25 14   100     71 my $inv = delete $args{invocant} || '$self';
26              
27 14     14   183 Parse::Keyword->import( { runmode => sub { my ($kw) = @_; parse_mode($kw, $inv); } } );
  14         7056  
  14         37  
28 14     6   1510 Parse::Keyword->import( { errormode => sub { my ($kw) = @_; parse_mode($kw, $inv); } } );
  6         5333  
  6         23  
29 14     8   1357 Parse::Keyword->import( { startmode => sub { my ($kw) = @_; parse_mode($kw, $inv); } } );
  8         8099  
  8         29  
30              
31 14         1171 for my $e(@EXPORT) {
32 42         86 my $fn = $into . '::' . $e;
33 9     9   4008 no strict 'refs';
  9         21  
  9         2948  
34 42         249 *$fn = \&$e;
35 42         553 *$fn if 0;
36             }
37             }
38              
39 11 50   11 1 40 sub runmode { @_ ? $_[0] : () }
40 6 50   6 1 24 sub errormode { @_ ? $_[0] : () }
41 8 50   8 1 69 sub startmode { @_ ? $_[0] : () }
42              
43             my %REGISTRY;
44             sub _setup_runmode {
45 11     11   50 my ($pkg, $name, $code) = @_;
46 11     33   75 $pkg->add_callback( init => sub { $_[0]->run_modes([ $name ]) } );
  33         1108  
47             }
48             sub _setup_startmode {
49 8     8   29 my ($pkg, $name, $code) = @_;
50 9     9   76 no strict 'refs'; no warnings 'uninitialized';
  9     9   42  
  9         488  
  9         81  
  9         32  
  9         2739  
51             # compile time check
52 8 50       44 croak "start mode redefined (from $REGISTRY{$pkg}{start_mode_installed})" if $REGISTRY{$pkg}{start_mode_installed};
53             $pkg->add_callback(
54             init => sub {
55             # run time check
56 26 100   26   87197 return if exists $_[0]->{__START_MODE_SET_BY_RUNMODEDECLARE};
57 16         132 $_[0]->run_modes( [$name] );
58 16         406 $_[0]->start_mode($name);
59 16         183 $_[0]->{__START_MODE_SET_BY_RUNMODEDECLARE} = 1;
60             }
61 8         104 );
62 8         173 $REGISTRY{$pkg}{start_mode_installed} = join '::', $pkg, $name;
63             }
64             sub _setup_errormode {
65 6     6   29 my ($pkg, $name, $code) = @_;
66 9     9   69 no strict 'refs'; no warnings 'uninitialized';
  9     9   37  
  9         428  
  9         47  
  9         20  
  9         3675  
67 6 50       36 croak "error mode redefined (from $REGISTRY{$pkg}{error_mode_installed})" if $REGISTRY{$pkg}{error_mode_installed};
68             $pkg->add_callback(
69             init => sub {
70 19 100   19   7324 return if exists $_[0]->{__ERROR_MODE_SET_BY_RUNMODEDECLARE};
71 15         73 $_[0]->error_mode($name);
72 15         195 $_[0]->{__ERROR_MODE_SET_BY_RUNMODEDECLARE} = 1;
73             }
74 6         71 );
75 6         174 $REGISTRY{$pkg}{error_mode_installed} = join '::', $pkg, $name;
76             }
77              
78             =begin pod-coverage
79              
80             =over 4
81              
82             =item parse_mode - we hook into this to install cgiapp callbacks
83              
84             =item parse_name - identifier name parser
85              
86             =item parse_signature - runmode signature parser
87              
88             =item parse_attribute - parse one sub attr
89              
90             =item parse_attributes - parse sub attrs
91              
92             =item parse_body - parse code and inject defaults
93              
94             =back
95              
96             =end pod-coverage
97              
98             =cut
99              
100             sub parse_mode {
101 28     28 1 76 my ($keyword, $invocant) = @_;
102              
103 28         72 my $name = parse_name();
104 28         87 my $sig = parse_signature($invocant);
105 27         91 my $attr = parse_attributes();
106 27 100 50     109 my $body = parse_body($sig) or ($@ and die);
107              
108 25 50       90 if (defined $name) {
109 25         124 my $full_name = join('::', compiling_package, $name);
110             {
111 9     9   91 no strict 'refs';
  9         21  
  9         664  
  25         46  
112 25         255 *$full_name = subname $full_name, $body;
113 25 100       74 if ($attr) {
114 9     9   6157 use attributes ();
  9         15726  
  9         18170  
115 1         6 attributes->import(compiling_package, $body, $_) for @$attr;
116             }
117 25         357 my $setup = '_setup_' . $keyword;
118 25         113 $setup->(compiling_package, $name, $body);
119              
120             }
121 25     25   960 return (sub {}, 1);
122             }
123             else {
124 0     0   0 return (sub { $body }, 0);
  0         0  
125             }
126             }
127              
128             my $start_rx = qr/^[\p{ID_Start}_]$/;
129             my $cont_rx = qr/^\p{ID_Continue}$/;
130              
131             sub parse_name {
132 59     59 1 132 my $name = '';
133              
134 59         157 lex_read_space;
135              
136 59         114 my $char_rx = $start_rx;
137              
138 59         100 while (1) {
139 304         640 my $char = lex_peek;
140 304 50       704 last unless length $char;
141 304 100       1330 if ($char =~ $char_rx) {
142 245         433 $name .= $char;
143 245         603 lex_read;
144 245         434 $char_rx = $cont_rx;
145             }
146             else {
147 59         165 last;
148             }
149             }
150              
151 59 100       185 return length($name) ? $name : undef;
152             }
153              
154             sub parse_signature {
155 28     28 1 61 my ($invocant_name) = @_;
156 28         88 lex_read_space;
157              
158 28         123 my @vars = ({ index => 0, name => $invocant_name });
159 28 0       103 return \@vars unless lex_peek eq '(';
160              
161 19         59 my @attr = ();
162              
163 19         53 lex_read;
164 19         59 lex_read_space;
165              
166 19 0       103 if (lex_peek eq ')') {
167 1         3 lex_read;
168 1         3 return \@vars;
169             }
170              
171 18         60 my $seen_slurpy;
172 18         60 while ((my $sigil = lex_peek) ne ')') {
173 29         70 my $var = {};
174 29 50 66     132 die "syntax error: expected ')' but found '$sigil'"
      33        
175             unless $sigil eq '$' || $sigil eq '@' || $sigil eq '%';
176 29 50       73 die "Can't declare parameters after a slurpy parameter"
177             if $seen_slurpy;
178              
179 29 100 66     130 $seen_slurpy = 1 if $sigil eq '@' || $sigil eq '%';
180              
181 29         73 lex_read;
182 29         69 lex_read_space;
183 29         78 my $name = parse_name(0);
184 29         144 lex_read_space;
185              
186 29         89 $var->{name} = "$sigil$name";
187              
188 29 0       92 if (lex_peek eq '=') {
189 3         14 lex_read;
190 3         8 lex_read_space;
191 3         75 $var->{default} = parse_arithexpr;
192             }
193              
194 29         116 $var->{index} = @vars - 1;
195              
196 29 0       72 if (lex_peek eq ':') {
197 0         0 $vars[0] = $var;
198 0         0 lex_read;
199 0         0 lex_read_space;
200 0         0 next;
201             }
202              
203 29         73 push @vars, $var;
204              
205 29         66 my $c = lex_peek;
206 29 100 100     150 die "syntax error: expected ')' or ',' but found '$c'"
207             unless $c eq ')' || $c eq ',';
208              
209 28 0       75 if (lex_peek eq ',') {
210 11         37 lex_read;
211 11         45 lex_read_space;
212             }
213             }
214              
215 17         1845 lex_read;
216              
217 17         47 return \@vars;
218             }
219              
220             # grabbed these two functions from
221             # https://metacpan.org/release/PEVANS/XS-Parse-Keyword-0.22/source/hax/lexer-additions.c.inc#L74
222             sub parse_attribute {
223 2     2 1 5 my $name = parse_name;
224 2 0       6 if (lex_peek ne '(') {
225 1         5 return $name;
226             }
227 1         2 $name .= lex_peek;
228 1         3 lex_read;
229 1         1 my $count = 1;
230 1         2 my $c = lex_peek;
231 1   33     19 while($count && length $c) {
232 8 50       20 if($c eq '(') {
233 0         0 $count++;
234             }
235 8 100       16 if($c eq ')') {
236 1         3 $count--;
237             }
238 8 50       16 if($c eq '\\') {
239             # The next char does not bump count even if it is ( or );
240             # the \\ is still captured
241             #
242 0         0 $name .= $c;
243 0         0 lex_read;
244 0         0 $c = lex_peek;
245 0 0       0 if(! length $c) {
246 0         0 goto unterminated;
247             }
248             }
249              
250             # Don't append final closing ')' on split name/val
251 8         13 $name .= $c;
252 8         19 lex_read;
253              
254 8         19 $c = lex_peek;
255             }
256              
257 1 50       4 if(!length $c) {
258 0         0 return;
259             }
260              
261 1         5 return $name;
262              
263 0         0 unterminated:
264             croak("Unterminated attribute parameter in attribute list");
265 0         0 return;
266             }
267              
268             sub parse_attributes {
269 27     27 1 104 lex_read_space;
270 27 0       66 return unless lex_peek eq ':';
271 1         3 lex_read;
272 1         2 lex_read_space;
273 1         1 my @attrs;
274 1         4 while (my $attr = parse_attribute) {
275 1         3 push @attrs, $attr;
276 1         4 lex_read_space;
277 1 0       3 if (lex_peek eq ':') {
278 0         0 lex_read;
279 0         0 lex_read_space;
280             }
281             }
282              
283 1         3 return \@attrs;
284             }
285              
286             sub parse_body {
287 27     27 1 72 my $sigs = shift;
288 27         44 my $body;
289              
290 27         64 lex_read_space;
291              
292 27         107 my $c = lex_peek;
293 27 100       259 croak "syntax error: expected start of block '{' but found '$c'" unless $c eq '{';
294             {
295 26         40 local $CAPRPK::{'DEFAULTS::'};
  26         80  
296 26 50       69 if ($sigs) {
297 26         101 lex_read;
298              
299 26         72 my $preamble = '{';
300              
301             # invocant
302 26         58 my $inv = shift @$sigs;
303 26         66 $preamble .= "my $inv->{name} = shift;";
304              
305             # arguments / query params
306 26         78 my @names = map { $_->{name} } @$sigs;
  27         116  
307 26         84 $preamble .= 'my (' . join(', ', @names) . ') = @_;';
308              
309 26         64 for my $name (@names) {
310 27         43 my $p;
311 27         57 my $s = substr($name,0,1);
312 27         2218 my $n = substr($name,1);
313 27 100       77 if ($s eq '$') {
314 21         43 $p = $inv->{name} . '->param("' . $n . '")';
315 21 50       77 $preamble .= $name . ' = ' . $p . ' unless ' . ( $s eq '$' ? 'defined ' : 'scalar ') . $name . ';';
316             }
317 27 100       78 $p = $inv->{name} . '->query->' . ($s eq '@' ? 'multi_param' : 'param') . '("' . $n . '")';
318 27 100       105 $preamble .= $name . ' = ' . $p . ' unless ' . ( $s eq '$' ? 'defined ' : 'scalar ') . $name . ';';
319 27 100       83 if ($s eq '@') {
320 6 50       24 $p = $inv->{name} . '->query->' . ($s eq '@' ? 'multi_param' : 'param') . '("' . $n . '[]")';
321 6 50       37 $preamble .= $name . ' = ' . $p . ' unless ' . ( $s eq '$' ? 'defined ' : 'scalar ') . $name . ';';
322             }
323             }
324              
325 26         61 my $index = 0;
326 26         81 for my $var (grep { defined $_->{default} } @$sigs) {
  27         83  
327             {
328 9     9   82 no strict 'refs';
  9         20  
  9         2257  
  3         6  
329 3         51 *{ 'CAPRPK::DEFAULTS::default_' . $index } = sub () {
330             $var->{default}
331 3     3   11 };
  3         58198  
332             }
333 3         9 $preamble .= $var->{name} . ' = CAPRPK::DEFAULTS::default_' . $index . '->()' . ' unless ' . $var->{name} . ';';
334              
335 3         6 $index++;
336             }
337              
338             # warn $preamble . $/;
339 26         125 lex_stuff($preamble);
340             }
341 26         3462 $body = parse_block;
342             }
343 26         180 return $body;
344             }
345              
346             1;
347              
348             __END__