File Coverage

blib/lib/Dancer.pm
Criterion Covered Total %
statement 236 285 82.8
branch 56 78 71.7
condition 15 36 41.6
subroutine 90 107 84.1
pod 67 67 100.0
total 464 573 80.9


line stmt bran cond sub pod time code
1             package Dancer;
2             our $AUTHORITY = 'cpan:SUKRIA';
3             #ABSTRACT: lightweight yet powerful web application framework
4             $Dancer::VERSION = '1.3521';
5 165     165   7342632 use strict;
  165         1442  
  165         4975  
6 165     165   940 use warnings;
  165         403  
  165         4329  
7 165     165   906 use Carp;
  165         368  
  165         11509  
8 165     165   1186 use Cwd 'realpath';
  165         355  
  165         8397  
9              
10 165     165   74471 use Dancer::App;
  165         1181  
  165         5422  
11 165     165   1147 use Dancer::Config;
  165         380  
  165         6616  
12 165     165   2050 use Dancer::Cookies;
  165         15489  
  165         4956  
13 165     165   1076 use Dancer::FileUtils;
  165         329  
  165         6272  
14 165     165   85953 use Dancer::GetOpt;
  165         484  
  165         6301  
15 165     165   78147 use Dancer::Error;
  165         536  
  165         4973  
16 165     165   72898 use Dancer::Hook;
  165         553  
  165         4293  
17 165     165   1185 use Dancer::Logger;
  165         436  
  165         3197  
18 165     165   854 use Dancer::Renderer;
  165         353  
  165         2854  
19 165     165   805 use Dancer::Route;
  165         369  
  165         2920  
20 165     165   75289 use Dancer::Serializer::JSON;
  165         492  
  165         5077  
21 165     165   71740 use Dancer::Serializer::YAML;
  165         496  
  165         4799  
22 165     165   72243 use Dancer::Serializer::XML;
  165         503  
  165         4920  
23 165     165   69801 use Dancer::Serializer::Dumper;
  165         517  
  165         4805  
24 165     165   1145 use Dancer::Session;
  165         419  
  165         3185  
25 165     165   810 use Dancer::SharedData;
  165         366  
  165         3105  
26 165     165   72936 use Dancer::Handler;
  165         558  
  165         5448  
27 165     165   1211 use Dancer::MIME;
  165         410  
  165         3864  
28 165     165   932 use Dancer::Exception qw(:all);
  165         424  
  165         17629  
29              
30 165     165   1328 use Dancer::Continuation::Halted;
  165         384  
  165         4365  
31 165     165   81408 use Dancer::Continuation::Route::Forwarded;
  165         470  
  165         5047  
32 165     165   68441 use Dancer::Continuation::Route::Passed;
  165         526  
  165         4752  
33 165     165   70131 use Dancer::Continuation::Route::ErrorSent;
  165         499  
  165         4926  
34 165     165   72492 use Dancer::Continuation::Route::FileSent;
  165         500  
  165         4847  
35 165     165   70123 use Dancer::Continuation::Route::Templated;
  165         497  
  165         4532  
36              
37 165     165   1115 use File::Spec;
  165         407  
  165         3035  
38 165     165   843 use Scalar::Util;
  165         386  
  165         6046  
39              
40 165     165   998 use base 'Exporter';
  165         514  
  165         590338  
41              
42             our @EXPORT = qw(
43             after
44             any
45             before
46             before_template
47             cookie
48             cookies
49             config
50             content_type
51             dance
52             dancer_version
53             debug
54             del
55             dirname
56             info
57             error
58             engine
59             false
60             forward
61             from_dumper
62             from_json
63             from_yaml
64             from_xml
65             get
66             halt
67             header
68             headers
69             hook
70             layout
71             load
72             load_app
73             logger
74             mime
75             options
76             param
77             param_array
78             params
79             pass
80             path
81             patch
82             post
83             prefix
84             push_header
85             put
86             redirect
87             render_with_layout
88             request
89             send_file
90             send_error
91             set
92             setting
93             set_cookie
94             session
95             splat
96             status
97             start
98             template
99             to_dumper
100             to_json
101             to_yaml
102             to_xml
103             true
104             upload
105             captures
106             uri_for
107             var
108             vars
109             warning
110             );
111              
112             # Dancer's syntax
113              
114             sub after {
115 0     0 1 0 Dancer::Deprecation->deprecated(reason => "use hooks!",
116             version => '1.3080',
117             fatal => 0);
118 0         0 Dancer::Hook->new('after', @_);
119             }
120             sub before {
121 0     0 1 0 Dancer::Deprecation->deprecated(reason => "use hooks!",
122             version => '1.3080',
123             fatal => 0);
124 0         0 Dancer::Hook->new('before', @_);
125             }
126             sub before_template {
127 0     0 1 0 Dancer::Deprecation->deprecated(reason => "use hooks!",
128             version => '1.3080',
129             fatal => 0);
130 0         0 Dancer::Hook->new('before_template', @_);
131             }
132              
133 24     24 1 846 sub any { Dancer::App->current->registry->any_add(@_) }
134 2     2 1 11 sub captures { Dancer::SharedData->request->params->{captures} }
135 4     4 1 1158 sub cookie { Dancer::Cookies->cookie( @_ ) }
136 7     7 1 999 sub cookies { Dancer::Cookies->cookies }
137 17     17 1 969 sub config { Dancer::Config::settings() }
138 22     22 1 147 sub content_type { Dancer::SharedData->response->content_type(@_) }
139 0     0 1 0 sub dance { goto &start }
140 0     0 1 0 sub dancer_version { Dancer->VERSION }
141 5     5 1 63 sub debug { goto &Dancer::Logger::debug }
142 2     2 1 47 sub del { Dancer::App->current->registry->universal_add('delete', @_) }
143 12     12 1 5529 sub dirname { Dancer::FileUtils::dirname(@_) }
144 32     32 1 161 sub engine { Dancer::Engine->engine(@_) }
145 6     6 1 32 sub error { goto &Dancer::Logger::error }
146 0     0 1 0 sub false { 0 }
147 15     15 1 94 sub forward { Dancer::SharedData->response->forward(@_);
148             # throw a special continuation exception
149 15         85 Dancer::Continuation::Route::Forwarded->new->throw;
150             }
151 0     0 1 0 sub from_dumper { Dancer::Serializer::Dumper::from_dumper(@_) }
152 13     13 1 63257 sub from_json { Dancer::Serializer::JSON::from_json(@_) }
153 0     0 1 0 sub from_xml { Dancer::Serializer::XML::from_xml(@_) }
154 8     8 1 26553 sub from_yaml { Dancer::Serializer::YAML::from_yaml(@_) }
155 638     638 1 28444 sub get { map { my $r = $_; Dancer::App->current->registry->universal_add($r, @_) } qw(head get) }
  1275         2097  
  1275         3440  
156 11     11 1 248 sub halt { Dancer::SharedData->response->halt(@_);
157             # throw a special continuation exception
158 11         140 Dancer::Continuation::Halted->new->throw;
159             }
160 8     8 1 233 sub header { goto &headers }
161 0     0 1 0 sub info { goto &Dancer::Logger::info }
162 9     9 1 251 sub push_header { Dancer::SharedData->response->push_header(@_); }
163 15     15 1 184 sub headers { Dancer::SharedData->response->headers(@_); }
164 115     115 1 39615 sub hook { Dancer::Hook->new(@_) }
165             sub layout {
166 0     0 1 0 Dancer::Deprecation->deprecated(reason => "use 'set layout => \"value\"'",
167             version => '1.3050',
168             fatal => 1);
169             }
170 1     1 1 934 sub load { require $_ for @_ }
171 9     9 1 4014 sub load_app { goto &_load_app } # goto doesn't add a call frame. So caller() will work as expected
172             sub logger {
173 0     0 1 0 Dancer::Deprecation->deprecated(reason => "use 'set logger => \"value\"'",
174             fatal => 1,version=>'1.3050');
175             }
176 8     8 1 3295 sub mime { Dancer::MIME->instance() }
177 1     1 1 12 sub options { Dancer::App->current->registry->universal_add('options', @_) }
178 255     255 1 5509 sub params { Dancer::SharedData->request->params(@_) }
179 13     13 1 86 sub param { params->{$_[0]} }
180             sub param_array {
181 6     6 1 29 my $value = param(shift);
182              
183 6 100       25 my @array = ref $value eq 'ARRAY' ? @$value
    100          
184             : defined $value ? ( $value )
185             : ()
186             ;
187              
188 6         20 return @array;
189             }
190 78     78 1 364 sub pass { Dancer::SharedData->response->pass(1);
191             # throw a special continuation exception
192 78         334 Dancer::Continuation::Route::Passed->new->throw;
193             }
194 0     0 1 0 sub patch { Dancer::App->current->registry->universal_add('patch', @_) }
195 223     223 1 3863 sub path { Dancer::FileUtils::path(@_) }
196 72     72 1 3112 sub post { Dancer::App->current->registry->universal_add('post', @_) }
197 32 100   32 1 2278 sub prefix { @_ == 0 ? Dancer::App->current->get_prefix :
198             Dancer::App->current->set_prefix(@_) }
199 20     20 1 433 sub put { Dancer::App->current->registry->universal_add('put', @_) }
200 19     19 1 205 sub redirect { goto &_redirect }
201 0     0 1 0 sub render_with_layout { Dancer::Template::Abstract->_render_with_layout(@_) }
202 221     221 1 1145 sub request { Dancer::SharedData->request }
203 11   100 11 1 195 sub send_error { Dancer::Continuation::Route::ErrorSent->new(
204             return_value => Dancer::Error->new(
205             message => $_[0],
206             code => $_[1] || 500)->render()
207             )->throw }
208             #sub send_file { goto &_send_file }
209 9     9 1 52 sub send_file { Dancer::Continuation::Route::FileSent->new(
210             return_value => _send_file(@_)
211             )->throw
212             }
213 97     97 1 40933 sub set { goto &setting }
214 8     8 1 1524 sub set_cookie { Dancer::Cookies->set_cookie(@_) }
215 1363 100   1363 1 45949 sub setting { Dancer::App->applications ? Dancer::App->current->setting(@_) : Dancer::Config::setting(@_) }
216 18     18 1 107 sub session { goto &_session }
217 21 50   21 1 114 sub splat { @{ Dancer::SharedData->request->params->{splat} || [] } }
  21         70  
218 0     0 1 0 sub start { goto &_start }
219 9     9 1 95 sub status { Dancer::SharedData->response->status(@_) }
220 39     39 1 480 sub template { Dancer::Template::Abstract->template(@_) }
221 1     1 1 812 sub to_dumper { Dancer::Serializer::Dumper::to_dumper(@_) }
222 8     8 1 2876 sub to_json { Dancer::Serializer::JSON::to_json(@_) }
223 0     0 1 0 sub to_xml { Dancer::Serializer::XML::to_xml(@_) }
224 4     4 1 704 sub to_yaml { Dancer::Serializer::YAML::to_yaml(@_) }
225 21     21 1 2197 sub true { 1 }
226 3     3 1 392 sub upload { Dancer::SharedData->request->upload(@_) }
227 1     1 1 9 sub uri_for { Dancer::SharedData->request->uri_for(@_) }
228 19     19 1 1241 sub var { Dancer::SharedData->var(@_) }
229 28     28 1 132 sub vars { Dancer::SharedData->vars }
230 3     3 1 18 sub warning { goto &Dancer::Logger::warning }
231              
232             # When importing the package, strict and warnings pragma are loaded,
233             # and the appdir detection is performed.
234             {
235             my $as_script = 0;
236              
237             sub import {
238 304     304   40786 my ($class, @args) = @_;
239 304         1182 my ($package, $script) = caller;
240              
241 304         1958 strict->import;
242 304         3865 warnings->import;
243 304         2819 utf8->import;
244              
245 304         691 my @final_args;
246 304         557 my $syntax_only = 0;
247 304         752 foreach (@args) {
248 332 100       1816 if ( $_ eq ':moose' ) {
    100          
    100          
    50          
249 2         7 push @final_args, '!before', '!after';
250             }
251             elsif ( $_ eq ':tests' ) {
252 125         444 push @final_args, '!pass';
253             }
254             elsif ( $_ eq ':syntax' ) {
255 204         504 $syntax_only = 1;
256             }
257             elsif ($_ eq ':script') {
258 1         2 $as_script = 1;
259             } else {
260 0         0 push @final_args, $_;
261             }
262             }
263              
264 304         85354 $class->export_to_level(1, $class, @final_args);
265              
266             # if :syntax option exists, don't change settings
267 304 100       1656599 return if $syntax_only;
268              
269 100 100       558 $as_script = 1 if $ENV{PLACK_ENV};
270              
271 100 100       1774 Dancer::GetOpt->process_args unless $as_script;
272              
273 100         413 _init_script_dir($script);
274 100         751 Dancer::Config->load;
275             }
276              
277             }
278              
279             # private code
280              
281             # FIXME handle previous usage of load_app with multiple app names
282             sub _load_app {
283 9     9   35 my ($app_name, %options) = @_;
284 9         38 my $script = (caller)[1];
285 9         65 Dancer::Logger::core("loading application $app_name");
286              
287             # set the application
288 9         65 my $app = Dancer::App->set_running_app($app_name);
289              
290             # Application options
291 9 100       38 $app->set_app_prefix($options{prefix}) if $options{prefix};
292 9 100       38 $app->settings($options{settings}) if $options{settings};
293              
294             # load the application
295 9         41 _init_script_dir($script);
296 9         48 my ($res, $error) = Dancer::ModuleLoader->load($app_name);
297 9 100       47 $res or raise core => "unable to load application $app_name : $error";
298              
299             # restore the main application
300 7         30 Dancer::App->set_running_app('main');
301             }
302              
303             sub _init_script_dir {
304 194     194   620 my ($script) = @_;
305              
306 194         11945 my ($script_vol, $script_dirs, $script_name) =
307             File::Spec->splitpath(File::Spec->rel2abs($script));
308              
309             # normalize
310 194 100       5762 if ( -d ( my $fulldir = File::Spec->catdir( $script_dirs, $script_name ) ) ) {
311 84         355 $script_dirs = $fulldir;
312 84         224 $script_name = '';
313             }
314              
315 194         3165 my @script_dirs = File::Spec->splitdir($script_dirs);
316 194         438 my $script_path;
317 194 50       713 if ($script_vol) {
318 0         0 $script_path = Dancer::path($script_vol, $script_dirs);
319             } else {
320 194         646 $script_path = Dancer::path($script_dirs);
321             }
322              
323 194         506 my $LAYOUT_PRE_DANCER_1_2 = 1;
324              
325             # in bin/ or public/ or t/ we need to go one level up to find the appdir
326 194 50 33     2673 $LAYOUT_PRE_DANCER_1_2 = 0
      33        
327             if ($script_dirs[$#script_dirs - 1] eq 'bin')
328             or ($script_dirs[$#script_dirs - 1] eq 'public')
329             or ($script_dirs[$#script_dirs - 1] eq 't');
330              
331             my $appdir = $ENV{DANCER_APPDIR} || (
332 194   66     1695 $LAYOUT_PRE_DANCER_1_2
333             ? $script_path
334             : File::Spec->rel2abs(Dancer::path($script_path, '..'))
335             );
336 194         828 Dancer::setting(appdir => $appdir);
337              
338             # once the dancer_appdir have been defined, we export to env
339 194         1518 $ENV{DANCER_APPDIR} = $appdir;
340              
341 194         1743 Dancer::Logger::core("initializing appdir to: `$appdir'");
342              
343             Dancer::setting(confdir => $ENV{DANCER_CONFDIR}
344 194 100 33     612 || $appdir) unless Dancer::setting('confdir');
345              
346             Dancer::setting(public => $ENV{DANCER_PUBLIC}
347 194   33     1554 || Dancer::FileUtils::path($appdir, 'public'));
348              
349             Dancer::setting(views => $ENV{DANCER_VIEWS}
350 194   33     1452 || Dancer::FileUtils::path($appdir, 'views'));
351              
352 194         787 my ($res, $error) = Dancer::ModuleLoader->use_lib(Dancer::FileUtils::path($appdir, 'lib'));
353 194 50       1096 $res or raise core => "unable to set libdir : $error";
354             }
355              
356              
357             # Scheme grammar as defined in RFC 2396
358             # scheme = alpha *( alpha | digit | "+" | "-" | "." )
359             my $scheme_re = qr{ [a-z][a-z0-9\+\-\.]* }ix;
360             sub _redirect {
361 19     19   48 my ($destination, $status) = @_;
362              
363             # RFC 2616 requires an absolute URI with a scheme,
364             # turn the URI into that if it needs it
365 19 100       313 if ($destination !~ m{^ $scheme_re : }x) {
366 18         65 my $request = Dancer::SharedData->request;
367 18         78 $destination = $request->uri_for($destination, {}, 1);
368             }
369 19         1946 my $response = Dancer::SharedData->response;
370 19   50     127 $response->status($status || 302);
371 19         56 $response->headers('Location' => $destination);
372             }
373              
374             sub _session {
375 18 50   18   55 engine 'session'
376             or raise core => "Must specify session engine in settings prior to using 'session' keyword";
377 18 100       124 @_ == 0 ? Dancer::Session->get
    100          
378             : @_ == 1 ? Dancer::Session->read(@_)
379             : Dancer::Session->write(@_);
380             }
381              
382             sub _send_file {
383 9     9   31 my ($path, %options) = @_;
384 9         37 my $env = Dancer::SharedData->request->env;
385              
386 9         32 my $request = Dancer::Request->new_for_request('GET' => $path);
387 9         49 Dancer::SharedData->request($request);
388              
389             # if you asked for streaming but it's not supported in PSGI
390 9 50 33     61 if ( $options{'streaming'} && ! $env->{'psgi.streaming'} ) {
391             # TODO: throw a fit (AKA "exception") or a Dancer::Error?
392 0         0 raise core => 'Sorry, streaming is not supported on this server.';
393             }
394              
395 9 100       38 if (exists($options{content_type})) {
396 3         9 $request->content_type($options{content_type});
397             }
398              
399             # If we're given an IO::Scalar object, DTRT (take the scalar ref from it)
400 9 50 33     33 if (Scalar::Util::blessed($path) && $path->isa('IO::Scalar')) {
401 0         0 $path = $path->sref;
402             }
403              
404 9         15 my $resp;
405 9 100       22 if (ref($path) eq "SCALAR") {
406             # send_data
407 1   33     5 $resp = Dancer::SharedData->response() || Dancer::Response->new();
408             $resp->header('Content-Type' => exists($options{content_type}) ?
409 1 50       7 $options{content_type} : Dancer::MIME->default());
410 1         49 $resp->content($$path);
411             } else {
412             # real send_file
413 8 100 66     82 if ($options{system_path} && -f $path) {
414 2         14 $resp = Dancer::Renderer->get_file_response_for_path($path);
415             } else {
416 6         37 $resp = Dancer::Renderer->get_file_response();
417             }
418             }
419              
420 9 100       32 if ($resp) {
421              
422 8 100       22 if (exists($options{filename})) {
423 2         15 $resp->push_header('Content-Disposition' =>
424             "attachment; filename=\"$options{filename}\""
425             );
426             }
427              
428 8 50       111 if ( $options{'streaming'} ) {
429             # handle streaming
430             $resp->streamed( sub {
431 0     0   0 my ( $status, $headers ) = @_;
432             my %callbacks = defined $options{'callbacks'} ?
433 0 0       0 %{ $options{'callbacks'} } :
  0         0  
434             ();
435              
436             return sub {
437 0         0 my $respond = shift;
438             exists $callbacks{'override'}
439 0 0       0 and return $callbacks{'override'}->( $respond, $resp );
440              
441             # get respond callback and set headers, get writer in return
442 0         0 my $writer = $respond->( [
443             $status,
444             $headers,
445             ] );
446              
447             # get content from original response
448 0         0 my $content = $resp->content;
449              
450             exists $callbacks{'around'}
451 0 0       0 and return $callbacks{'around'}->( $writer, $content );
452              
453 0 0       0 if ( ref $content ) {
454 0   0     0 my $bytes = $options{'bytes'} || '43008'; # 42K (dams)
455 0         0 my $buf;
456 0         0 while ( ( my $read = sysread $content, $buf, $bytes ) != 0 ) {
457 0 0       0 if ( exists $callbacks{'around_content'} ) {
458 0         0 $callbacks{'around_content'}->( $writer, $buf );
459             } else {
460 0         0 $writer->write($buf);
461             }
462             }
463             } else {
464 0         0 $writer->write($content);
465             }
466 0         0 };
467 0         0 } );
468             }
469              
470 8         71 return $resp;
471              
472             }
473              
474             Dancer::Error->new(
475 1         24 code => 404,
476             message => "No such file: `$path'"
477             )->render();
478             }
479              
480             # Start/Run the application with the chosen apphandler
481             sub _start {
482 0     0     my ($class, $request) = @_;
483 0           Dancer::Config->load;
484              
485             # Backward compatibility for app.psgi that has sub { Dancer->dance($req) }
486 0 0         if ($request) {
487 0           Dancer::Handler->init_request_headers( $request->env );
488             # TODO _build_headers should either not be private, or we should call
489             # init
490 0           $request->_build_headers;
491 0           return Dancer::Handler->handle_request($request);
492             }
493              
494 0           my $handler = Dancer::Handler->get_handler;
495 0           Dancer::Logger::core("loading handler '".ref($handler)."'");
496 0           return $handler->dance;
497             }
498              
499              
500             1;
501              
502             __END__