File Coverage

blib/lib/Dancer2/Core/DSL.pm
Criterion Covered Total %
statement 140 200 70.0
branch 19 42 45.2
condition 1 5 20.0
subroutine 70 92 76.0
pod 2 82 2.4
total 232 421 55.1


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 = '1.0.0';
5 138     138   163551 use Moo;
  138         7299  
  138         1168  
6 138     138   55208 use Carp;
  138         375  
  138         10530  
7 138     138   2029 use Module::Runtime 'require_module';
  138         3992  
  138         1606  
8 138     138   9898 use Ref::Util qw< is_arrayref >;
  138         3515  
  138         7939  
9 138     138   6359 use Dancer2::Core::Hook;
  138         365  
  138         4566  
10 138     138   6709 use Dancer2::FileUtils;
  138         422  
  138         6447  
11 138     138   63436 use Dancer2::Core::Response::Delayed;
  138         472  
  138         429218  
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 260     260 0 25759 { 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             var => { is_global => 0 },
126             vars => { is_global => 0 },
127             warning => { is_global => 1 },
128             };
129             }
130              
131 13     13 0 190 sub dancer_app { shift->app }
132 1     1 0 8 sub dancer_version { Dancer2->VERSION }
133              
134             sub dancer_major_version {
135 0     0 0 0 return ( split /\./, dancer_version )[0];
136             }
137              
138 0     0 0 0 sub log { shift->app->log( @_ ) }
139 3     3 0 72 sub debug { shift->app->log( debug => @_ ) }
140 1     1 0 7 sub info { shift->app->log( info => @_ ) }
141 5     5 0 34 sub warning { shift->app->log( warning => @_ ) }
142 2     2 0 11 sub error { shift->app->log( error => @_ ) }
143              
144 1     1 0 20 sub true {1}
145 0     0 0 0 sub false {0}
146              
147 0 0   0 0 0 sub dirname { shift and Dancer2::FileUtils::dirname(@_) }
148 0 0   0 0 0 sub path { shift and Dancer2::FileUtils::path(@_) }
149              
150 4     4 0 24 sub config { shift->app->settings }
151              
152 9     9 0 63 sub engine { shift->app->engine(@_) }
153              
154 146     146 1 1213 sub setting { shift->app->setting(@_) }
155              
156 130     130 1 593 sub set { shift->setting(@_) }
157              
158 24     24 0 170 sub template { shift->app->template(@_) }
159              
160             sub session {
161 127     127 0 317 my ( $self, $key, $value ) = @_;
162              
163             # shortcut reads if no session exists, so we don't
164             # instantiate sessions for no reason
165 127 100       367 if ( @_ == 2 ) {
166 56 100       244 return unless $self->app->has_session;
167             }
168              
169 116   33     2194 my $session = $self->app->session
170             || croak "No session available, a session engine needs to be set";
171              
172 116         2783 $self->app->setup_session;
173              
174             # return the session object if no key
175 116 100       5325 @_ == 1 and return $session;
176              
177             # read if a key is provided
178 104 100       450 @_ == 2 and return $session->read($key);
179              
180              
181             # write to the session or delete if value is undef
182 59 100       157 if ( defined $value ) {
183 50         221 $session->write( $key => $value );
184             }
185             else {
186 9         43 $session->delete($key);
187             }
188             }
189              
190 8     8 0 36 sub send_as { shift->app->send_as(@_) }
191              
192 8     8 0 56 sub send_error { shift->app->send_error(@_) }
193              
194 11     11 0 56 sub send_file { shift->app->send_file(@_) }
195              
196             #
197             # route handlers & friends
198             #
199              
200             sub hook {
201 69     69 0 269 my ( $self, $name, $code ) = @_;
202 69         1788 $self->app->add_hook(
203             Dancer2::Core::Hook->new( name => $name, code => $code ) );
204             }
205              
206             sub prefix {
207 4     4 0 34 my $app = shift->app;
208 4 100       80 @_ == 1
209             ? $app->prefix(@_)
210             : $app->lexical_prefix(@_);
211             }
212              
213 7     7 0 43 sub halt { shift->app->halt(@_) }
214              
215 1     1 0 10 sub del { shift->_normalize_route( [qw/delete /], @_ ) }
216 259     259 0 1494 sub get { shift->_normalize_route( [qw/get head/], @_ ) }
217 1     1 0 7 sub options { shift->_normalize_route( [qw/options /], @_ ) }
218 1     1 0 6 sub patch { shift->_normalize_route( [qw/patch /], @_ ) }
219 34     34 0 264 sub post { shift->_normalize_route( [qw/post /], @_ ) }
220 3     3 0 18 sub put { shift->_normalize_route( [qw/put /], @_ ) }
221              
222 2     2 0 5 sub prepare_app { push @{ shift->app->prep_apps }, @_ }
  2         20  
223              
224             sub any {
225 7     7 0 24 my $self = shift;
226              
227             # If they've supplied their own list of methods,
228             # expand del, otherwise give them the default list.
229 7 100       42 if ( is_arrayref($_[0]) ) {
230 3         11 s/^del$/delete/ for @{ $_[0] };
  3         23  
231             }
232             else {
233 4         22 unshift @_, [qw/delete get head options patch post put/];
234             }
235              
236 7         32 $self->_normalize_route(@_);
237             }
238              
239             sub _normalize_route {
240 306     306   1393 my $app = shift->app;
241 306         655 my $methods = shift;
242 306         582 my %args;
243              
244             # Options are optional, deduce their presence from arg length.
245             # @_ = ( REGEXP, OPTIONS, CODE )
246             # or
247             # @_ = ( REGEXP, CODE )
248 306 50       1971 @args{qw/regexp options code/} = @_ == 3 ? @_ : ( $_[0], {}, $_[1] );
249              
250 306         680 return map $app->add_route( %args, method => $_ ), @{$methods};
  306         2050  
251             }
252              
253             #
254             # Server startup
255             #
256              
257             # access to the runner singleton
258             # will be populated on-the-fly when needed
259             # this singleton contains anything needed to start the application server
260 1     1 0 6 sub runner { Dancer2->runner }
261              
262             # start the server
263 0     0 0 0 sub start { shift->runner->start }
264              
265 0     0 0 0 sub dance { shift->start(@_) }
266              
267             sub psgi_app {
268 2     2 0 6 my $self = shift;
269              
270 2         16 $self->app->to_app;
271             }
272              
273 139     139 0 1412 sub to_app { shift->app->to_app }
274              
275             #
276             # Response alterations
277             #
278              
279             sub status {
280 7     7 0 161 $Dancer2::Core::Route::RESPONSE->status( $_[1] );
281             }
282              
283             sub push_header {
284 0     0 0 0 Carp::croak "DEPRECATED: push_header keyword. Please use the 'push_response_header' keyword instead of 'push_header'";
285             }
286              
287             sub push_response_header {
288 0     0 0 0 shift;
289 0         0 $Dancer2::Core::Route::RESPONSE->push_header(@_);
290             }
291              
292             sub header {
293 0     0 0 0 Carp::croak "DEPRECATED: header keyword. Please use the 'response_header' keyword instead of 'header'";
294             }
295              
296             sub response_header {
297 4     4 0 11 shift;
298 4         92 $Dancer2::Core::Route::RESPONSE->header(@_);
299             }
300              
301             sub headers {
302 0     0 0 0 Carp::croak "DEPRECATED: headers keyword. Please use the 'response_headers' keyword instead of 'headers'";
303             }
304              
305             sub response_headers {
306 0     0 0 0 shift;
307 0         0 $Dancer2::Core::Route::RESPONSE->header(@_);
308             }
309              
310             sub content {
311 1     1 0 3 my $dsl = shift;
312              
313             # simple synchronous response
314 1 50       242 my $responder = $Dancer2::Core::Route::RESPONDER
315             or croak 'Cannot use content keyword outside delayed response';
316              
317             # flush if wasn't flushed before
318 0 0       0 if ( !$Dancer2::Core::Route::WRITER ) {
319 0         0 $Dancer2::Core::Route::WRITER = $responder->([
320             $Dancer2::Core::Route::RESPONSE->status,
321             $Dancer2::Core::Route::RESPONSE->headers_to_array,
322             ]);
323             }
324              
325             eval {
326 0         0 $Dancer2::Core::Route::WRITER->write(@_);
327 0         0 1;
328 0 0       0 } or do {
329 0   0     0 my $error = $@ || 'Zombie Error';
330 0 0       0 $Dancer2::Core::Route::ERROR_HANDLER
331             ? $Dancer2::Core::Route::ERROR_HANDLER->($error)
332             : $dsl->app->logger_engine->log(
333             warning => "Error in delayed response: $error"
334             );
335             };
336             }
337              
338             sub content_type {
339 0     0 0 0 shift;
340 0         0 $Dancer2::Core::Route::RESPONSE->content_type(@_);
341             }
342              
343             sub delayed {
344 1     1 0 5 my ( $dsl, $cb, @args ) = @_;
345              
346 1 50       21 @args % 2 == 0
347             or croak 'Arguments to delayed() keyword must be key/value pairs';
348              
349             # first time, responder doesn't exist yet
350 1         7 my %opts = @args;
351             $Dancer2::Core::Route::RESPONDER
352             or return Dancer2::Core::Response::Delayed->new(
353             cb => $cb,
354             request => $Dancer2::Core::Route::REQUEST,
355             response => $Dancer2::Core::Route::RESPONSE,
356              
357 1 50       20 ( error_cb => $opts{'on_error'} )x!! $opts{'on_error'},
358             );
359              
360             # we're in an async request process
361 0         0 my $request = $Dancer2::Core::Route::REQUEST;
362 0         0 my $response = $Dancer2::Core::Route::RESPONSE;
363 0         0 my $responder = $Dancer2::Core::Route::RESPONDER;
364 0         0 my $writer = $Dancer2::Core::Route::WRITER;
365 0         0 my $handler = $Dancer2::Core::Route::ERROR_HANDLER;
366              
367             return sub {
368 0     0   0 local $Dancer2::Core::Route::REQUEST = $request;
369 0         0 local $Dancer2::Core::Route::RESPONSE = $response;
370 0         0 local $Dancer2::Core::Route::RESPONDER = $responder;
371 0         0 local $Dancer2::Core::Route::WRITER = $writer;
372 0         0 local $Dancer2::Core::Route::ERROR_HANDLER = $handler;
373              
374 0         0 $cb->(@_);
375 0         0 };
376             }
377              
378             sub flush {
379 0 0   0 0 0 my $responder = $Dancer2::Core::Route::RESPONDER
380             or croak 'flush() called outside streaming response';
381              
382 0         0 my $response = $Dancer2::Core::Route::RESPONSE;
383 0         0 $Dancer2::Core::Route::WRITER = $responder->([
384             $response->status, $response->headers_to_array,
385             ]);
386             }
387              
388             sub done {
389 0 0   0 0 0 my $writer = $Dancer2::Core::Route::WRITER
390             or croak 'done() called outside streaming response';
391              
392 0         0 $writer->close;
393             }
394              
395 2     2 0 17 sub pass { shift->app->pass }
396              
397             #
398             # Route handler helpers
399             #
400              
401             sub context {
402 0     0 0 0 Carp::croak "DEPRECATED: context keyword. Please use the 'app' keyword instead of 'context'";
403             }
404              
405 91     91 0 317 sub request { $Dancer2::Core::Route::REQUEST }
406              
407 1     1 0 3 sub request_header { shift; $Dancer2::Core::Route::REQUEST->headers->header(@_) }
  1         5  
408              
409 7     7 0 156 sub response { $Dancer2::Core::Route::RESPONSE }
410              
411 2     2 0 4 sub upload { shift; $Dancer2::Core::Route::REQUEST->upload(@_); }
  2         12  
412              
413 2     2 0 11 sub captures { $Dancer2::Core::Route::REQUEST->captures }
414              
415 3     3 0 8 sub uri_for { shift; $Dancer2::Core::Route::REQUEST->uri_for(@_); }
  3         16  
416              
417 31     31 0 161 sub splat { $Dancer2::Core::Route::REQUEST->splat }
418              
419 41     41 0 82 sub params { shift; $Dancer2::Core::Route::REQUEST->params(@_); }
  41         204  
420              
421 6     6 0 15 sub param { shift; $Dancer2::Core::Route::REQUEST->param(@_); }
  6         32  
422              
423 3     3 0 6 sub query_parameters { shift; $Dancer2::Core::Route::REQUEST->query_parameters(@_); }
  3         15  
424 5     5 0 11 sub body_parameters { shift; $Dancer2::Core::Route::REQUEST->body_parameters(@_); }
  5         19  
425 14     14 0 26 sub route_parameters { shift; $Dancer2::Core::Route::REQUEST->route_parameters(@_); }
  14         55  
426              
427 2     2 0 5 sub request_data { shift; $Dancer2::Core::Route::REQUEST->body_data(@_); }
  2         11  
428              
429 26     26 0 115 sub redirect { shift->app->redirect(@_) }
430              
431 44     44 0 233 sub forward { shift->app->forward(@_) }
432              
433 12     12 0 37 sub vars { $Dancer2::Core::Route::REQUEST->vars }
434              
435 8     8 0 17 sub var { shift; $Dancer2::Core::Route::REQUEST->var(@_); }
  8         36  
436              
437 0     0 0 0 sub cookies { $Dancer2::Core::Route::REQUEST->cookies }
438 0     0 0 0 sub cookie { shift->app->cookie(@_) }
439              
440             sub mime {
441 2     2 0 5 my $self = shift;
442 2 50       21 if ( $self->app ) {
443 2         9 return $self->app->mime_type;
444             }
445             else {
446 0         0 my $runner = $self->runner;
447 0         0 $runner->mime_type->reset_default;
448 0         0 return $runner->mime_type;
449             }
450             }
451              
452             #
453             # engines
454             #
455              
456             sub from_json {
457 6     6 0 12 shift; # remove first element
458 6         24 require_module('Dancer2::Serializer::JSON');
459 6         187 Dancer2::Serializer::JSON::from_json(@_);
460             }
461              
462             sub to_json {
463 21     21 0 33 shift; # remove first element
464 21         80 require_module('Dancer2::Serializer::JSON');
465 21         547 Dancer2::Serializer::JSON::to_json(@_);
466             }
467              
468             sub decode_json {
469 1     1 0 2 shift; # remove first element
470 1         4 require_module('Dancer2::Serializer::JSON');
471 1         24 Dancer2::Serializer::JSON::decode_json(@_);
472             }
473              
474             sub encode_json {
475 2     2 0 4 shift; # remove first element
476 2         8 require_module('Dancer2::Serializer::JSON');
477 2         36 Dancer2::Serializer::JSON::encode_json(@_);
478             }
479              
480             sub from_yaml {
481 4     4 0 13 shift; # remove first element
482 4         19 require_module('Dancer2::Serializer::YAML');
483 4         134 Dancer2::Serializer::YAML::from_yaml(@_);
484             }
485              
486             sub to_yaml {
487 11     11 0 21 shift; # remove first element
488 11         45 require_module('Dancer2::Serializer::YAML');
489 11         261 Dancer2::Serializer::YAML::to_yaml(@_);
490             }
491              
492             sub from_dumper {
493 2     2 0 5 shift; # remove first element
494 2         11 require_module('Dancer2::Serializer::Dumper');
495 2         66 Dancer2::Serializer::Dumper::from_dumper(@_);
496             }
497              
498             sub to_dumper {
499 7     7 0 16 shift; # remove first element
500 7         28 require_module('Dancer2::Serializer::Dumper');
501 7         171 Dancer2::Serializer::Dumper::to_dumper(@_);
502             }
503              
504             1;
505              
506             __END__
507              
508             =pod
509              
510             =encoding UTF-8
511              
512             =head1 NAME
513              
514             Dancer2::Core::DSL - Dancer2's Domain Specific Language (DSL)
515              
516             =head1 VERSION
517              
518             version 1.0.0
519              
520             =head1 FUNCTIONS
521              
522             =head2 setting
523              
524             Lets you define settings and access them:
525              
526             setting('foo' => 42);
527             setting('foo' => 42, 'bar' => 43);
528             my $foo=setting('foo');
529              
530             If settings were defined returns number of settings.
531              
532             =head2 set ()
533              
534             alias for L<setting>:
535              
536             set('foo' => '42');
537             my $port=set('port');
538              
539             =head1 SEE ALSO
540              
541             L<http://advent.perldancer.org/2010/18>
542              
543             =head1 AUTHOR
544              
545             Dancer Core Developers
546              
547             =head1 COPYRIGHT AND LICENSE
548              
549             This software is copyright (c) 2023 by Alexis Sukrieh.
550              
551             This is free software; you can redistribute it and/or modify it under
552             the same terms as the Perl 5 programming language system itself.
553              
554             =cut