File Coverage

blib/lib/Dancer2/Plugin/DoFile.pm
Criterion Covered Total %
statement 68 217 31.3
branch 19 96 19.7
condition 19 74 25.6
subroutine 8 12 66.6
pod 0 6 0.0
total 114 405 28.1


line stmt bran cond sub pod time code
1             $Dancer2::Plugin::DoFile::VERSION = '0.13';
2             # ABSTRACT: File-based MVC plugin for Dancer2
3              
4             use strict;
5 1     1   1129984 use warnings;
  1         3  
  1         33  
6 1     1   5  
  1         2  
  1         29  
7             use Dancer2::Plugin;
8 1     1   578  
  1         14711  
  1         12  
9             use JSON;
10 1     1   4582 use Hash::Merge;
  1         9115  
  1         6  
11 1     1   645 use HTTP::Accept;
  1         4315  
  1         51  
12 1     1   511  
  1         3323  
  1         3000  
13             # Not sure if this is necessary at this point, as the model
14             # Should in general not be dynamically loaded...
15             #has model_loc => (
16             # is => 'rw',
17             # default => sub {'dofiles/models'},
18             #);
19             has controller_loc => (
20             is => 'rw',
21             default => sub {'dofiles/controllers'},
22             );
23             has controller_extension_list => (
24             is => 'rw',
25             default => sub { ['.ctl','.do'] }
26             );
27             has view_loc => (
28             is => 'rw',
29             default => sub {'dofiles/views'},
30             );
31             has view_extension_list => (
32             is => 'rw',
33             default => sub { ['.view','.po'] }
34             );
35              
36             has default_file => (
37             is => 'rw',
38             default => sub {'index'},
39             );
40              
41             # This is old config syntax and should not be used
42             # Only preserved as temporary backwards compatibility
43             has page_loc => (
44             is => 'rw',
45             default => sub {'dofiles/pages'},
46             );
47             # This list will change over time to remove "do" and "po" in favour of "ctl" (controllers) and "view" (views)
48             has extension_list => (
49             is => 'rw',
50             default => sub { ['.ctl', '.do', '.po', '.view']}
51             );
52              
53             # Old method
54             plugin_keywords 'dofile';
55              
56             # New methods
57             plugin_keywords 'controller';
58             plugin_keywords 'view';
59              
60             my %dofiles;
61             my $acceptext = {
62             "" => "",
63             "text/html" => ".html",
64             "application/json" => ".json"
65             };
66              
67             my $self = shift;
68             my $settings = $self->config;
69 1     1 0 86  
70 1         25 $settings->{$_} and $self->$_( $settings->{$_} )
71             for qw/ page_loc default_file extension_list controller_loc controller_extension_list view_loc view_extension_list /;
72             }
73 1   100     94  
74             my $plugin = shift;
75             my $arg = shift;
76             my %opts = @_;
77 0     0 0 0  
78 0         0 my $app = $plugin->app;
79 0         0 my $settings = $app->settings;
80             my $method = $app->request->method;
81 0         0 my $pageroot = $settings->{appdir};
82 0         0 if ($pageroot !~ /\/$/) {
83 0         0 $pageroot .= "/";
84 0         0 }
85 0 0       0 $pageroot .= $plugin->controller_loc;
86 0         0  
87             my $path = $arg || $app->request->path;
88 0         0  
89             # If any one of these returns content then we stop processing any more of them
90 0   0     0 # Content is defined as an array ref (it's an Obj2HTML array), a hashref with a "content" element, or a scalar (assumed HTML string - it's not checked!)
91             # This can lead to some interesting results if someone doesn't explicitly return undef when they want to fall through to the next file
92             # as perl will return the last evaluated value, which would be intepretted as content according to the above rules
93              
94             my $merger = Hash::Merge->new('RIGHT_PRECEDENT');
95              
96             my $stash = $opts{stash} || {};
97 0         0  
98             # Safety first...
99 0   0     0 $path =~ s|/$|"/".$plugin->default_file|e;
100             $path =~ s|^/+||;
101             $path =~ s|\.\./||g;
102 0         0 $path =~ s|~||g;
  0         0  
103 0         0  
104 0         0 if (!$path) { $path = $plugin->default_file; }
105 0         0 if (-d $pageroot."/$path") {
106             if ($path !~ /\/$/) {
107 0 0       0 $path .= "/".$plugin->default_file;
  0         0  
108 0 0       0 } else {
109 0 0       0 return {
110 0         0 url => "/$path/",
111             redirect => 1,
112             done => 1
113 0         0 };
114             }
115             }
116              
117             if (!defined $stash->{dofiles_executed}) { $stash->{dofiles_executed} = 0; }
118             OUTER:
119             foreach my $ext (@{$plugin->controller_extension_list}) {
120 0 0       0 foreach my $m ("", "-$method", "-ANY") {
  0         0  
121             my $cururl = $path;
122 0         0 my @path = ();
  0         0  
123 0         0  
124 0         0 # This iterates back through the path to find the closest FILE downstream, using the rest of the url as a "path" argument
125 0         0 while (!-f $pageroot."/".$cururl.$m.$ext && $cururl =~ s/\/([^\/]*)$//) {
126             if ($1) { unshift(@path, $1); }
127             }
128 0   0     0  
129 0 0       0 # "Do" the file
  0         0  
130             if ($cururl) {
131             my $result;
132             if (defined $dofiles{$pageroot."/".$cururl.$m.$ext}) {
133 0 0       0 $stash->{dofiles}->{$cururl.$m.$ext} = { origin => "cache", order => $stash->{dofiles_executed}++ };
134 0         0 $result = $dofiles{$pageroot."/".$cururl.$m.$ext}->({path => \@path, this_url => $cururl, dofile_plugin => $plugin, stash => $stash, env => $app->request->env});
135 0 0       0  
    0          
136 0         0 } elsif (-f $pageroot."/".$cururl.$m.$ext) {
137 0         0 $stash->{dofiles}->{$cururl.$m.$ext} = { origin => "file", order => $stash->{dofiles_executed}++ };
138              
139             our $args = { path => \@path, this_url => $cururl, dofile_plugin => $plugin, stash => $stash, env => $app->request->env };
140 0         0  
141             $result = do($pageroot."/".$cururl.$m.$ext);
142 0         0 if ($@ || $!) { $plugin->app->log( error => "Error processing $pageroot / $cururl.$m.$ext: $@ $!\n"); }
143             if (ref $result eq "CODE") {
144 0         0 $stash->{dofiles}->{$cururl.$m.$ext}->{cached} = 1;
145 0 0 0     0 $dofiles{$pageroot."/".$cururl.$m.$ext} = $result;
  0         0  
146 0 0       0 $result = $result->($args);
147 0         0 }
148 0         0 }
149 0         0  
150             # We need to reassign the stash to the opts hash as the merge will have destroyed the old stash
151             $opts{stash} = $stash;
152              
153             if (defined $result && ref $result eq "HASH") {
154 0         0 $stash = $merger->merge($stash, $result);
155             if (defined $result->{url} && !defined $result->{done}) {
156 0 0 0     0 $path = $result->{url};
    0 0        
    0          
157 0         0 next OUTER;
158 0 0 0     0 }
159 0         0 if (defined $result->{view} && $result->{done}) {
160 0         0 $stash->{dofiles}->{$cururl.$m.$ext}->{last} = 1;
161             $stash->{'controller_result'} = $result;
162 0 0 0     0 return $plugin->view($result->{view}, path => \@path, this_url => $cururl, dofile_plugin => $plugin, stash => $stash, env => $app->request->env );
    0 0        
      0        
163 0         0  
164 0         0 } elsif (defined $result->{content} || $result->{url} || $result->{done}) {
165 0         0 $stash->{dofiles}->{$cururl.$m.$ext}->{last} = 1;
166             return $result;
167             }
168 0         0 # Move on to the next file
169 0         0  
170             } elsif (ref $result eq "ARRAY") {
171             $stash->{dofiles}->{$cururl.$m.$ext}->{last} = 1;
172             return { content => $result };
173              
174 0         0 } elsif (!ref $result && $result) {
175 0         0 # do we assume this is HTML? Or a file to use in templating? Who knows!
176             $stash->{dofiles}->{$cururl.$m.$ext}->{last} = 1;
177             return { content => $result };
178              
179 0         0 }
180 0         0 }
181             }
182             }
183              
184             # If we got here we didn't find a controller. We should fail over to see if it's just a view on its own (effectively this module or the route acts as the controller)
185             $opts{stash} = $stash;
186             if ($stash->{view}) {
187             $opts{'controller_arg'} = $arg;
188 0         0 return $plugin->view($stash->{view}, %opts);
189 0 0       0 } else {
190 0         0 return $plugin->view($arg, %opts);
191 0         0 }
192              
193 0         0 }
194              
195             my $plugin = shift;
196             my $arg = shift;
197             my %opts = @_;
198              
199 0     0 0 0 my $app = $plugin->app;
200 0         0 my $settings = $app->settings;
201 0         0 my $method = $app->request->method;
202              
203 0         0 my $accept = HTTP::Accept->new( $app->request->accept )->values();
204 0         0 push(@{$accept}, "");
205 0         0  
206             my $pageroot = $settings->{appdir};
207 0         0 if ($pageroot !~ /\/$/) {
208 0         0 $pageroot .= "/";
  0         0  
209             }
210 0         0 $pageroot .= $plugin->view_loc;
211 0 0       0  
212 0         0 my $path = $arg || $app->request->path;
213              
214 0         0 # If any one of these returns content then we stop processing any more of them
215             # Content is defined as an array ref (it's an Obj2HTML array), a hashref with a "content" element, or a scalar (assumed HTML string - it's not checked!)
216 0   0     0 # This can lead to some interesting results if someone doesn't explicitly return undef when they want to fall through to the next file
217             # as perl will return the last evaluated value, which would be intepretted as content according to the above rules
218              
219             my $merger = Hash::Merge->new('RIGHT_PRECEDENT');
220              
221             my $stash = $opts{stash} || {};
222              
223 0         0 # Safety first...
224             $path =~ s|/$|"/".$plugin->default_file|e;
225 0   0     0 $path =~ s|^/+||;
226             $path =~ s|\.\./||g;
227             $path =~ s|~||g;
228 0         0  
  0         0  
229 0         0 if (!$path) { $path = $plugin->default_file; }
230 0         0 if (-d $pageroot."/$path") {
231 0         0 if ($path !~ /\/$/) {
232             $path .= "/".$plugin->default_file;
233 0 0       0 } else {
  0         0  
234 0 0       0 return {
235 0 0       0 url => "/$path/",
236 0         0 redirect => 1,
237             done => 1
238             };
239 0         0 }
240             }
241             OUTER:
242             foreach my $ext (@{$plugin->view_extension_list}) {
243             foreach my $fmt (@{$accept}) {
244             if (defined $acceptext->{$fmt}) {
245             foreach my $m ("", "-$method", "-ANY") {
246 0         0 my $cururl = $path;
  0         0  
247 0         0 my @path = ();
  0         0  
248 0 0       0 # This iterates back through the path to find the closest FILE downstream, using the rest of the url as a "path" argument
249 0         0 while (!-f $pageroot."/".$cururl.$m.$acceptext->{$fmt}.$ext && $cururl =~ s/\/([^\/]*)$//) {
250 0         0 if ($1) { unshift(@path, $1); }
251 0         0 }
252              
253 0   0     0 # "Do" the file
254 0 0       0 if ($cururl) {
  0         0  
255             my $result;
256             if (defined $dofiles{$pageroot."/".$cururl.$m.$acceptext->{$fmt}.$ext}) {
257             $stash->{dofiles}->{$cururl.$m.$acceptext->{$fmt}.$ext} = { origin => "cache", order => $stash->{dofiles_executed}++ };
258 0 0       0 $result = $dofiles{$pageroot."/".$cururl.$m.$acceptext->{$fmt}.$ext}->({path => \@path, this_url => $cururl, dofile_plugin => $plugin, stash => $stash, env => $app->request->env});
259 0         0  
260 0 0       0 } elsif (-f $pageroot."/".$cururl.$m.$acceptext->{$fmt}.$ext) {
    0          
261 0         0 $stash->{dofiles}->{$cururl.$m.$acceptext->{$fmt}.$ext} = { origin => "file", order => $stash->{dofiles_executed}++ };
262 0         0  
263             our $args = { path => \@path, this_url => $cururl, dofile_plugin => $plugin, stash => $stash, env => $app->request->env };
264              
265 0         0 $result = do($pageroot."/".$cururl.$m.$acceptext->{$fmt}.$ext);
266             if ($@ || $!) { $plugin->app->log( error => "Error processing $pageroot / $cururl.$m.$acceptext->{$fmt}.$ext: $@ $!\n"); }
267 0         0 if (ref $result eq "CODE") {
268             $stash->{dofiles}->{$cururl.$m.$acceptext->{$fmt}.$ext}->{cached} = 1;
269 0         0 $dofiles{$pageroot."/".$cururl.$m.$acceptext->{$fmt}.$ext} = $result;
270 0 0 0     0 $result = $result->($args);
  0         0  
271 0 0       0 }
272 0         0 }
273 0         0  
274 0         0 # We need to reassign the stash to the opts hash as the merge will have destroyed the old stash
275             $opts{stash} = $stash;
276              
277             if (defined $result && ref $result eq "HASH") {
278             $result->{'content-type'} = $acceptext->{$fmt};
279 0         0 $stash->{dofiles}->{$cururl.$m.$acceptext->{$fmt}.$ext}->{last} = 1;
280             return $result;
281 0 0 0     0  
    0 0        
    0          
282 0         0 } elsif (ref $result eq "ARRAY") {
283 0         0 $stash->{dofiles}->{$cururl.$m.$acceptext->{$fmt}.$ext}->{last} = 1;
284 0         0 return { 'content-type' => $acceptext->{$fmt}, content => $result };
285              
286             } elsif (!ref $result && $result) {
287 0         0 # do we assume this is HTML? Or a file to use in templating? Who knows!
288 0         0 $stash->{dofiles}->{$cururl.$m.$acceptext->{$fmt}.$ext}->{last} = 1;
289             return { 'content-type' => $acceptext->{$fmt}, content => $result };
290              
291             }
292 0         0 }
293 0         0 }
294             }
295             }
296             }
297              
298             # If we got here we didn't find a do file that returned some content
299             return { status => 404 };
300              
301             }
302              
303 0         0  
304             # Backward compatibility
305             my $plugin = shift;
306             my $arg = shift;
307             my %opts = @_;
308              
309             my $app = $plugin->app;
310 7     7 0 215357 my $settings = $app->settings;
311 7         13 my $method = $app->request->method;
312 7         17 my $pageroot = $settings->{appdir};
313             if ($pageroot !~ /\/$/) {
314 7         25 $pageroot .= "/";
315 7         44 }
316 7         580 $pageroot .= $plugin->page_loc;
317 7         48  
318 7 50       28 my $path = $arg || $app->request->path;
319 7         15  
320             # If any one of these returns content then we stop processing any more of them
321 7         19 # Content is defined as an array ref (it's an Obj2HTML array), a hashref with a "content" element, or a scalar (assumed HTML string - it's not checked!)
322             # This can lead to some interesting results if someone doesn't explicitly return undef when they want to fall through to the next file
323 7   33     33 # as perl will return the last evaluated value, which would be intepretted as content according to the above rules
324              
325             my $merger = Hash::Merge->new('RIGHT_PRECEDENT');
326              
327             my $stash = $opts{stash} || {};
328              
329             # Safety first...
330 7         82 $path =~ s|/$|"/".$plugin->default_file|e;
331             $path =~ s|^/+||;
332 7   50     549 $path =~ s|\.\./||g;
333             $path =~ s|~||g;
334              
335 7         20 if (!$path) { $path = $plugin->default_file; }
  1         7  
336 7         35 if (-d $pageroot."/$path") {
337 7         15 if ($path =~ /\/$/) {
338 7         16 $path .= "/".$plugin->default_file;
339             } else {
340 7 50       16 return {
  0         0  
341 7 50       278 url => "/$path/",
342 0 0       0 redirect => 1,
343 0         0 done => 1
344             };
345             }
346 0         0 }
347              
348             if (!defined $stash->{dofiles_executed}) { $stash->{dofiles_executed} = 0; }
349             OUTER:
350             foreach my $ext (@{$plugin->extension_list}) {
351             foreach my $m ("", "-$method") {
352             my $cururl = $path;
353 7 50       28 my @path = ();
  7         20  
354              
355 7         13 # This iterates back through the path to find the closest FILE downstream, using the rest of the url as a "path" argument
  7         29  
356 24         130 while (!-f $pageroot."/".$cururl.$m.$ext && $cururl =~ s/\/([^\/]*)$//) {
357 42         251 if ($1) { unshift(@path, $1); }
358 42         65 }
359              
360             # "Do" the file
361 42   100     873 if ($cururl) {
362 13 50       51 my $result;
  13         268  
363             if (defined $dofiles{$pageroot."/".$cururl.$m.$ext}) {
364             $stash->{dofiles}->{$cururl.$m.$ext} = { origin => "cache", order => $stash->{dofiles_executed}++ };
365             $result = $dofiles{$pageroot."/".$cururl.$m.$ext}->({path => \@path, this_url => $cururl, dofile_plugin => $plugin, stash => $stash, env => $app->request->env});
366 42 50       112  
367 42         60 } elsif (-f $pageroot."/".$cururl.$m.$ext) {
368 42 50       487 $stash->{dofiles}->{$cururl.$m.$ext} = { origin => "file", order => $stash->{dofiles_executed}++ };
    100          
369 0         0  
370 0         0 our $args = { path => \@path, this_url => $cururl, dofile_plugin => $plugin, stash => $stash, env => $app->request->env };
371              
372             $result = do($pageroot."/".$cururl.$m.$ext);
373 10         75 if ($@ || $!) { $plugin->app->log( error => "Error processing $pageroot / $cururl.$m.$ext: $@ $!\n"); }
374             if (ref $result eq "CODE") {
375 10         114 $stash->{dofiles}->{$cururl.$m.$ext}->{cached} = 1;
376             $dofiles{$pageroot."/".$cururl.$m.$ext} = $result;
377 10         3811 $result = $result->($args);
378 10 50 33     255 }
  0         0  
379 10 50       41 }
380 0         0  
381 0         0 if (defined $result && ref $result eq "HASH") {
382 0         0 if (defined $result->{url} && !defined $result->{done}) {
383             $path = $result->{url};
384             next OUTER;
385             }
386 42 100 66     280 if (defined $result->{content} || $result->{url} || $result->{done}) {
    50 33        
    50          
387 10 100 100     54 $stash->{dofiles}->{$cururl.$m.$ext}->{last} = 1;
388 1         5 return $result;
389 1         8 } else {
390             $stash = $merger->merge($stash, $result);
391 9 100 100     54 }
      66        
392 6         22 # Move on to the next file
393 6         251  
394             } elsif (ref $result eq "ARRAY") {
395 3         17 $stash->{dofiles}->{$cururl.$m.$ext}->{last} = 1;
396             return { content => $result };
397              
398             } elsif (!ref $result && $result) {
399             # do we assume this is HTML? Or a file to use in templating? Who knows!
400 0         0 $stash->{dofiles}->{$cururl.$m.$ext}->{last} = 1;
401 0         0 return { content => $result };
402              
403             }
404             }
405 0         0 }
406 0         0 }
407              
408             # If we got here we didn't find a do file that returned some content
409             return { status => 404 };
410             }
411              
412             my ( $self, $view ) = @_;
413             return path($view);
414 1         42 }
415             my ( $self, $layout ) = @_;
416             return $layout;
417             }
418 0     0 0    
419 0           1;
420              
421              
422 0     0 0   =pod
423 0            
424             =head1 NAME
425              
426             Dancer2::Plugin::DoFile - A file based MVC style plugin for Dancer2
427              
428             =head1 SYNOPSYS
429              
430             In your config.yml
431              
432             plugins:
433             DoFile:
434             controller_loc: 'dofiles/controllers'
435             controller_extension_list: ['.ctl','.do']
436             view_loc: 'dofiles/views'
437             view_extension_list: ['.view','.po']
438             default_file: "index"
439              
440             Make sure you have created the directory used for the locations in your dancer application root.
441              
442             Within a route in dancer2:
443              
444             my $result = controller 'path/to/file'
445              
446             You must not include the extension of the file as part of the path, as this will
447             be added per the settings.
448              
449             An example route in Dancer2, not using HTML::Obj2HTML (the controller returns the
450             layout and tokens directly):
451              
452             get 'dashboard' => sub {
453             my $self = shift;
454             my $result = controller 'dashboards/user-dashboard';
455             return template $result->{layout} => $result->{tokens};
456             }
457              
458             An example default route that will search for controllers (or views) based on the
459             URI requested, and some handling of other controller return keys:
460              
461             prefix '/';
462             any qr{.*} => sub {
463             my $self = shift;
464              
465             my $result = controller undef; # Not specifying the controller to use will use the URI to guess
466              
467             # My controller might return all manner of different things; this is an example:
468             if ($result && ref $result eq "HASH") {
469             if (defined $result->{url}) {
470             if (defined $result->{redirect} && $result->{redirect} eq "forward") {
471             return forward $result->{url};
472             } else {
473             return redirect $result->{url};
474             }
475             }
476             if (defined $result->{status}) {
477             status $result->{status};
478             }
479             if (defined $result->{template}) {
480             set layout => $result->{template};
481             }
482             if (defined $result->{headers}) {
483             headers %{$result->{headers}};
484             }
485             if (defined $result->{content}) {
486             return template $result->{content}, $result->{tokens};
487             }
488             };
489             };
490              
491              
492             When the 1st parameter to 'controller' is undef it'll use the request URI to work
493             out what the file(s) to execute are.
494              
495             =head1 DESCRIPTION
496              
497             DoFile is a way of automatically pulling multiple perl files to execute as a way
498             to simplify routing complexity in Dancer2 for very large applications. In
499             particular it was designed to split out larger controllers into logical partitions
500             based on heirarchy of files compared to what's being requested.
501              
502             The magic will look through your filesystem for files to 'do' (execute), and
503             there may be several.
504              
505             An added benefit of using DoFile is it's ability to execute multiple files per
506             request, effectively allowing you to split controllers into sub-parts. For example,
507             you might have a "DoFile" that is always executed for /some/uri, and another
508             for POST or GET, and even another for the fact you hade /some too.
509              
510             =head2 File Search Ordering
511              
512             When presented with the URI C<path/to/file> DoFile will begin searching for
513             files that can be executed for this request, until it finds one that returns
514             something that looks like content, a URL or is told you're done, when it stops.
515              
516             Files are searched:
517              
518             =over 4
519              
520             =item * By extension
521              
522             The default extensions .ctl and .view are checked (.do and .po are legacy extensions),
523             unless defined in your config.yml. The intention here is that .do files contain
524             controller code and don't typically return content, but may return redirects. After
525             .do files have been executed, .view files are executed. These are expected to return
526             content.
527              
528             You can define as many extensions as you like. You could, for example have:
529             C<['.init','.do','.view','.final']>
530              
531             =item * Root/HTTP request method
532              
533             For each extension, first the "root" file C<file.ext> is tested, then a file
534             that matches C<file-METHOD.ext> is tested (where METHOD is the HTTP request
535             method for this request, .ext is the extension). Finally C<file-ANY.ext> is
536             checked.
537              
538             =item * Iterating up the directory tree
539              
540             If your call to C<path/to/file> results in a miss for C<path/to/file.ctl>, DoFile
541             will then test for C<path/to.ctl> and finally C<path.ctl> before moving on to
542             C<path/to/file-METHOD.ctl>
543              
544             Once DoFile has found one it will not transcend the directory tree any further.
545             Therefore defining C<path/to/file.ctl> and C<path/to.ctl> will not result in
546             both being executed for the URI C<path/to/file> - only the first will be
547             executed.
548              
549             =back
550              
551             If you define files like so:
552              
553             path.do
554             path/
555             to.view
556             to/
557             file-POST.do
558              
559             A POST to the URI C<path/to/file> will execute C<path.do>, then
560             C<path/to/file-POST.do> and finally C<path/to.view>.
561              
562             =head2 Arguments to the executed files
563              
564             During execution of the file a hashref called $args is available that contains
565             some important things.
566              
567             If the executed file returns a coderef, the coderef is executed with this same
568             hashref as the only argument.
569              
570             =over 4
571              
572             =item * path (arrayref)
573              
574             Anything that appears after the currently executing file on the URI. For example
575             if I request C</path/to/file> and DoFile is executing C<path-POST.do>, the
576             C<path> element will contain ['to','file']
577              
578             =item * this_url (string)
579              
580             The currently executing file without any extension. In the above example this
581             would be C<path>.
582              
583             =item * stash (hashref)
584              
585             The stash can be initially passed from the router:
586              
587             dofile 'path/to/file', stash => { option => 1 }
588              
589             The stash can be read/written to from each file that executes:
590              
591             if ($args->{stash}->{option} == 1) {
592             $args->{stash}->{anotheroption} = 2;
593             }
594              
595             Or if the file being executed returns a hashref that does not contain any of
596             the elements C<contents>, C<url> or C<done> (see below), it's merged into the
597             stash automatically for passing on to the next file to be executed
598              
599             The stash is used to pass internal state down the file chain.
600              
601             =item * dofile_plugin (object)
602              
603             Just in case the file being executed wants to mess about with Dancer2 or
604             the plugin's internals.
605              
606             =back
607              
608             =head2 How DoFile interprets individual executed files response
609              
610             The result (returned value) of each file is checked; if something is returned
611             DoFile will inspect the value to determine what to do next.
612              
613             =head3 Coderef (anonymous sub)
614              
615             You can return a coderef; this will be cached within the plugin and the file
616             will not be checked again, but the coderef will be executed each time that
617             "file" is requested. Generally whether the file should be checked or not is
618             left up to the application (e.g. C<plackup -R ./ -r ...>).
619              
620             In the case a coderef is used, when the code is executed it is passed one
621             argument, a hashref, which is the stash. This saves needing to import the stash
622             from within the code of the file.
623              
624             The return of the coderef will be evaluated exactly as below.
625              
626             =head3 Internal Redirects
627              
628             If a hashref is returned it's checked for a C<url> element but NO C<done>
629             element. In this case, the DoFile restarts from the begining using the new URL.
630             This is a method for internally redirecting. For example, returning:
631              
632             {
633             url => "account/login"
634             }
635              
636             Will cause DoFile to start over with the new URI C<account/login>, without
637             processing any more files from the old URI. The stash is preserved.
638              
639             =head3 Content
640              
641             If a scalar or arrayref is returned, it's wrapped into a hashref into the
642             C<contents> element and sent back to the router.
643              
644             If a hashref is returned and contains a C<contents> element, no more files will
645             be processed. The entire hashref is returned to the router. NB: the
646             C<contents> element must contain something that evals to true, else it's
647             considered not there.
648              
649             =head3 Done
650              
651             If a hashref is returned and there is a C<done> element that evals to a true
652             value, DoFile will stop processing files and return the returned hashref to
653             the router.
654              
655             =head3 Continue
656              
657             If a hashref is returned and there is no C<url>, C<content> or C<done> element
658             then the contents of the hasref is combined with the stash and DoFile will look
659             for the next file.
660              
661             If nothing is returned at all, DoFile will continue with the next file.
662              
663             =head2 What the router gets back
664              
665             DoFile will always return a hashref, even if the files being executed do not
666             return a hashref. This hashref may have anything, but the recommended design
667             is to return one of the following:
668              
669             =over 4
670              
671             =item * A C<contents> element
672              
673             The implication is that you've had the web page to be served back. Note that
674             DoFile doesn't care if this is a scalar string or an arrayref. This Plugin
675             was designed to work with Obj2HTML, so in the case of an arrayref the
676             implication is that Obj2HTML should be asked to convert that to HTML.
677              
678             =item * A C<url> element
679              
680             In this case the router should probably send a 30x response redirecting the
681             client, or perform an internal forward... implementors choice.
682              
683             =item * A C<status> element
684              
685             This could be used to set the status code for returning to the client
686              
687             =back
688              
689             DoFile may however return pretty much whatever you want to handle in your final
690             router code.
691              
692             =head1 EXAMPLES
693              
694             As noted, what's returned from a DoFile can contain anything. That gives you
695             the opportunity to do pretty much whatever you want with what's returned.
696              
697              
698             =head1 AUTHOR
699              
700             Pero Moretti
701              
702             =head1 COPYRIGHT AND LICENSE
703              
704             This software is copyright (c) 2022 by Pero Moretti.
705              
706             This is free software; you can redistribute it and/or modify it under
707             the same terms as the Perl 5 programming language system itself.