File Coverage

lib/Dancer/Template/Caribou.pm
Criterion Covered Total %
statement 88 90 97.7
branch 11 18 61.1
condition n/a
subroutine 21 23 91.3
pod 4 8 50.0
total 124 139 89.2


line stmt bran cond sub pod time code
1             package Dancer::Template::Caribou;
2             our $AUTHORITY = 'cpan:YANICK';
3             #ABSTRACT: Template::Caribou wrapper for Dancer
4             $Dancer::Template::Caribou::VERSION = '1.0.0';
5             # TODO I think the template loading can be simplified
6              
7 2     2   2792 use strict;
  2         2  
  2         56  
8 2     2   7 use warnings;
  2         2  
  2         44  
9              
10 2     2   676 use Path::Tiny;
  2         7607  
  2         92  
11 2     2   1008 use Path::Iterator::Rule;
  2         15523  
  2         63  
12 2     2   660 use Moose::Util qw/ with_traits find_meta /;
  2         84714  
  2         21  
13 2     2   627 use FindBin;
  2         2  
  2         84  
14 2     2   8 use Dancer::Config qw/ setting /;
  2         2  
  2         81  
15              
16 2     2   1004 use Moo;
  2         12721  
  2         8  
17              
18             extends 'Dancer::Template::Abstract';
19              
20 0     0   0 sub _build_name { 'Dancer::Template::Caribou' };
21              
22             has 'default_tmpl_ext' => (
23             is => 'ro',
24             default => sub { 'bou' },
25             );
26              
27             has view_class => (
28             is => 'ro',
29             default => sub { { } },
30             );
31              
32             has layout_class => (
33             is => 'ro',
34             default => sub { { } },
35             );
36              
37             has namespace => (
38             is => 'ro',
39             lazy => 1,
40             default => sub {
41             $_[0]->config->{namespace} || 'Dancer::View';
42             },
43             );
44              
45             sub BUILD {
46 2     2 0 11 my $self = shift;
47              
48 2         12 my $views_dir = setting 'views';
49              
50 2         35 my @views =
51             Path::Iterator::Rule->new->skip_dirs('layouts')->file->name('bou')->all(
52             $views_dir );
53              
54 2         2316 $self->generate_view_class( $_ ) for @views;
55              
56 2         15 my @layouts =
57             Path::Iterator::Rule->new->file->name('bou')->all(
58             path( $views_dir, 'layouts' ) );
59              
60 2         1173 $self->generate_layout_class( $_ ) for @layouts;
61             }
62              
63             sub generate_layout_class {
64 1     1 0 2 my( $self, $bou ) = @_;
65              
66 1         3 my $bou_dir = path($bou)->parent;
67 1         72 my $segment = ''.path($bou)->relative( setting( 'views').'/layouts')->parent;
68              
69 1         317 ( my $name = $segment ) =~ s#/#::#;
70 1         20 $name = join '::', $self->namespace, $name;
71              
72 1         10 my $inner = path($bou)->slurp;
73              
74 1 50   1   82 eval qq{
  1     1   1  
  1         6  
  1         3717  
  1         1  
  1         6  
  1         152  
75             package $name;
76              
77             use Moose::Role;
78             use Template::Caribou;
79              
80             with 'Template::Caribou::Files' => {
81             dirs => [ '$bou_dir' ],
82             };
83              
84             # line 1 "$bou"
85              
86             $inner
87              
88             1;
89             } unless find_meta( $name );
90              
91 1 50       5 warn $@ if $@;
92              
93 1         25 $self->layout_class->{$segment} = $name;
94              
95             }
96              
97             sub generate_view_class {
98 3     3 0 5 my( $self, $bou ) = @_;
99              
100 3         13 my $bou_dir = path($bou)->parent;
101 3         271 my $segment = ''.path($bou)->relative(setting('views'))->parent;
102              
103 3         761 ( my $name = $segment ) =~ s#/#::#;
104 3         41 $name = join '::', $self->namespace, $name;
105              
106 3 50       41 return if $self->layout_class->{$segment};
107              
108 3         8 my $inner = path($bou)->slurp;
109              
110 1 50   1   487 eval qq{
  1     1   251032  
  1     1   4  
  1         81  
  1         1  
  1         6  
  1         79  
  1         2  
  1         6  
  3         465  
111             package $name;
112              
113             use Template::Caribou;
114              
115             with 'Template::Caribou::Files' => {
116             dirs => [ '$bou_dir' ],
117             auto_reload => 1,
118             };
119              
120             has app => (
121             is => 'ro',
122             handles => [ 'config' ],
123             );
124              
125             # line 1 "$bou"
126              
127             $inner
128              
129             1;
130             } unless find_meta($name);
131              
132 3 50       15 warn $@ if $@;
133              
134 3         33 $self->view_class->{$segment} = $name;
135              
136             }
137              
138             sub apply_layout {
139 4     4 0 227 return $_[1];
140             }
141              
142             sub layout {
143 0     0 1 0 return $_[3];
144             }
145              
146             sub render {
147 4     4 1 264 my( $self, $template, $tokens ) = @_;
148              
149 4         8 $DB::single = 1;
150            
151              
152 4         7 $template =~ s/\.bou$//;
153              
154 4         12 my $class = $self->view_class->{$template};
155              
156 4 100       12 unless ( $class ) {
157 1         2 my $c = $template;
158 1         1 $c =~ s#/#::#g;
159 1         5 $c = join '::', $self->namespace, $c;
160             die "template '$template' not found\n"
161 1 50       12 unless eval { $c->DOES('Template::Caribou::Role') };
  1         33  
162 1         237 $class = $c;
163             }
164              
165 4 100       28 if ( my $lay = Dancer::App->current->setting('layout') ) {
166 1 50       20 my $role = $self->layout_class->{$lay}
167             or die "layout '$lay' not defined\n";
168              
169 1         4 $class = with_traits( $class, $role,
170             )
171             }
172              
173 4         15107 my $x = $class->new( %$tokens)->render('page');
174 2     2   7443 use utf8;utf8::decode($x);
  2         17  
  2         8  
  4         8095  
175 4         315 return $x;
176             }
177              
178             sub view {
179 8     8 1 11832 my( $self, $view ) = @_;
180 8         18 return $view;
181             }
182              
183             sub view_exists {
184 4     4 1 16 1;
185             }
186              
187              
188             1;
189              
190             __END__
191              
192             =pod
193              
194             =encoding UTF-8
195              
196             =head1 NAME
197              
198             Dancer::Template::Caribou - Template::Caribou wrapper for Dancer
199              
200             =head1 VERSION
201              
202             version 1.0.0
203              
204             =head1 SYNOPSIS
205              
206             # in 'config.yml'
207             template: Caribou
208              
209             engines:
210             template:
211             Caribou:
212             namespace: MyApp::View
213             auto_reload: 1
214              
215              
216             # and then in the application
217             get '/' => sub {
218             ...;
219              
220             template 'main' => \%options;
221             };
222              
223             =head1 DESCRIPTION
224              
225             C<Dancer::Template::Caribou> is an interface for the L<Template::Caribou>
226             template system. Be forewarned, both this module and C<Template::Caribou>
227             itself are alpha-quality software and are still subject to any changes. <Caveat
228             Maxima Emptor>.
229              
230             =head2 Basic Usage
231              
232             At the base, if you do
233              
234             get '/' => sub {
235             ...
236              
237             return template 'MyView', \%options;
238             };
239              
240             the template name (here I<MyView>) will be concatenated with the
241             configured view namespace (which defaults to I<Dancer::View>)
242             to generate the Caribou class name. A Caribou object is created
243             using C<%options> as its arguments, and its inner template C<page> is then
244             rendered. In other words, the last line of the code above becomes
245             equivalent to
246              
247             return Dancer::View::MyView->new( %options )->render('page');
248              
249             =head2 '/views' template classes
250              
251             Template classes can be created straight from the C</views> directory.
252             Any directory containing a file named C<bou> will be turned into a
253             C<Template::Caribou> class. Additionally, any file with a C<.bou> extension
254             contained within that directory will be turned into a inner template for
255             that class.
256              
257             =head3 The 'bou' file
258              
259             The 'bou' file holds the custom bits of the Template::Caribou class.
260              
261             For example, a basic welcome template could be:
262              
263             # in /views/welcome/bou
264            
265             use Template::Caribou::Tags::HTML ':all';
266              
267             has name => ( is => 'ro' );
268              
269             template page => sub {
270             my $self = shift;
271              
272             html {
273             head { title { 'My App' } };
274             body {
275             h1 { 'hello ' . $self->name .'!' };
276             };
277             }
278             };
279              
280             which would be invoqued via
281              
282             get '/hi/:name' => sub {
283             template 'welcome' => { name => param('name') };
284             };
285              
286             =head3 The inner template files
287              
288             All files with a '.bou' extension found in the same directory as the 'bou'
289             file become inner templates for the class. So, to continue with the example
290             above, we could change it into
291              
292             # in /views/howdie/bou
293            
294             use Template::Caribou::Tags::HTML ':all';
295              
296             has name => ( is => 'ro' );
297              
298              
299             # in /views/howdie/page
300             sub {
301             my $self = shift;
302              
303             html {
304             head { title { 'My App' } };
305             body {
306             h1 { 'howdie ' . $self->name . '!' };
307             };
308             }
309             }
310              
311             =head3 Layouts as roles
312              
313             For the layout sub-directory, an additional piece of magic is performed.
314             The 'bou'-marked directories are turned into roles instead of classes, which will be applied to
315             the template class. Again, to take our example:
316              
317             # in /views/layouts/main/bou
318             # empty file
319              
320             # in /views/layouts/main/page
321            
322             # the import of tags really needs to be here
323             # instead than in the 'bou' file
324             use Template::Caribou::Tags::HTML ':all';
325              
326             sub {
327             my $self = shift;
328              
329             html {
330             head { title { 'My App' } };
331             body {
332             $self->inner_template;
333             };
334             }
335             }
336              
337             # in /views/hullo/bou
338            
339             use Template::Caribou::Tags::HTML ':all';
340              
341             has name => ( is => 'ro' );
342              
343             # in /views/howdie/inner
344             sub { my $self = shift; h1 { 'hullo ' . $self->name . '!' } }
345              
346             =head1 CONFIGURATION
347              
348             =over
349              
350             =item namespace
351              
352             The namespace under which the Caribou classes are created.
353             defaults to C<Dancer::View>.
354              
355             =back
356              
357             =head1 AUTHOR
358              
359             Yanick Champoux <yanick@babyl.dyndns.org>
360              
361             =head1 COPYRIGHT AND LICENSE
362              
363             This software is copyright (c) 2013 by Yanick Champoux.
364              
365             This is free software; you can redistribute it and/or modify it under
366             the same terms as the Perl 5 programming language system itself.
367              
368             =cut