File Coverage

blib/lib/CGI/Application/Plugin/RunmodeParseKeyword.pm
Criterion Covered Total %
statement 207 225 92.0
branch 38 76 50.0
condition 10 20 50.0
subroutine 36 37 97.3
pod 9 9 100.0
total 300 367 81.7


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