File Coverage

blib/lib/Dancer2/Core/DSL.pm
Criterion Covered Total %
statement 145 207 70.0
branch 24 50 48.0
condition 1 5 20.0
subroutine 71 94 75.5
pod 2 83 2.4
total 243 439 55.3


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.1.0';
5 145     145   477711 use Moo;
  145         18366  
  145         2835  
6 145     145   66991 use Carp;
  145         365  
  145         13422  
7 145     145   13828 use Path::Tiny ();
  145         220332  
  145         5376  
8 145     145   2602 use Module::Runtime 'require_module';
  145         5823  
  145         1322  
9 145     145   11122 use Ref::Util qw< is_arrayref is_hashref >;
  145         7522  
  145         10153  
10 145     145   8728 use Dancer2::Core::Hook;
  145         478  
  145         4925  
11 145     145   80692 use Dancer2::Core::Response::Delayed;
  145         627  
  145         533096  
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 275     275 0 40334 { 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 26 sub debug { shift->app->log( debug => @_ ) }
141 1     1 0 37 sub info { shift->app->log( info => @_ ) }
142 5     5 0 55 sub warning { shift->app->log( warning => @_ ) }
143 2     2 0 14 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 sub _path_obj { shift and Path::Tiny::path(@_) }
149 0 0   0 0 0 sub dirname { shift and _path_obj(@_)->parent->stringify }
150 0 0   0 0 0 sub path { shift and _path_obj(@_)->stringify }
151              
152 4     4 0 30 sub config { shift->app->settings }
153              
154 10     10 0 131 sub engine { shift->app->engine(@_) }
155              
156 155     155 1 1478 sub setting { shift->app->setting(@_) }
157              
158 139     139 1 1003 sub set { shift->setting(@_) }
159              
160 28     28 0 449 sub template { shift->app->template(@_) }
161              
162             sub session {
163 127     127 0 343 my ( $self, $key, $value ) = @_;
164              
165             # shortcut reads if no session exists, so we don't
166             # instantiate sessions for no reason
167 127 100       334 if ( @_ == 2 ) {
168 56 100       278 return unless $self->app->has_session;
169             }
170              
171 116   33     2261 my $session = $self->app->session
172             || croak "No session available, a session engine needs to be set";
173              
174 116         3297 $self->app->setup_session;
175              
176             # return the session object if no key
177 116 100       4604 @_ == 1 and return $session;
178              
179             # read if a key is provided
180 104 100       431 @_ == 2 and return $session->read($key);
181              
182              
183             # write to the session or delete if value is undef
184 59 100       263 if ( defined $value ) {
185 50         341 $session->write( $key => $value );
186             }
187             else {
188 9         35 $session->delete($key);
189             }
190             }
191              
192 11     11 0 86 sub send_as { shift->app->send_as(@_) }
193              
194 8     8 0 86 sub send_error { shift->app->send_error(@_) }
195              
196 12     12 0 94 sub send_file { shift->app->send_file(@_) }
197              
198             #
199             # route handlers & friends
200             #
201              
202             sub hook {
203 78     78 0 280 my ( $self, $name, $code ) = @_;
204 78         2410 $self->app->add_hook(
205             Dancer2::Core::Hook->new( name => $name, code => $code ) );
206             }
207              
208             sub prefix {
209 4     4 0 19 my $app = shift->app;
210 4 100       50 @_ == 1
211             ? $app->prefix(@_)
212             : $app->lexical_prefix(@_);
213             }
214              
215 7     7 0 67 sub halt { shift->app->halt(@_) }
216              
217 1     1 0 8 sub del { shift->_normalize_route( [qw/delete /], @_ ) }
218 291     291 0 1852 sub get { shift->_normalize_route( [qw/get head/], @_ ) }
219 1     1 0 5 sub options { shift->_normalize_route( [qw/options /], @_ ) }
220 2     2 0 15 sub patch { shift->_normalize_route( [qw/patch /], @_ ) }
221 35     35 0 247 sub post { shift->_normalize_route( [qw/post /], @_ ) }
222 3     3 0 21 sub put { shift->_normalize_route( [qw/put /], @_ ) }
223              
224 2     2 0 6 sub prepare_app { push @{ shift->app->prep_apps }, @_ }
  2         25  
225              
226             sub any {
227 7     7 0 19 my $self = shift;
228              
229             # If they've supplied their own list of methods,
230             # expand del, otherwise give them the default list.
231 7 100       43 if ( is_arrayref($_[0]) ) {
232 3         7 s/^del$/delete/ for @{ $_[0] };
  3         20  
233             }
234             else {
235 4         24 unshift @_, [qw/delete get head options patch post put/];
236             }
237              
238 7         35 $self->_normalize_route(@_);
239             }
240              
241             sub _normalize_route {
242 340     340   2180 my $app = shift->app;
243 340         829 my $methods = shift;
244 340         805 my %args;
245              
246             # Options are optional, try to deduce their presence from arg length.
247 340 100       2118 if ( @_ == 4 ) {
    100          
    50          
248             # @_ = ( NAME, REGEXP, OPTIONS, CODE )
249             # get 'foo', '/foo', { 'user_agent' => '...' }, sub {...}
250 3         46 @args{qw} = @_;
251             } elsif ( @_ == 2 ) {
252             # @_ = ( REGEXP, CODE )
253             # get '/foo', sub {...}
254 329         2063 @args{qw} = @_;
255             } elsif ( @_ == 3 ) {
256             # @_ = ( REGEXP, OPTIONS, CODE )
257             # get '/foo', { 'user_agent' => '...' }, sub {...}
258             # @_ = ( NAME, REGEXP, CODE )
259             # get 'foo', '/foo',sub {...}
260 8 50       29 if ( is_hashref( $_[1] ) ) {
261 0         0 @args{qw} = @_;
262             } else {
263 8         43 @args{qw} = @_;
264             }
265             }
266              
267 340         743 return map $app->add_route( %args, method => $_ ), @{$methods};
  340         2590  
268             }
269              
270             #
271             # Server startup
272             #
273              
274             # access to the runner singleton
275             # will be populated on-the-fly when needed
276             # this singleton contains anything needed to start the application server
277 1     1 0 6 sub runner { Dancer2->runner }
278              
279             # start the server
280 0     0 0 0 sub start { shift->runner->start }
281              
282 0     0 0 0 sub dance { shift->start(@_) }
283              
284             sub psgi_app {
285 2     2 0 5 my $self = shift;
286              
287 2         35 $self->app->to_app;
288             }
289              
290 154     154 0 1653 sub to_app { shift->app->to_app }
291              
292             #
293             # Response alterations
294             #
295              
296             sub status {
297 8     8 0 241 $Dancer2::Core::Route::RESPONSE->status( $_[1] );
298             }
299              
300             sub push_header {
301 0     0 0 0 Carp::croak "DEPRECATED: push_header keyword. Please use the 'push_response_header' keyword instead of 'push_header'";
302             }
303              
304             sub push_response_header {
305 0     0 0 0 shift;
306 0         0 $Dancer2::Core::Route::RESPONSE->push_header(@_);
307             }
308              
309             sub header {
310 0     0 0 0 Carp::croak "DEPRECATED: header keyword. Please use the 'response_header' keyword instead of 'header'";
311             }
312              
313             sub response_header {
314 4     4 0 11 shift;
315 4         95 $Dancer2::Core::Route::RESPONSE->header(@_);
316             }
317              
318             sub headers {
319 0     0 0 0 Carp::croak "DEPRECATED: headers keyword. Please use the 'response_headers' keyword instead of 'headers'";
320             }
321              
322             sub response_headers {
323 0     0 0 0 shift;
324 0         0 $Dancer2::Core::Route::RESPONSE->header(@_);
325             }
326              
327             sub content {
328 1     1 0 2 my $dsl = shift;
329              
330             # simple synchronous response
331 1 50       345 my $responder = $Dancer2::Core::Route::RESPONDER
332             or croak 'Cannot use content keyword outside delayed response';
333              
334             # flush if wasn't flushed before
335 0 0       0 if ( !$Dancer2::Core::Route::WRITER ) {
336 0         0 $Dancer2::Core::Route::WRITER = $responder->([
337             $Dancer2::Core::Route::RESPONSE->status,
338             $Dancer2::Core::Route::RESPONSE->headers_to_array,
339             ]);
340             }
341              
342             eval {
343 0         0 $Dancer2::Core::Route::WRITER->write(@_);
344 0         0 1;
345 0 0       0 } or do {
346 0   0     0 my $error = $@ || 'Zombie Error';
347 0 0       0 $Dancer2::Core::Route::ERROR_HANDLER
348             ? $Dancer2::Core::Route::ERROR_HANDLER->($error)
349             : $dsl->app->logger_engine->log(
350             warning => "Error in delayed response: $error"
351             );
352             };
353             }
354              
355             sub content_type {
356 0     0 0 0 shift;
357 0         0 $Dancer2::Core::Route::RESPONSE->content_type(@_);
358             }
359              
360             sub delayed {
361 1     1 0 5 my ( $dsl, $cb, @args ) = @_;
362              
363 1 50       6 @args % 2 == 0
364             or croak 'Arguments to delayed() keyword must be key/value pairs';
365              
366             # first time, responder doesn't exist yet
367 1         3 my %opts = @args;
368             $Dancer2::Core::Route::RESPONDER
369             or return Dancer2::Core::Response::Delayed->new(
370             cb => $cb,
371             request => $Dancer2::Core::Route::REQUEST,
372             response => $Dancer2::Core::Route::RESPONSE,
373              
374 1 50       19 ( error_cb => $opts{'on_error'} )x!! $opts{'on_error'},
375             );
376              
377             # we're in an async request process
378 0         0 my $request = $Dancer2::Core::Route::REQUEST;
379 0         0 my $response = $Dancer2::Core::Route::RESPONSE;
380 0         0 my $responder = $Dancer2::Core::Route::RESPONDER;
381 0         0 my $writer = $Dancer2::Core::Route::WRITER;
382 0         0 my $handler = $Dancer2::Core::Route::ERROR_HANDLER;
383              
384             return sub {
385 0     0   0 local $Dancer2::Core::Route::REQUEST = $request;
386 0         0 local $Dancer2::Core::Route::RESPONSE = $response;
387 0         0 local $Dancer2::Core::Route::RESPONDER = $responder;
388 0         0 local $Dancer2::Core::Route::WRITER = $writer;
389 0         0 local $Dancer2::Core::Route::ERROR_HANDLER = $handler;
390              
391 0         0 $cb->(@_);
392 0         0 };
393             }
394              
395             sub flush {
396 0 0   0 0 0 my $responder = $Dancer2::Core::Route::RESPONDER
397             or croak 'flush() called outside streaming response';
398              
399 0         0 my $response = $Dancer2::Core::Route::RESPONSE;
400 0         0 $Dancer2::Core::Route::WRITER = $responder->([
401             $response->status, $response->headers_to_array,
402             ]);
403             }
404              
405             sub done {
406 0 0   0 0 0 my $writer = $Dancer2::Core::Route::WRITER
407             or croak 'done() called outside streaming response';
408              
409 0         0 $writer->close;
410             }
411              
412 2     2 0 25 sub pass { shift->app->pass }
413              
414             #
415             # Route handler helpers
416             #
417              
418             sub context {
419 0     0 0 0 Carp::croak "DEPRECATED: context keyword. Please use the 'app' keyword instead of 'context'";
420             }
421              
422 103     103 0 411 sub request { $Dancer2::Core::Route::REQUEST }
423              
424 1     1 0 4 sub request_header { shift; $Dancer2::Core::Route::REQUEST->headers->header(@_) }
  1         10  
425              
426 10     10 0 715 sub response { $Dancer2::Core::Route::RESPONSE }
427              
428 2     2 0 5 sub upload { shift; $Dancer2::Core::Route::REQUEST->upload(@_); }
  2         13  
429              
430 2     2 0 14 sub captures { $Dancer2::Core::Route::REQUEST->captures }
431              
432 4     4 0 10 sub uri_for { shift; $Dancer2::Core::Route::REQUEST->uri_for(@_); }
  4         28  
433              
434 18     18 0 75 sub uri_for_route { shift->app->uri_for_route(@_); }
435              
436 31     31 0 151 sub splat { $Dancer2::Core::Route::REQUEST->splat }
437              
438 41     41 0 81 sub params { shift; $Dancer2::Core::Route::REQUEST->params(@_); }
  41         218  
439              
440 6     6 0 14 sub param { shift; $Dancer2::Core::Route::REQUEST->param(@_); }
  6         34  
441              
442 3     3 0 6 sub query_parameters { shift; $Dancer2::Core::Route::REQUEST->query_parameters(@_); }
  3         16  
443 5     5 0 12 sub body_parameters { shift; $Dancer2::Core::Route::REQUEST->body_parameters(@_); }
  5         25  
444 14     14 0 33 sub route_parameters { shift; $Dancer2::Core::Route::REQUEST->route_parameters(@_); }
  14         67  
445              
446 2     2 0 4 sub request_data { shift; $Dancer2::Core::Route::REQUEST->body_data(@_); }
  2         16  
447              
448 26     26 0 161 sub redirect { shift->app->redirect(@_) }
449              
450 44     44 0 397 sub forward { shift->app->forward(@_) }
451              
452 12     12 0 43 sub vars { $Dancer2::Core::Route::REQUEST->vars }
453              
454 8     8 0 15 sub var { shift; $Dancer2::Core::Route::REQUEST->var(@_); }
  8         74  
455              
456 0     0 0 0 sub cookies { $Dancer2::Core::Route::REQUEST->cookies }
457 0     0 0 0 sub cookie { shift->app->cookie(@_) }
458              
459             sub mime {
460 2     2 0 5 my $self = shift;
461 2 50       25 if ( $self->app ) {
462 2         25 return $self->app->mime_type;
463             }
464             else {
465 0         0 my $runner = $self->runner;
466 0         0 $runner->mime_type->reset_to_default;
467 0         0 return $runner->mime_type;
468             }
469             }
470              
471             #
472             # engines
473             #
474              
475             sub from_json {
476 6     6 0 12 shift; # remove first element
477 6         28 require_module('Dancer2::Serializer::JSON');
478 6         194 Dancer2::Serializer::JSON::from_json(@_);
479             }
480              
481             sub to_json {
482 21     21 0 35 shift; # remove first element
483 21         119 require_module('Dancer2::Serializer::JSON');
484 21         672 Dancer2::Serializer::JSON::to_json(@_);
485             }
486              
487             sub decode_json {
488 1     1 0 3 shift; # remove first element
489 1         4 require_module('Dancer2::Serializer::JSON');
490 1         35 Dancer2::Serializer::JSON::decode_json(@_);
491             }
492              
493             sub encode_json {
494 2     2 0 8 shift; # remove first element
495 2         13 require_module('Dancer2::Serializer::JSON');
496 2         53 Dancer2::Serializer::JSON::encode_json(@_);
497             }
498              
499             sub from_yaml {
500 4     4 0 9 shift; # remove first element
501 4         21 require_module('Dancer2::Serializer::YAML');
502 4         171 Dancer2::Serializer::YAML::from_yaml(@_);
503             }
504              
505             sub to_yaml {
506 11     11 0 26 shift; # remove first element
507 11         70 require_module('Dancer2::Serializer::YAML');
508 11         357 Dancer2::Serializer::YAML::to_yaml(@_);
509             }
510              
511             sub from_dumper {
512 2     2 0 4 shift; # remove first element
513 2         43 require_module('Dancer2::Serializer::Dumper');
514 2         71 Dancer2::Serializer::Dumper::from_dumper(@_);
515             }
516              
517             sub to_dumper {
518 7     7 0 14 shift; # remove first element
519 7         34 require_module('Dancer2::Serializer::Dumper');
520 7         219 Dancer2::Serializer::Dumper::to_dumper(@_);
521             }
522              
523             1;
524              
525             __END__