File Coverage

blib/lib/Dancer2/Template/Caribou.pm
Criterion Covered Total %
statement 71 72 98.6
branch 11 18 61.1
condition 2 3 66.6
subroutine 16 17 94.1
pod 0 4 0.0
total 100 114 87.7


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