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.3520';
5 165     165   7552506 use strict;
  165         1578  
  165         5060  
6 165     165   984 use warnings;
  165         398  
  165         4490  
7 165     165   894 use Carp;
  165         321  
  165         11639  
8 165     165   1204 use Cwd 'realpath';
  165         399  
  165         8525  
9              
10 165     165   82368 use Dancer::App;
  165         1224  
  165         5828  
11 165     165   1186 use Dancer::Config;
  165         410  
  165         6504  
12 165     165   2101 use Dancer::Cookies;
  165         15443  
  165         5539  
13 165     165   1137 use Dancer::FileUtils;
  165         354  
  165         6427  
14 165     165   87426 use Dancer::GetOpt;
  165         518  
  165         6378  
15 165     165   80026 use Dancer::Error;
  165         524  
  165         5023  
16 165     165   72757 use Dancer::Hook;
  165         537  
  165         4251  
17 165     165   1146 use Dancer::Logger;
  165         410  
  165         3121  
18 165     165   817 use Dancer::Renderer;
  165         406  
  165         2877  
19 165     165   861 use Dancer::Route;
  165         379  
  165         2909  
20 165     165   78068 use Dancer::Serializer::JSON;
  165         541  
  165         5267  
21 165     165   71738 use Dancer::Serializer::YAML;
  165         508  
  165         5176  
22 165     165   72507 use Dancer::Serializer::XML;
  165         463  
  165         4982  
23 165     165   70636 use Dancer::Serializer::Dumper;
  165         526  
  165         4995  
24 165     165   1149 use Dancer::Session;
  165         426  
  165         3098  
25 165     165   862 use Dancer::SharedData;
  165         364  
  165         3124  
26 165     165   72407 use Dancer::Handler;
  165         563  
  165         5346  
27 165     165   1264 use Dancer::MIME;
  165         482  
  165         3977  
28 165     165   903 use Dancer::Exception qw(:all);
  165         797  
  165         17899  
29              
30 165     165   1320 use Dancer::Continuation::Halted;
  165         405  
  165         4196  
31 165     165   82554 use Dancer::Continuation::Route::Forwarded;
  165         500  
  165         5146  
32 165     165   68588 use Dancer::Continuation::Route::Passed;
  165         464  
  165         4764  
33 165     165   71401 use Dancer::Continuation::Route::ErrorSent;
  165         523  
  165         5021  
34 165     165   72956 use Dancer::Continuation::Route::FileSent;
  165         490  
  165         4857  
35 165     165   71060 use Dancer::Continuation::Route::Templated;
  165         485  
  165         4744  
36              
37 165     165   1109 use File::Spec;
  165         431  
  165         3155  
38 165     165   834 use Scalar::Util;
  165         355  
  165         6157  
39              
40 165     165   963 use base 'Exporter';
  165         478  
  165         611075  
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 954 sub any { Dancer::App->current->registry->any_add(@_) }
134 2     2 1 12 sub captures { Dancer::SharedData->request->params->{captures} }
135 4     4 1 1188 sub cookie { Dancer::Cookies->cookie( @_ ) }
136 7     7 1 1009 sub cookies { Dancer::Cookies->cookies }
137 17     17 1 1228 sub config { Dancer::Config::settings() }
138 22     22 1 131 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 61 sub debug { goto &Dancer::Logger::debug }
142 2     2 1 41 sub del { Dancer::App->current->registry->universal_add('delete', @_) }
143 12     12 1 5809 sub dirname { Dancer::FileUtils::dirname(@_) }
144 32     32 1 173 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 121 sub forward { Dancer::SharedData->response->forward(@_);
148             # throw a special continuation exception
149 15         82 Dancer::Continuation::Route::Forwarded->new->throw;
150             }
151 0     0 1 0 sub from_dumper { Dancer::Serializer::Dumper::from_dumper(@_) }
152 13     13 1 55154 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 27461 sub from_yaml { Dancer::Serializer::YAML::from_yaml(@_) }
155 638     638 1 30189 sub get { map { my $r = $_; Dancer::App->current->registry->universal_add($r, @_) } qw(head get) }
  1275         2090  
  1275         3418  
156 11     11 1 256 sub halt { Dancer::SharedData->response->halt(@_);
157             # throw a special continuation exception
158 11         131 Dancer::Continuation::Halted->new->throw;
159             }
160 8     8 1 213 sub header { goto &headers }
161 0     0 1 0 sub info { goto &Dancer::Logger::info }
162 9     9 1 245 sub push_header { Dancer::SharedData->response->push_header(@_); }
163 15     15 1 136 sub headers { Dancer::SharedData->response->headers(@_); }
164 115     115 1 40075 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 979 sub load { require $_ for @_ }
171 9     9 1 3980 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 3245 sub mime { Dancer::MIME->instance() }
177 1     1 1 26 sub options { Dancer::App->current->registry->universal_add('options', @_) }
178 255     255 1 5513 sub params { Dancer::SharedData->request->params(@_) }
179 13     13 1 68 sub param { params->{$_[0]} }
180             sub param_array {
181 6     6 1 39 my $value = param(shift);
182              
183 6 100       23 my @array = ref $value eq 'ARRAY' ? @$value
    100          
184             : defined $value ? ( $value )
185             : ()
186             ;
187              
188 6         22 return @array;
189             }
190 78     78 1 365 sub pass { Dancer::SharedData->response->pass(1);
191             # throw a special continuation exception
192 78         328 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 3737 sub path { Dancer::FileUtils::path(@_) }
196 72     72 1 3320 sub post { Dancer::App->current->registry->universal_add('post', @_) }
197 32 100   32 1 1630 sub prefix { @_ == 0 ? Dancer::App->current->get_prefix :
198             Dancer::App->current->set_prefix(@_) }
199 20     20 1 493 sub put { Dancer::App->current->registry->universal_add('put', @_) }
200 19     19 1 216 sub redirect { goto &_redirect }
201 0     0 1 0 sub render_with_layout { Dancer::Template::Abstract->_render_with_layout(@_) }
202 221     221 1 1123 sub request { Dancer::SharedData->request }
203 11   100 11 1 165 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 47 sub send_file { Dancer::Continuation::Route::FileSent->new(
210             return_value => _send_file(@_)
211             )->throw
212             }
213 97     97 1 44534 sub set { goto &setting }
214 8     8 1 1792 sub set_cookie { Dancer::Cookies->set_cookie(@_) }
215 1363 100   1363 1 46472 sub setting { Dancer::App->applications ? Dancer::App->current->setting(@_) : Dancer::Config::setting(@_) }
216 18     18 1 98 sub session { goto &_session }
217 21 50   21 1 114 sub splat { @{ Dancer::SharedData->request->params->{splat} || [] } }
  21         72  
218 0     0 1 0 sub start { goto &_start }
219 9     9 1 95 sub status { Dancer::SharedData->response->status(@_) }
220 39     39 1 488 sub template { Dancer::Template::Abstract->template(@_) }
221 1     1 1 871 sub to_dumper { Dancer::Serializer::Dumper::to_dumper(@_) }
222 8     8 1 3001 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 810 sub to_yaml { Dancer::Serializer::YAML::to_yaml(@_) }
225 21     21 1 2633 sub true { 1 }
226 3     3 1 525 sub upload { Dancer::SharedData->request->upload(@_) }
227 1     1 1 13 sub uri_for { Dancer::SharedData->request->uri_for(@_) }
228 19     19 1 1531 sub var { Dancer::SharedData->var(@_) }
229 28     28 1 142 sub vars { Dancer::SharedData->vars }
230 3     3 1 20 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   41279 my ($class, @args) = @_;
239 304         1299 my ($package, $script) = caller;
240              
241 304         1946 strict->import;
242 304         3681 warnings->import;
243 304         2767 utf8->import;
244              
245 304         550 my @final_args;
246 304         606 my $syntax_only = 0;
247 304         821 foreach (@args) {
248 332 100       1857 if ( $_ eq ':moose' ) {
    100          
    100          
    50          
249 2         6 push @final_args, '!before', '!after';
250             }
251             elsif ( $_ eq ':tests' ) {
252 125         419 push @final_args, '!pass';
253             }
254             elsif ( $_ eq ':syntax' ) {
255 204         496 $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         86218 $class->export_to_level(1, $class, @final_args);
265              
266             # if :syntax option exists, don't change settings
267 304 100       1645560 return if $syntax_only;
268              
269 100 100       517 $as_script = 1 if $ENV{PLACK_ENV};
270              
271 100 100       1805 Dancer::GetOpt->process_args unless $as_script;
272              
273 100         412 _init_script_dir($script);
274 100         744 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   38 my ($app_name, %options) = @_;
284 9         37 my $script = (caller)[1];
285 9         76 Dancer::Logger::core("loading application $app_name");
286              
287             # set the application
288 9         72 my $app = Dancer::App->set_running_app($app_name);
289              
290             # Application options
291 9 100       53 $app->set_app_prefix($options{prefix}) if $options{prefix};
292 9 100       42 $app->settings($options{settings}) if $options{settings};
293              
294             # load the application
295 9         44 _init_script_dir($script);
296 9         44 my ($res, $error) = Dancer::ModuleLoader->load($app_name);
297 9 100       48 $res or raise core => "unable to load application $app_name : $error";
298              
299             # restore the main application
300 7         33 Dancer::App->set_running_app('main');
301             }
302              
303             sub _init_script_dir {
304 194     194   602 my ($script) = @_;
305              
306 194         12210 my ($script_vol, $script_dirs, $script_name) =
307             File::Spec->splitpath(File::Spec->rel2abs($script));
308              
309             # normalize
310 194 100       5963 if ( -d ( my $fulldir = File::Spec->catdir( $script_dirs, $script_name ) ) ) {
311 84         366 $script_dirs = $fulldir;
312 84         208 $script_name = '';
313             }
314              
315 194         3357 my @script_dirs = File::Spec->splitdir($script_dirs);
316 194         496 my $script_path;
317 194 50       762 if ($script_vol) {
318 0         0 $script_path = Dancer::path($script_vol, $script_dirs);
319             } else {
320 194         677 $script_path = Dancer::path($script_dirs);
321             }
322              
323 194         492 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     2707 $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     1733 $LAYOUT_PRE_DANCER_1_2
333             ? $script_path
334             : File::Spec->rel2abs(Dancer::path($script_path, '..'))
335             );
336 194         832 Dancer::setting(appdir => $appdir);
337              
338             # once the dancer_appdir have been defined, we export to env
339 194         1602 $ENV{DANCER_APPDIR} = $appdir;
340              
341 194         1614 Dancer::Logger::core("initializing appdir to: `$appdir'");
342              
343             Dancer::setting(confdir => $ENV{DANCER_CONFDIR}
344 194 100 33     661 || $appdir) unless Dancer::setting('confdir');
345              
346             Dancer::setting(public => $ENV{DANCER_PUBLIC}
347 194   33     1655 || Dancer::FileUtils::path($appdir, 'public'));
348              
349             Dancer::setting(views => $ENV{DANCER_VIEWS}
350 194   33     1489 || Dancer::FileUtils::path($appdir, 'views'));
351              
352 194         770 my ($res, $error) = Dancer::ModuleLoader->use_lib(Dancer::FileUtils::path($appdir, 'lib'));
353 194 50       1091 $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   52 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       309 if ($destination !~ m{^ $scheme_re : }x) {
366 18         65 my $request = Dancer::SharedData->request;
367 18         90 $destination = $request->uri_for($destination, {}, 1);
368             }
369 19         1928 my $response = Dancer::SharedData->response;
370 19   50     149 $response->status($status || 302);
371 19         64 $response->headers('Location' => $destination);
372             }
373              
374             sub _session {
375 18 50   18   56 engine 'session'
376             or raise core => "Must specify session engine in settings prior to using 'session' keyword";
377 18 100       116 @_ == 0 ? Dancer::Session->get
    100          
378             : @_ == 1 ? Dancer::Session->read(@_)
379             : Dancer::Session->write(@_);
380             }
381              
382             sub _send_file {
383 9     9   23 my ($path, %options) = @_;
384 9         27 my $env = Dancer::SharedData->request->env;
385              
386 9         24 my $request = Dancer::Request->new_for_request('GET' => $path);
387 9         34 Dancer::SharedData->request($request);
388              
389             # if you asked for streaming but it's not supported in PSGI
390 9 50 33     40 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       22 if (exists($options{content_type})) {
396 3         23 $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     30 if (Scalar::Util::blessed($path) && $path->isa('IO::Scalar')) {
401 0         0 $path = $path->sref;
402             }
403              
404 9         13 my $resp;
405 9 100       22 if (ref($path) eq "SCALAR") {
406             # send_data
407 1   33     11 $resp = Dancer::SharedData->response() || Dancer::Response->new();
408             $resp->header('Content-Type' => exists($options{content_type}) ?
409 1 50       12 $options{content_type} : Dancer::MIME->default());
410 1         47 $resp->content($$path);
411             } else {
412             # real send_file
413 8 100 66     66 if ($options{system_path} && -f $path) {
414 2         13 $resp = Dancer::Renderer->get_file_response_for_path($path);
415             } else {
416 6         30 $resp = Dancer::Renderer->get_file_response();
417             }
418             }
419              
420 9 100       28 if ($resp) {
421              
422 8 100       31 if (exists($options{filename})) {
423 2         10 $resp->push_header('Content-Disposition' =>
424             "attachment; filename=\"$options{filename}\""
425             );
426             }
427              
428 8 50       99 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         76 return $resp;
471              
472             }
473              
474             Dancer::Error->new(
475 1         23 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__