File Coverage

blib/lib/Dancer2/Core/DSL.pm
Criterion Covered Total %
statement 145 206 70.3
branch 24 48 50.0
condition 1 5 20.0
subroutine 71 93 76.3
pod 2 83 2.4
total 243 435 55.8


line stmt bran cond sub pod time code
1             # ABSTRACT: Dancer2's Domain Specific Language (DSL)
2              
3             package Dancer2::Core::DSL;
4             $Dancer2::Core::DSL::VERSION = '2.0.1';
5 142     142   611278 use Moo;
  142         22037  
  142         1098  
6 142     142   70496 use Carp;
  142         348  
  142         15087  
7 142     142   2926 use Module::Runtime 'require_module';
  142         7597  
  142         2802  
8 142     142   12944 use Ref::Util qw< is_arrayref is_hashref >;
  142         10271  
  142         11432  
9 142     142   11707 use Dancer2::Core::Hook;
  142         355  
  142         5495  
10 142     142   9555 use Dancer2::FileUtils;
  142         359  
  142         7119  
11 142     142   81046 use Dancer2::Core::Response::Delayed;
  142         743  
  142         538906  
12              
13             with 'Dancer2::Core::Role::DSL';
14              
15 0     0 0 0 sub hook_aliases { +{} }
16 0     0 0 0 sub supported_hooks { () }
17              
18             sub _add_postponed_plugin_hooks {
19 0     0   0 my ( $self, $postponed_hooks) = @_;
20              
21 0         0 $postponed_hooks = $postponed_hooks->{'plugin'};
22 0 0       0 return unless defined $postponed_hooks;
23              
24 0         0 for my $plugin ( keys %{$postponed_hooks} ) {
  0         0  
25 0         0 for my $name ( keys %{$postponed_hooks->{$plugin} } ) {
  0         0  
26 0         0 my $hook = $postponed_hooks->{$plugin}{$name}{hook};
27 0         0 my $caller = $postponed_hooks->{$plugin}{$name}{caller};
28              
29             $self->has_hook($name)
30             or croak "plugin $plugin does not support the hook `$name'. ("
31 0 0       0 . join( ", ", @{$caller} ) . ")";
  0         0  
32              
33 0         0 $self->add_hook($hook);
34             }
35             }
36             }
37              
38             sub dsl_keywords {
39              
40             # the flag means : 1 = is global, 0 = is not global. global means can be
41             # called from anywhere. not global means must be called from within a route
42             # handler
43 269     269 0 39837 { any => { is_global => 1 },
44             app => { is_global => 1 },
45             captures => { is_global => 0 },
46             config => { is_global => 1 },
47             content => { is_global => 0 },
48             content_type => { is_global => 0 },
49             context => { is_global => 0 },
50             cookie => { is_global => 0 },
51             cookies => { is_global => 0 },
52             dance => { is_global => 1 },
53             dancer_app => { is_global => 1 },
54             dancer_version => { is_global => 1 },
55             dancer_major_version => { is_global => 1 },
56             debug => { is_global => 1 },
57             decode_json => { is_global => 1 },
58             del => { is_global => 1 },
59             delayed => {
60             is_global => 0, prototype => '&@',
61             },
62             dirname => { is_global => 1 },
63             done => { is_global => 0 },
64             dsl => { is_global => 1 },
65             encode_json => { is_global => 1 },
66             engine => { is_global => 1 },
67             error => { is_global => 1 },
68             false => { is_global => 1 },
69             flush => { is_global => 0 },
70             forward => { is_global => 0 },
71             from_dumper => { is_global => 1 },
72             from_json => { is_global => 1 },
73             from_yaml => { is_global => 1 },
74             get => { is_global => 1 },
75             halt => { is_global => 0 },
76             header => { is_global => 0 },
77             headers => { is_global => 0 },
78             hook => { is_global => 1 },
79             info => { is_global => 1 },
80             log => { is_global => 1 },
81             mime => { is_global => 1 },
82             options => { is_global => 1 },
83             param => { is_global => 0 },
84             params => { is_global => 0 },
85             query_parameters => { is_global => 0 },
86             body_parameters => { is_global => 0 },
87             route_parameters => { is_global => 0 },
88             pass => { is_global => 0 },
89             patch => { is_global => 1 },
90             path => { is_global => 1 },
91             post => { is_global => 1 },
92             prefix => { is_global => 1 },
93             prepare_app => {
94             is_global => 1, prototype => '&',
95             },
96             psgi_app => { is_global => 1 },
97             push_header => { is_global => 0 },
98             push_response_header => { is_global => 0 },
99             put => { is_global => 1 },
100             redirect => { is_global => 0 },
101             request => { is_global => 0 },
102             request_data => { is_global => 0 },
103             request_header => { is_global => 0 },
104             response => { is_global => 0 },
105             response_header => { is_global => 0 },
106             response_headers => { is_global => 0 },
107             runner => { is_global => 1 },
108             send_as => { is_global => 0 },
109             send_error => { is_global => 0 },
110             send_file => { is_global => 0 },
111             session => { is_global => 0 },
112             set => { is_global => 1 },
113             setting => { is_global => 1 },
114             splat => { is_global => 0 },
115             start => { is_global => 1 },
116             status => { is_global => 0 },
117             template => { is_global => 1 },
118             to_app => { is_global => 1 },
119             to_dumper => { is_global => 1 },
120             to_json => { is_global => 1 },
121             to_yaml => { is_global => 1 },
122             true => { is_global => 1 },
123             upload => { is_global => 0 },
124             uri_for => { is_global => 0 },
125             uri_for_route => { is_global => 0 },
126             var => { is_global => 0 },
127             vars => { is_global => 0 },
128             warning => { is_global => 1 },
129             };
130             }
131              
132 13     13 0 245 sub dancer_app { shift->app }
133 1     1 0 10 sub dancer_version { Dancer2->VERSION }
134              
135             sub dancer_major_version {
136 0     0 0 0 return ( split /\./, dancer_version )[0];
137             }
138              
139 0     0 0 0 sub log { shift->app->log( @_ ) }
140 3     3 0 28 sub debug { shift->app->log( debug => @_ ) }
141 1     1 0 10 sub info { shift->app->log( info => @_ ) }
142 5     5 0 42 sub warning { shift->app->log( warning => @_ ) }
143 2     2 0 16 sub error { shift->app->log( error => @_ ) }
144              
145 1     1 0 22 sub true {1}
146 0     0 0 0 sub false {0}
147              
148 0 0   0 0 0 sub dirname { shift and Dancer2::FileUtils::dirname(@_) }
149 0 0   0 0 0 sub path { shift and Dancer2::FileUtils::path(@_) }
150              
151 4     4 0 35 sub config { shift->app->settings }
152              
153 10     10 0 393 sub engine { shift->app->engine(@_) }
154              
155 153     153 1 1573 sub setting { shift->app->setting(@_) }
156              
157 137     137 1 706 sub set { shift->setting(@_) }
158              
159 28     28 0 262 sub template { shift->app->template(@_) }
160              
161             sub session {
162 127     127 0 418 my ( $self, $key, $value ) = @_;
163              
164             # shortcut reads if no session exists, so we don't
165             # instantiate sessions for no reason
166 127 100       447 if ( @_ == 2 ) {
167 56 100       1881 return unless $self->app->has_session;
168             }
169              
170 116   33     2879 my $session = $self->app->session
171             || croak "No session available, a session engine needs to be set";
172              
173 116         3583 $self->app->setup_session;
174              
175             # return the session object if no key
176 116 100       5612 @_ == 1 and return $session;
177              
178             # read if a key is provided
179 104 100       504 @_ == 2 and return $session->read($key);
180              
181              
182             # write to the session or delete if value is undef
183 59 100       176 if ( defined $value ) {
184 50         281 $session->write( $key => $value );
185             }
186             else {
187 9         40 $session->delete($key);
188             }
189             }
190              
191 11     11 0 89 sub send_as { shift->app->send_as(@_) }
192              
193 8     8 0 66 sub send_error { shift->app->send_error(@_) }
194              
195 11     11 0 102 sub send_file { shift->app->send_file(@_) }
196              
197             #
198             # route handlers & friends
199             #
200              
201             sub hook {
202 78     78 0 503 my ( $self, $name, $code ) = @_;
203 78         2597 $self->app->add_hook(
204             Dancer2::Core::Hook->new( name => $name, code => $code ) );
205             }
206              
207             sub prefix {
208 4     4 0 21 my $app = shift->app;
209 4 100       67 @_ == 1
210             ? $app->prefix(@_)
211             : $app->lexical_prefix(@_);
212             }
213              
214 7     7 0 51 sub halt { shift->app->halt(@_) }
215              
216 1     1 0 7 sub del { shift->_normalize_route( [qw/delete /], @_ ) }
217 286     286 0 2087 sub get { shift->_normalize_route( [qw/get head/], @_ ) }
218 1     1 0 8 sub options { shift->_normalize_route( [qw/options /], @_ ) }
219 2     2 0 15 sub patch { shift->_normalize_route( [qw/patch /], @_ ) }
220 35     35 0 251 sub post { shift->_normalize_route( [qw/post /], @_ ) }
221 3     3 0 20 sub put { shift->_normalize_route( [qw/put /], @_ ) }
222              
223 2     2 0 5 sub prepare_app { push @{ shift->app->prep_apps }, @_ }
  2         23  
224              
225             sub any {
226 7     7 0 21 my $self = shift;
227              
228             # If they've supplied their own list of methods,
229             # expand del, otherwise give them the default list.
230 7 100       42 if ( is_arrayref($_[0]) ) {
231 3         7 s/^del$/delete/ for @{ $_[0] };
  3         22  
232             }
233             else {
234 4         26 unshift @_, [qw/delete get head options patch post put/];
235             }
236              
237 7         38 $self->_normalize_route(@_);
238             }
239              
240             sub _normalize_route {
241 335     335   2406 my $app = shift->app;
242 335         1075 my $methods = shift;
243 335         750 my %args;
244              
245             # Options are optional, try to deduce their presence from arg length.
246 335 100       2395 if ( @_ == 4 ) {
    100          
    50          
247             # @_ = ( NAME, REGEXP, OPTIONS, CODE )
248             # get 'foo', '/foo', { 'user_agent' => '...' }, sub {...}
249 3         18 @args{qw} = @_;
250             } elsif ( @_ == 2 ) {
251             # @_ = ( REGEXP, CODE )
252             # get '/foo', sub {...}
253 324         1959 @args{qw} = @_;
254             } elsif ( @_ == 3 ) {
255             # @_ = ( REGEXP, OPTIONS, CODE )
256             # get '/foo', { 'user_agent' => '...' }, sub {...}
257             # @_ = ( NAME, REGEXP, CODE )
258             # get 'foo', '/foo',sub {...}
259 8 50       51 if ( is_hashref( $_[1] ) ) {
260 0         0 @args{qw} = @_;
261             } else {
262 8         45 @args{qw} = @_;
263             }
264             }
265              
266 335         715 return map $app->add_route( %args, method => $_ ), @{$methods};
  335         2978  
267             }
268              
269             #
270             # Server startup
271             #
272              
273             # access to the runner singleton
274             # will be populated on-the-fly when needed
275             # this singleton contains anything needed to start the application server
276 1     1 0 10 sub runner { Dancer2->runner }
277              
278             # start the server
279 0     0 0 0 sub start { shift->runner->start }
280              
281 0     0 0 0 sub dance { shift->start(@_) }
282              
283             sub psgi_app {
284 2     2 0 7 my $self = shift;
285              
286 2         20 $self->app->to_app;
287             }
288              
289 151     151 0 1666 sub to_app { shift->app->to_app }
290              
291             #
292             # Response alterations
293             #
294              
295             sub status {
296 8     8 0 229 $Dancer2::Core::Route::RESPONSE->status( $_[1] );
297             }
298              
299             sub push_header {
300 0     0 0 0 Carp::croak "DEPRECATED: push_header keyword. Please use the 'push_response_header' keyword instead of 'push_header'";
301             }
302              
303             sub push_response_header {
304 0     0 0 0 shift;
305 0         0 $Dancer2::Core::Route::RESPONSE->push_header(@_);
306             }
307              
308             sub header {
309 0     0 0 0 Carp::croak "DEPRECATED: header keyword. Please use the 'response_header' keyword instead of 'header'";
310             }
311              
312             sub response_header {
313 4     4 0 14 shift;
314 4         126 $Dancer2::Core::Route::RESPONSE->header(@_);
315             }
316              
317             sub headers {
318 0     0 0 0 Carp::croak "DEPRECATED: headers keyword. Please use the 'response_headers' keyword instead of 'headers'";
319             }
320              
321             sub response_headers {
322 0     0 0 0 shift;
323 0         0 $Dancer2::Core::Route::RESPONSE->header(@_);
324             }
325              
326             sub content {
327 1     1 0 3 my $dsl = shift;
328              
329             # simple synchronous response
330 1 50       270 my $responder = $Dancer2::Core::Route::RESPONDER
331             or croak 'Cannot use content keyword outside delayed response';
332              
333             # flush if wasn't flushed before
334 0 0       0 if ( !$Dancer2::Core::Route::WRITER ) {
335 0         0 $Dancer2::Core::Route::WRITER = $responder->([
336             $Dancer2::Core::Route::RESPONSE->status,
337             $Dancer2::Core::Route::RESPONSE->headers_to_array,
338             ]);
339             }
340              
341             eval {
342 0         0 $Dancer2::Core::Route::WRITER->write(@_);
343 0         0 1;
344 0 0       0 } or do {
345 0   0     0 my $error = $@ || 'Zombie Error';
346 0 0       0 $Dancer2::Core::Route::ERROR_HANDLER
347             ? $Dancer2::Core::Route::ERROR_HANDLER->($error)
348             : $dsl->app->logger_engine->log(
349             warning => "Error in delayed response: $error"
350             );
351             };
352             }
353              
354             sub content_type {
355 0     0 0 0 shift;
356 0         0 $Dancer2::Core::Route::RESPONSE->content_type(@_);
357             }
358              
359             sub delayed {
360 1     1 0 4 my ( $dsl, $cb, @args ) = @_;
361              
362 1 50       6 @args % 2 == 0
363             or croak 'Arguments to delayed() keyword must be key/value pairs';
364              
365             # first time, responder doesn't exist yet
366 1         4 my %opts = @args;
367             $Dancer2::Core::Route::RESPONDER
368             or return Dancer2::Core::Response::Delayed->new(
369             cb => $cb,
370             request => $Dancer2::Core::Route::REQUEST,
371             response => $Dancer2::Core::Route::RESPONSE,
372              
373 1 50       20 ( error_cb => $opts{'on_error'} )x!! $opts{'on_error'},
374             );
375              
376             # we're in an async request process
377 0         0 my $request = $Dancer2::Core::Route::REQUEST;
378 0         0 my $response = $Dancer2::Core::Route::RESPONSE;
379 0         0 my $responder = $Dancer2::Core::Route::RESPONDER;
380 0         0 my $writer = $Dancer2::Core::Route::WRITER;
381 0         0 my $handler = $Dancer2::Core::Route::ERROR_HANDLER;
382              
383             return sub {
384 0     0   0 local $Dancer2::Core::Route::REQUEST = $request;
385 0         0 local $Dancer2::Core::Route::RESPONSE = $response;
386 0         0 local $Dancer2::Core::Route::RESPONDER = $responder;
387 0         0 local $Dancer2::Core::Route::WRITER = $writer;
388 0         0 local $Dancer2::Core::Route::ERROR_HANDLER = $handler;
389              
390 0         0 $cb->(@_);
391 0         0 };
392             }
393              
394             sub flush {
395 0 0   0 0 0 my $responder = $Dancer2::Core::Route::RESPONDER
396             or croak 'flush() called outside streaming response';
397              
398 0         0 my $response = $Dancer2::Core::Route::RESPONSE;
399 0         0 $Dancer2::Core::Route::WRITER = $responder->([
400             $response->status, $response->headers_to_array,
401             ]);
402             }
403              
404             sub done {
405 0 0   0 0 0 my $writer = $Dancer2::Core::Route::WRITER
406             or croak 'done() called outside streaming response';
407              
408 0         0 $writer->close;
409             }
410              
411 2     2 0 23 sub pass { shift->app->pass }
412              
413             #
414             # Route handler helpers
415             #
416              
417             sub context {
418 0     0 0 0 Carp::croak "DEPRECATED: context keyword. Please use the 'app' keyword instead of 'context'";
419             }
420              
421 103     103 0 452 sub request { $Dancer2::Core::Route::REQUEST }
422              
423 1     1 0 3 sub request_header { shift; $Dancer2::Core::Route::REQUEST->headers->header(@_) }
  1         7  
424              
425 7     7 0 211 sub response { $Dancer2::Core::Route::RESPONSE }
426              
427 2     2 0 6 sub upload { shift; $Dancer2::Core::Route::REQUEST->upload(@_); }
  2         12  
428              
429 2     2 0 12 sub captures { $Dancer2::Core::Route::REQUEST->captures }
430              
431 4     4 0 10 sub uri_for { shift; $Dancer2::Core::Route::REQUEST->uri_for(@_); }
  4         20  
432              
433 18     18 0 119 sub uri_for_route { shift->app->uri_for_route(@_); }
434              
435 31     31 0 203 sub splat { $Dancer2::Core::Route::REQUEST->splat }
436              
437 41     41 0 79 sub params { shift; $Dancer2::Core::Route::REQUEST->params(@_); }
  41         219  
438              
439 6     6 0 10 sub param { shift; $Dancer2::Core::Route::REQUEST->param(@_); }
  6         27  
440              
441 3     3 0 5 sub query_parameters { shift; $Dancer2::Core::Route::REQUEST->query_parameters(@_); }
  3         28  
442 5     5 0 30 sub body_parameters { shift; $Dancer2::Core::Route::REQUEST->body_parameters(@_); }
  5         19  
443 14     14 0 35 sub route_parameters { shift; $Dancer2::Core::Route::REQUEST->route_parameters(@_); }
  14         79  
444              
445 2     2 0 5 sub request_data { shift; $Dancer2::Core::Route::REQUEST->body_data(@_); }
  2         11  
446              
447 26     26 0 235 sub redirect { shift->app->redirect(@_) }
448              
449 44     44 0 368 sub forward { shift->app->forward(@_) }
450              
451 12     12 0 44 sub vars { $Dancer2::Core::Route::REQUEST->vars }
452              
453 8     8 0 39 sub var { shift; $Dancer2::Core::Route::REQUEST->var(@_); }
  8         40  
454              
455 0     0 0 0 sub cookies { $Dancer2::Core::Route::REQUEST->cookies }
456 0     0 0 0 sub cookie { shift->app->cookie(@_) }
457              
458             sub mime {
459 2     2 0 5 my $self = shift;
460 2 50       28 if ( $self->app ) {
461 2         14 return $self->app->mime_type;
462             }
463             else {
464 0         0 my $runner = $self->runner;
465 0         0 $runner->mime_type->reset_default;
466 0         0 return $runner->mime_type;
467             }
468             }
469              
470             #
471             # engines
472             #
473              
474             sub from_json {
475 6     6 0 13 shift; # remove first element
476 6         45 require_module('Dancer2::Serializer::JSON');
477 6         185 Dancer2::Serializer::JSON::from_json(@_);
478             }
479              
480             sub to_json {
481 21     21 0 40 shift; # remove first element
482 21         91 require_module('Dancer2::Serializer::JSON');
483 21         538 Dancer2::Serializer::JSON::to_json(@_);
484             }
485              
486             sub decode_json {
487 1     1 0 4 shift; # remove first element
488 1         3 require_module('Dancer2::Serializer::JSON');
489 1         16 Dancer2::Serializer::JSON::decode_json(@_);
490             }
491              
492             sub encode_json {
493 2     2 0 4 shift; # remove first element
494 2         18 require_module('Dancer2::Serializer::JSON');
495 2         50 Dancer2::Serializer::JSON::encode_json(@_);
496             }
497              
498             sub from_yaml {
499 4     4 0 9 shift; # remove first element
500 4         27 require_module('Dancer2::Serializer::YAML');
501 4         166 Dancer2::Serializer::YAML::from_yaml(@_);
502             }
503              
504             sub to_yaml {
505 11     11 0 32 shift; # remove first element
506 11         69 require_module('Dancer2::Serializer::YAML');
507 11         366 Dancer2::Serializer::YAML::to_yaml(@_);
508             }
509              
510             sub from_dumper {
511 2     2 0 6 shift; # remove first element
512 2         12 require_module('Dancer2::Serializer::Dumper');
513 2         77 Dancer2::Serializer::Dumper::from_dumper(@_);
514             }
515              
516             sub to_dumper {
517 7     7 0 16 shift; # remove first element
518 7         39 require_module('Dancer2::Serializer::Dumper');
519 7         208 Dancer2::Serializer::Dumper::to_dumper(@_);
520             }
521              
522             1;
523              
524             __END__