File Coverage

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


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