File Coverage

blib/lib/CGI/Application/Plugin/RunmodeParseKeyword.pm
Criterion Covered Total %
statement 201 219 91.7
branch 39 76 51.3
condition 8 17 47.0
subroutine 35 36 97.2
pod 9 9 100.0
total 292 357 81.7


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