File Coverage

blib/lib/Pinwheel/Controller.pm
Criterion Covered Total %
statement 374 375 99.7
branch 173 174 99.4
condition 22 22 100.0
subroutine 58 58 100.0
pod 24 26 92.3
total 651 655 99.3


line stmt bran cond sub pod time code
1             package Pinwheel::Controller;
2              
3 4     4   598 use strict;
  4         7  
  4         150  
4 4     4   28 use warnings;
  4         8  
  4         97  
5              
6 4     4   23 use Carp;
  4         6  
  4         421  
7 4     4   23 use Exporter;
  4         36  
  4         205  
8              
9             require File::Find;
10 4     4   4340 use File::Slurp;
  4         81917  
  4         391  
11 4     4   46 use Scalar::Util qw(blessed);
  4         10  
  4         449  
12              
13 4     4   1905 use Pinwheel::Context;
  4         22  
  4         102  
14 4     4   2309 use Pinwheel::Database;
  4         12  
  4         171  
15 4     4   2172 use Pinwheel::Helpers;
  4         14  
  4         181  
16 4     4   2245 use Pinwheel::Mapper;
  4         14  
  4         145  
17 4     4   32 use Pinwheel::Model::Time;
  4         8  
  4         91  
18 4     4   2569 use Pinwheel::View::Data;
  4         16  
  4         187  
19 4     4   2807 use Pinwheel::View::ERB;
  4         17  
  4         23547  
20              
21             our @ISA = qw(Exporter);
22             our @EXPORT = qw(
23             url_for
24             redirect_to
25             render
26             render_to_string
27             render_404
28             render_500
29             set_header
30             expires_in
31             expires_at
32             expires_now
33             accepts
34             respond_to
35             params
36             query_params
37             );
38             our @EXPORT_OK = qw(
39             connect
40             dispatch
41             format
42             set_base_format
43             set_format_param
44             expand_static_path
45             set_static_root
46             set_templates_root
47             request_time
48             request_time_model
49             );
50              
51              
52             our $map = new Pinwheel::Mapper;
53             our $static_root = '.';
54             our $templates_root = '.';
55             our %template_types = (
56             'tmpl' => \&Pinwheel::View::ERB::parse_template,
57             'erb' => \&Pinwheel::View::ERB::parse_template,
58             'data' => \&Pinwheel::View::Data::parse_template,
59             );
60             our $format_param = 'format';
61             our %format_defaults = (
62             'atom' => { content_type => 'application/atom+xml', layout => 0 },
63             'css' => { content_type => 'text/css', layout => 0 },
64             'gif' => { content_type => 'image/gif', layout => 0 },
65             'html' => { content_type => 'text/html' },
66             'ics' => { content_type => 'text/calendar', layout => 0 },
67             'json' => { content_type => 'application/json', layout => 0 },
68             'mp' => { content_type => 'text/html' },
69             'ram' => { content_type => 'audio/x-pn-realaudio', layout => 0 },
70             'rdf' => { content_type => 'application/rdf+xml' },
71             'rss' => { content_type => 'application/rss+xml', layout => 0 },
72             'ssi' => { content_type => 'text/plain', layout => 0 },
73             'sssi' => { content_type => 'text/html', layout => 0 },
74             'txt' => { content_type => 'text/plain', layout => 0 },
75             'wml' => { content_type => 'text/vnd.wap.wml' },
76             'xml' => { content_type => 'application/xml', layout => 0 },
77             'yaml' => { content_type => 'application/x-yaml', layout => 0 },
78             );
79             our $error_logger = \&default_error_logger;
80             our (%template_cache, %controllers, $layout_helpers);
81              
82              
83             sub initialise
84             {
85 1     1 0 947 my ($hooks);
86              
87 1         4 $hooks = \%Config::Hooks::;
88 1 50       10 $hooks->{initialise}() if (exists($hooks->{initialise}));
89             }
90              
91             sub connect
92             {
93 24     24 1 225 return $map->connect(@_);
94             }
95              
96             sub dispatch
97             {
98 21     21 1 23771 my ($request, %args) = @_;
99 21         262 my ($ctx, $hooks);
100              
101             # Odd idiom, just to keep coverage happy
102             # Usually: $request->{time} ||= time();
103 21 100       330 $request->{time} = time() if not $request->{time};
104              
105 21         96 Pinwheel::Context::reset();
106              
107 21         68 $ctx = Pinwheel::Context::get();
108 21         69 $ctx->{request} = $request;
109 21         79 $ctx->{response} = {headers => {}};
110 21         45 $ctx->{rendering} = 0;
111              
112 21         85 Pinwheel::Context::get('render')->{format} = ['html'];
113              
114 21         57 $hooks = \%Config::Hooks::;
115 21 100       158 if (exists($hooks->{before_dispatch})) {
116 1         8 $hooks->{before_dispatch}($request);
117             }
118              
119 21         50 eval {
120             local $SIG{__DIE__} = sub {
121 5     5   104 local $Carp::CarpLevel = $Carp::CarpLevel + 2;
122 5         8903 Carp::confess($_[0]);
123 21         186 };
124 21         83 _process_request(\%args, $ctx);
125 17 100       136 render() unless $ctx->{headers};
126 16 100       178 if ($hooks->{after_dispatch})
127             {
128 1         7 $hooks->{after_dispatch}($ctx->{headers}, \$ctx->{content});
129             }
130             };
131              
132             # Defensive: pass a copy of $@, not $@ itself
133 21 100       1625 render_500("$@") if ($@);
134              
135 21         98 Pinwheel::Database::finish_all();
136              
137 21         107 return ($ctx->{headers}, $ctx->{content});
138             }
139              
140             sub _process_request
141             {
142 28     28   5213 my ($args, $ctx) = @_;
143 28         46 my ($hooks, $route, $controller, $fn);
144              
145 28         239 $route = $map->match($ctx->{request}{path}, $ctx->{request}{method});
146 28 100       122 $route = undef if (!_check_route_params($route));
147 28 100       87 return render_404('No matching route') unless $route;
148             # $route->{controller} and $route->{action} are now guaranteed to be
149             # strings matching /^[a-z][a-z0-9_]*$/
150              
151 23         88 $controller = _get_controller($route->{controller});
152 23 100       72 return render_404('Controller not found') unless $controller;
153 22         95 $fn = $controller->{actions}{$route->{action}};
154 22 100       72 return render_404('Action is missing or not in @ACTIONS') unless $fn;
155              
156 21         55 $ctx->{route} = $route;
157 21         114 $ctx->{controller} = $controller;
158 21         73 &$fn();
159             }
160              
161             sub url_for
162             {
163 26 100   26 1 3902 my $name = (scalar(@_) & 1) ? shift : undef;
164 26         83 my %params = @_;
165 26         38 my ($ctx, $only_path, $path, $base);
166              
167 26         84 $ctx = Pinwheel::Context::get();
168              
169 26         55 $only_path = delete $params{only_path};
170 26 100       87 $only_path = 1 unless defined($only_path);
171 26         161 $path = $map->generate($name, %params, _base => $ctx->{route});
172 26 100       106 return undef unless $path;
173              
174 25 100 100     133 if ($ctx->{request} && $path !~ /^\w+:\/\//) {
175 12         34 $path = $ctx->{request}{base} . $path;
176 12 100       44 $path = "http://$ctx->{request}{host}$path" unless $only_path;
177             }
178 25         105 return $path;
179             }
180              
181             sub redirect_to
182             {
183 9     9 1 22 my ($ctx, $status);
184 9         14 my $url = shift;
185 9         20 my %options = ();
186              
187 9 100       34 %options = @_ if (scalar(@_)>1);
188 9   100     40 $status = delete $options{status} || 302;
189              
190 9         23 $ctx = Pinwheel::Context::get();
191 9 100 100     50 if (defined $url && $url =~ /\//) {
192 5 100       29 $url = "http://$ctx->{request}{host}$url" if ($url !~ /^\w+:\/\//);
193             } else {
194 4         16 $url = url_for($url, %options, only_path => 0);
195             }
196              
197             # XXX $url might be undef, which should trigger a 500-ish error
198 9         39 render(text => "Please see $url\n", status => $status, location => $url);
199             }
200              
201             sub render
202             {
203 54     54   1684 my %options = @_;
204 54         97 my ($ctx, $content, $format);
205              
206 54         183 $ctx = Pinwheel::Context::get();
207              
208             # XXX an error has occurred (double render) if this happens: don't silently
209             # ignore it!
210 54 100       1242 return if ($ctx->{headers});
211              
212             # Set the top-level output format
213 53 100 100     393 if (!$ctx->{rendering} && $options{format}) {
214 1         6 Pinwheel::Context::get('render')->{format} = [$options{format}];
215             }
216              
217 53         100 $ctx->{rendering}++;
218 53         396 ($content, $format) = _render_content(\%options);
219 50         110 $ctx->{rendering}--;
220              
221 50 100       223 if ($ctx->{rendering} == 0) {
222 48 100       217 $content = $content->to_string($format) if ref($content);
223 48         223 set_header('Content-Length', length($content));
224 48         166 $ctx->{headers} = _render_headers(\%options, $format);
225 48         123 $ctx->{content} = $content;
226             }
227 50         175 return $content;
228             }
229              
230             sub render_to_string
231             {
232 3     3 1 1966 my %options = @_;
233 3         5 my ($ctx, $content);
234              
235 3         62 $ctx = Pinwheel::Context::get();
236 3         8 $ctx->{rendering}++;
237 3         9 ($content) = _render_content(\%options);
238 3         6 $ctx->{rendering}--;
239 3         12 return $content;
240             }
241              
242             sub default_error_logger
243             {
244 2     2 0 6 my ($status, $msg, $depth) = @_;
245 2 100       13 Carp::cluck("render_error [$depth]: $status $msg")
246             if $status == 500;
247             }
248              
249             sub render_error
250             {
251 22     22   2972 my ($status, $msg) = @_;
252 22         41 my ($ctx, $format, $template);
253              
254 22         86 $ctx = Pinwheel::Context::get();
255 22         56 $ctx->{headers} = undef;
256 22         42 $ctx->{rendering} = 0;
257 22 100       81 $ctx->{error}++ if ($status == 500);
258              
259 22   100     148 &$error_logger($status, $msg, $ctx->{error}||0);
260              
261 22 100 100     144 if (($ctx->{error} || 0) < 2) {
262 21         62 $format = Pinwheel::Context::get('render')->{format}[0];
263 21         109 $template = _get_template("shared/error$status.$format");
264             }
265              
266 22 100       73 if (!$template) {
267 3         8 render(text => $msg, status => $status);
268             } else {
269 19         35 eval {
270 19         129 render(
271             template => "shared/error$status",
272             status => $status,
273             locals => { msg => $msg }
274             );
275             };
276              
277             # Defensive: pass a copy of $@, not $@ itself
278 19 100       83 render_500("$@") if $@;
279             }
280              
281 22 100       97 $ctx->{error}-- if ($status == 500);
282             }
283              
284             sub render_404
285             {
286 9     9 1 1838 render_error(404, @_);
287             }
288              
289             sub render_500
290             {
291 10     10 1 4628 render_error(500, @_);
292             }
293              
294             sub set_header
295             {
296 68     68 1 1400 my ($key, $value) = @_;
297 68         267 my ($ctx);
298              
299 68         195 $ctx = Pinwheel::Context::get();
300 68         635 $ctx->{response}{headers}{lc($key)} = [$key, $value];
301             }
302              
303             sub request_time
304             {
305 5     5 1 5013924 my $ctx = Pinwheel::Context::get();
306 5         52 return $ctx->{request}{time};
307             }
308              
309             sub request_time_model
310             {
311 5     5 1 84 my $ctx = Pinwheel::Context::get();
312 5         72 return Pinwheel::Model::Time->new($ctx->{request}{time});
313             }
314              
315             sub expires_in
316             {
317 2     2 1 2284 my ($seconds) = @_;
318 2         4 my ($ctx, $now);
319              
320 2         9 $ctx = Pinwheel::Context::get();
321 2         11 $now = Pinwheel::Model::Time->new($ctx->{request}{time});
322 2         12 set_header('Date', $now->rfc822);
323 2         11 set_header('Expires', $now->add($seconds)->rfc822);
324 2         11 set_header('Cache-Control', 'max-age=' . $seconds);
325             }
326              
327             sub expires_at
328             {
329 1     1 1 4 my ($expires) = @_;
330 1         2 my ($ctx, $now, $seconds);
331              
332 1         6 $ctx = Pinwheel::Context::get();
333 1         8 $now = Pinwheel::Model::Time->new($ctx->{request}{time});
334 1         5 $seconds = $expires->timestamp - $now->timestamp;
335 1         6 set_header('Date', $now->rfc822);
336 1         7 set_header('Expires', $expires->rfc822);
337 1         6 set_header('Cache-Control', 'max-age=' . $seconds);
338             }
339              
340             sub expires_now
341             {
342 1     1 1 4274 set_header('Cache-Control', 'no-cache');
343 1         4 set_header('Pragma', 'no-cache');
344             }
345              
346             sub accepts
347             {
348 8     8 1 23 my ($ctx, $format);
349              
350 8         22 $ctx = Pinwheel::Context::get('render');
351 8         23 $format = Pinwheel::Context::get()->{route}{$format_param};
352 8 100       25 $format = $ctx->{format}[0] if (!$format);
353              
354 8         22 foreach (@_) {
355 11 100       32 if ($format eq $_) {
356 5         11 $ctx->{format}[0] = $format;
357 5         27 return 1;
358             }
359             }
360 3         12 render_error(404, 'Format not supported');
361 3         21 return 0;
362             }
363              
364             sub respond_to
365             {
366 6     6 1 10 my ($ctx, $format, $old_format, $fn);
367              
368 6         16 $ctx = Pinwheel::Context::get('render');
369 6         12 $old_format = pop @{$ctx->{format}};
  6         19  
370 6         17 $format = Pinwheel::Context::get()->{route}{$format_param};
371 6 100       19 $format = $old_format if (!$format);
372 6         8 push @{$ctx->{format}}, $format;
  6         19  
373              
374 6         10 my %handlers;
375 6         16 while (@_)
376             {
377 16         22 my $key = shift;
378 16 100       39 my $handler = ((ref($_[0]) eq "CODE") ? shift : undef);
379 16         44 $handlers{$key} = $handler;
380             }
381              
382 6 100       17 if (!exists($handlers{$format})) {
383 1         5 render_error(404, 'Format not supported');
384             } else {
385 5         11 $fn = $handlers{$format};
386 5 100       30 if (defined($fn)) {
387 1         13 &$fn();
388             } else {
389 4         14 render(format => $format);
390             }
391             }
392              
393 6         46 $ctx->{format}[-1] = $old_format;
394             }
395              
396             sub params
397             {
398 5     5 1 29 my $route = Pinwheel::Context::get()->{route};
399              
400 5 100       21 return $route->{$_[0]} if scalar(@_) == 1;
401 4         10 return [map { $route->{$_} } @_];
  10         46  
402             }
403              
404             sub query_params
405             {
406 10     10 1 25 my ($ctx, $q);
407              
408 10         29 $ctx = Pinwheel::Context::get();
409 10 100       36 unless ($q = $ctx->{query}) {
410 7         17 my $t = $ctx->{request}{query};
411 7 100       18 $t = '' if not defined $t;
412 12 100       34 $q = $ctx->{query} = {
413 7         37 map { ($_ eq "") ? () : _query_key_value($_) } split(/&+/, $t)
414             };
415             }
416              
417 10 100       61 return $q->{$_[0]} if scalar(@_) == 1;
418 4         9 return [map { $q->{$_} } @_];
  10         46  
419             }
420              
421             sub _query_key_value
422             {
423 11     11   16 my ($s) = @_;
424 11         15 my ($key, $value);
425              
426 11         16 $s =~ tr/+/ /;
427              
428 11         27 ($key, $value) = split('=', $s, 2);
429 11         22 $key =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/ge;
  1         6  
430 11 100       32 $value =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/ge if $value;
  2         10  
431              
432 11   100     70 return ($key, $value || '');
433             }
434              
435             sub format
436             {
437 6     6 1 29 return Pinwheel::Context::get('render')->{format}[-1];
438             }
439              
440             sub set_base_format
441             {
442 2     2 1 8 Pinwheel::Context::get('render')->{format}[0] = $_[0];
443             }
444              
445             sub set_format_param
446             {
447 5     5 1 17 $format_param = $_[0];
448             }
449              
450             sub expand_static_path
451             {
452 13     13 1 37 my ($path) = @_;
453 13         80 my ($ctx, $base);
454              
455 13 100       68 $path = '/' . $path unless $path =~ /^\//;
456              
457 13         39 $ctx = Pinwheel::Context::get();
458 13 100       40 if ($ctx->{request}) {
459 3         6 $base = $ctx->{request}{base};
460 3         38 $path =~ s/^$base//;
461             }
462              
463 13 100       694 return if ($path =~ /\/\./);
464              
465 8         29 $path =~ s/\/{2,}/\//g;
466 8         69 return $static_root . $path;
467             }
468              
469             sub set_static_root
470             {
471 1     1 1 7 $static_root = shift;
472             }
473              
474             sub set_templates_root
475             {
476 2     2 1 300 $templates_root = shift;
477             }
478              
479              
480             # ==============================================================================
481              
482              
483             sub _render_headers
484             {
485 67     67   554 my ($opts, $format) = @_;
486 67         101 my ($h, $type);
487              
488 67         184 $h = Pinwheel::Context::get()->{response}{headers};
489              
490 67 100       258 $h->{'status'} = ['Status', $opts->{status}] if $opts->{status};
491 67 100       288 $h->{'status'} = ['Status', 200] unless $h->{'status'};
492              
493 67 100       279 $type = $format_defaults{$format}{content_type} if $format;
494 67 100       149 $type = 'text/html' unless $type;
495 67 100       199 $type = $h->{'content-type'}[1] if $h->{'content-type'};
496 67 100       171 $type = $opts->{content_type} if $opts->{content_type};
497 67         561 $h->{'content-type'} = ['Content-Type', $type];
498              
499 67 100       410 $h->{'location'} = ['Location', $opts->{location}] if $opts->{location};
500              
501 67         233 return $h;
502             }
503              
504             sub _render_content
505             {
506 66     66   16977 my $options = shift;
507 66         125 my ($ctx, $renderctx, $name, $format, $srcformat);
508 0         0 my ($content, $template, $layout);
509              
510 66 100       331 if (exists($options->{text})) {
511             # Render a static piece of text
512 19         40 $content = $options->{text};
513 19         45 $format = 'txt';
514             } else {
515 47         145 $ctx = Pinwheel::Context::get();
516 47         143 $renderctx = Pinwheel::Context::get('render');
517              
518 47         168 $name = _make_template_name($options, $ctx->{route});
519 47 100       339 croak "Invalid template name" unless $name;
520 46         88 $format = $options->{format};
521 46 100       164 $format = $renderctx->{format}[-1] if !$format;
522 46         70 $srcformat = $options->{via};
523 46 100       110 $srcformat = $format if !$srcformat;
524 46         161 $template = _get_template("$name.$srcformat");
525 46 100       798 croak "Unable to find template $name" unless $template;
526 44         160 $layout = _get_layout($srcformat, $options);
527              
528 44         74 push @{$renderctx->{format}}, $srcformat;
  44         155  
529 44         205 $content = _render_template($template, $layout, $options->{locals});
530 42         88 pop @{$renderctx->{format}};
  42         301  
531             }
532              
533 61         229 return ($content, $format);
534             }
535              
536             sub _render_template
537             {
538 52     52   241 my ($template, $layout, $locals) = @_;
539 52         325 my ($ctx, $globals, $helpers, $content);
540              
541 52         331 $ctx = Pinwheel::Context::get();
542              
543 52 100       181 $locals = {} if (!$locals);
544 52         149 $globals = Pinwheel::Context::get('template');
545 52         132 $helpers = $ctx->{controller}{helpers};
546 52 100       181 $helpers = _get_helpers(['Pinwheel::Helpers', 'Application']) if (!$helpers);
547             # Any supplied locals are sent to the layout too, so don't pass a reference
548             # to the original here or it might be changed
549 52         2396 $content = $template->({%$locals}, $globals, $helpers);
550              
551             # The layout (if any) is added afterwards so that it has access to any
552             # state built up by the content template, eg a list of Javascript or CSS
553             # files to include
554 50 100       298 if ($layout) {
555 34         117 Pinwheel::Context::get('render')->{content}{'layout'} = $content;
556 34         160 $content = $layout->($locals, $globals, _get_layout_helpers());
557             }
558              
559 50         176 return $content;
560             }
561              
562             sub preload_templates
563             {
564 2     2 1 7 my ($types, $w);
565              
566 2         13 $types = join('|', keys %template_types);
567             $w = sub {
568 36 100   36   1195 if (/^\Q${templates_root}\E\/(.+)\.(${types})$/) {
569             # Ignore errors. Could mean there are files in the templates
570             # directory that aren't named like valid templates.
571 27 100       1292 _get_template($1) if -f $_;
572             }
573 2         14 };
574              
575 2         138 File::Find::find({ no_chdir => 1, wanted => $w }, $templates_root);
576             }
577              
578             sub _get_template
579             {
580 186     186   4620 my ($name) = @_;
581 186         244 my ($ext, $filename, $template);
582              
583 186 100       724 return $template_cache{$name} if exists($template_cache{$name});
584 68 100       395 return if $name !~ m{^\w+/\w+\.\w+$};
585              
586 59         211 foreach $ext (keys %template_types) {
587 133 100       9604 next unless -f ($filename = "$templates_root/$name.$ext");
588 44         221 $template = read_file($filename, binmode => ':raw');
589 44         14416 $template = $template_types{$ext}->($template, $name);
590 44         205 $template_cache{$name} = $template;
591 44         687 return $template;
592             }
593              
594             # Cache failed lookups to avoid hitting the filesystem repeatedly for
595             # non-existent layout files etc.
596 15         85 $template_cache{$name} = undef;
597             }
598              
599             sub _get_layout
600             {
601 53     53   1341 my ($format, $options) = @_;
602 53         152 my ($ctx, $controller, $base, $name, $layout);
603              
604 53         263 $ctx = Pinwheel::Context::get();
605 53         161 $controller = $ctx->{route}{controller};
606              
607             # When rendering a partial the layout comes from the same directory,
608             # otherwise it comes from 'layouts'.
609 53 100       233 if ($options->{partial}) {
610 7 100       49 $base = $1 if $options->{partial} =~ /^(\w+)\//;
611 7 100       24 $base = $controller if !$base;
612 7         16 $base = $base . '/_';
613             } else {
614 46         99 $base = 'layouts/';
615             }
616              
617 53 100       391 if (defined($options->{layout})) {
    100          
    100          
618 10         22 $name = $options->{layout};
619             } elsif ($options->{partial}) {
620 1         2 $name = 0;
621             } elsif (defined($ctx->{controller}{layout})) {
622 1         4 $name = $ctx->{controller}{layout};
623             } else {
624 41         131 $name = $format_defaults{$format}{layout};
625             }
626              
627 53 100       187 if (!defined($name)) {
    100          
628             # Automatic layout (eg, no layout option or layout => undef)
629 33 100       164 $layout = _get_template("${base}$controller.$format") if $controller;
630 33 100       166 $layout = _get_template("${base}application.$format") if !$layout;
631             } elsif ($name) {
632             # Specified layout (eg, layout => 'foo')
633 10         47 $layout = _get_template("${base}$name.$format");
634 10 100       307 croak "Unable to find ${base}$name" unless $layout;
635             } else {
636             # Layout disabled (eg, layout => 0)
637             }
638 52         159 return $layout;
639             }
640              
641              
642             sub _get_controller
643             {
644 31     31   11388 my $name = shift;
645 31         54 my ($info, $pkgname, $pkg, $layout, $helpers);
646              
647 31 100       324 return $info if ($info = $controllers{$name});
648              
649 6         22 $pkgname = _make_mixed_case($name);
650 6         27 $pkg = _get_package('Controllers::' . $pkgname);
651 6 100       32 return unless $pkg->{'ACTIONS'};
652              
653 4         10 $layout = $pkg->{'LAYOUT'};
654 4 100       14 $layout = $$layout if $layout;
655 4   100     25 $helpers = $pkg->{'HELPERS'} || [];
656 4         18 $helpers = ['Pinwheel::Helpers', 'Application', @$helpers, $pkgname];
657 4         15 return $controllers{$name} = {
658             layout => $layout,
659             helpers => _get_helpers($helpers),
660             actions => _get_actions($pkg)
661             };
662             }
663              
664             sub _get_layout_helpers
665             {
666 36 100   36   3298 if (!$layout_helpers) {
667 1         5 $layout_helpers = _get_helpers(['Pinwheel::Helpers', 'Application']);
668             }
669 36         1257 return $layout_helpers;
670             }
671              
672             sub _get_helpers
673             {
674 21     21   1154 my $helpers = shift;
675 21         34 my ($name, $fns, $pkg, $exports);
676              
677 21         46 $fns = {};
678 21         50 foreach $name (@$helpers) {
679 44 100       175 $name = "Helpers::$name" unless $name =~ /::/;
680 44         110 $pkg = _get_package($name);
681 44 100       212 next unless $exports = $pkg->{'EXPORT_OK'};
682 42         89 foreach (@$exports) {
683 62 100       176 next unless $pkg->{$_};
684 61         75 $fns->{$_} = \&{$pkg->{$_}};
  61         333  
685             }
686             }
687 21         70 return $fns;
688             }
689              
690             sub _get_actions
691             {
692 4     4   10 my $pkg = shift;
693 4         6 my ($actions, $fns);
694              
695 4         10 $actions = $pkg->{'ACTIONS'};
696 4         11 foreach (@$actions) {
697 7 100       23 next unless $pkg->{$_};
698 6         10 $fns->{$_} = \&{$pkg->{$_}};
  6         28  
699             }
700              
701 4         34 return $fns;
702             }
703              
704              
705             sub _check_route_params
706             {
707 39     39   465 my ($params) = @_;
708              
709             return (
710 39   100     587 $params
711             && $params->{controller} =~ /^[a-z][a-z0-9_]*$/
712             && $params->{action} =~ /^[a-z][a-z0-9_]*$/
713             );
714             }
715              
716              
717             sub _make_template_name
718             {
719 67     67   499 my ($options, $route) = @_;
720 67         96 my ($name, $format);
721              
722 67 100       285 if ($options->{action}) {
    100          
    100          
723 8         23 $name = "$route->{controller}/$options->{action}";
724             } elsif ($options->{template}) {
725 32         58 $name = $options->{template};
726             } elsif ($options->{partial}) {
727 11         24 $name = $options->{partial};
728 11 100       59 $name = "$route->{controller}/$name" unless $name =~ /\//;
729 11         60 $name =~ s!/!/_!;
730             } else {
731 16         68 $name = "$route->{controller}/$route->{action}";
732             }
733              
734 67 100       800 return unless $name =~ m{^(\w+/\w+)$};
735 54         169 return $name;
736             }
737              
738              
739             sub _make_mixed_case
740             {
741 12     12   348 my $name = shift;
742             # Convert some_name to SomeName
743 12         47 $name =~ s/_+/ /g;
744 12         116 $name =~ s/\b(\w)/\U$1/g;
745 12         36 $name =~ s/ +//g;
746 12         51 return $name;
747             }
748              
749             sub _get_package
750             {
751 50     50   160 my $name = shift;
752 50         85 my $pkg = \%::;
753 50         576 $pkg = $pkg->{"$_\::"} foreach split(/::/, $name);
754 50         169 return $pkg;
755             }
756              
757             1;
758              
759             __DATA__