File Coverage

blib/lib/Dancer2/Core/Role/Template.pm
Criterion Covered Total %
statement 68 72 94.4
branch 19 26 73.0
condition 7 9 77.7
subroutine 17 18 94.4
pod 7 9 77.7
total 118 134 88.0


line stmt bran cond sub pod time code
1             # ABSTRACT: Role for template engines
2              
3             package Dancer2::Core::Role::Template;
4             $Dancer2::Core::Role::Template::VERSION = '1.0.0';
5 117     117   83937 use Dancer2::Core::Types;
  117         343  
  117         915  
6 117     117   1517745 use Dancer2::FileUtils 'path';
  117         377  
  117         6600  
7 117     117   1926 use Carp 'croak';
  117         424  
  117         6055  
8 117     117   1395 use Ref::Util qw< is_ref >;
  117         976  
  117         6378  
9              
10 117     117   898 use Moo::Role;
  117         385  
  117         1164  
11             with 'Dancer2::Core::Role::Engine';
12              
13             sub hook_aliases {
14             {
15 109     109 0 964 before_template_render => 'engine.template.before_render',
16             after_template_render => 'engine.template.after_render',
17             before_layout_render => 'engine.template.before_layout_render',
18             after_layout_render => 'engine.template.after_layout_render',
19             }
20             }
21              
22 28     28 0 72 sub supported_hooks { values %{ shift->hook_aliases } }
  28         117  
23              
24 0     0   0 sub _build_type {'Template'}
25              
26             requires 'render';
27              
28             has log_cb => (
29             is => 'ro',
30             isa => CodeRef,
31             default => sub { sub {1} },
32             );
33              
34             has name => (
35             is => 'ro',
36             lazy => 1,
37             builder => 1,
38             );
39              
40             sub _build_name {
41 2     2   1978 ( my $name = ref shift ) =~ s/^Dancer2::Template:://;
42 2         12 $name;
43             }
44              
45             has charset => (
46             is => 'ro',
47             isa => Str,
48             default => sub {'UTF-8'},
49             );
50              
51             has default_tmpl_ext => (
52             is => 'ro',
53             isa => Str,
54             default => sub { shift->config->{extension} || 'tt' },
55             );
56              
57             has engine => (
58             is => 'ro',
59             isa => Object,
60             lazy => 1,
61             builder => 1,
62             );
63              
64             has settings => (
65             is => 'ro',
66             isa => HashRef,
67             lazy => 1,
68             default => sub { +{} },
69             writer => 'set_settings',
70             );
71              
72             # The attributes views, layout and layout_dir have triggers in
73             # Dancer2::Core::App that enable their values to be modified by
74             # the `set` keyword. As such, these are defined as read-write attrs.
75              
76             has views => (
77             is => 'rw',
78             isa => Maybe [Str],
79             );
80              
81             has layout => (
82             is => 'rw',
83             isa => Maybe [Str],
84             );
85              
86             has layout_dir => (
87             is => 'rw',
88             isa => Maybe [Str],
89             );
90              
91             sub _template_name {
92 167     167   460 my ( $self, $view ) = @_;
93 167         521 my $def_tmpl_ext = $self->default_tmpl_ext();
94 167 100       1510 $view .= ".$def_tmpl_ext" if $view !~ /\.\Q$def_tmpl_ext\E$/;
95 167         541 return $view;
96             }
97              
98             sub view_pathname {
99 141     141 1 1166 my ( $self, $view ) = @_;
100              
101 141         408 $view = $self->_template_name($view);
102 141         2629 return path( $self->views, $view );
103             }
104              
105             sub layout_pathname {
106 2     2 1 6 my ( $self, $layout ) = @_;
107              
108 2         33 return path(
109             $self->views,
110             $self->layout_dir,
111             $self->_template_name($layout),
112             );
113             }
114              
115             sub pathname_exists {
116 119     119 1 2006 my ( $self, $pathname ) = @_;
117 119         2984 return -f $pathname;
118             }
119              
120             sub render_layout {
121 6     6 1 25 my ( $self, $layout, $tokens, $content ) = @_;
122              
123 6         28 $layout = $self->layout_pathname($layout);
124              
125             # FIXME: not sure if I can "just call render"
126 6         71 $self->render( $layout, { %$tokens, content => $content } );
127             }
128              
129             sub apply_renderer {
130 37     37 1 110 my ( $self, $view, $tokens ) = @_;
131 37 100       211 $view = $self->view_pathname($view) if !is_ref($view);
132 37         195 $tokens = $self->_prepare_tokens_options( $tokens );
133              
134 37         200 $self->execute_hook( 'engine.template.before_render', $tokens );
135              
136 33         431 my $content = $self->render( $view, $tokens );
137 32         177 $self->execute_hook( 'engine.template.after_render', \$content );
138              
139             # make sure to avoid ( undef ) in list context return
140 32 50       417 defined $content and return $content;
141 0         0 return;
142             }
143              
144             sub apply_layout {
145 32     32 1 111 my ( $self, $content, $tokens, $options ) = @_;
146              
147 32         103 $tokens = $self->_prepare_tokens_options( $tokens );
148              
149             # If 'layout' was given in the options hashref, use it if it's a true value,
150             # or don't use a layout if it was false (0, or undef); if layout wasn't
151             # given in the options hashref, go with whatever the current layout setting
152             # is.
153             my $layout =
154             exists $options->{layout}
155             ? ( $options->{layout} ? $options->{layout} : undef )
156 32 50 66     664 : ( $self->layout || $self->config->{layout} );
    100          
157              
158             # that should only be $self->config, but the layout ain't there ???
159              
160 32 50       462 defined $content or return;
161 32 100       142 defined $layout or return $content;
162              
163 6         25 $self->execute_hook(
164             'engine.template.before_layout_render',
165             $tokens, \$content
166             );
167              
168 6         53 my $full_content = $self->render_layout( $layout, $tokens, $content );
169              
170 6         43 $self->execute_hook( 'engine.template.after_layout_render',
171             \$full_content );
172              
173             # make sure to avoid ( undef ) in list context return
174 6 50       64 defined $full_content and return $full_content;
175 0         0 return;
176             }
177              
178             sub _prepare_tokens_options {
179 69     69   187 my ( $self, $tokens ) = @_;
180              
181             # these are the default tokens provided for template processing
182 69   50     191 $tokens ||= {};
183 69         190 $tokens->{perl_version} = $^V;
184 69         389 $tokens->{dancer_version} = Dancer2->VERSION;
185 69         1494 $tokens->{settings} = $self->settings;
186              
187             # no request when template is called as a global keyword
188 69 100       1018 if ( $self->has_request ) {
189 67         213 $tokens->{request} = $self->request;
190 67         315 $tokens->{params} = $self->request->params;
191 67         292 $tokens->{vars} = $self->request->vars;
192              
193             # a session can not exist if there is no request
194 67 100       402 $tokens->{session} = $self->session->data
195             if $self->has_session;
196             }
197              
198 69         229 return $tokens;
199             }
200              
201             sub process {
202 37     37 1 140 my ( $self, $view, $tokens, $options ) = @_;
203 37         102 my ( $content, $full_content );
204              
205             # it's important that $tokens is not undef, so that things added to it via
206             # a before_template in apply_renderer survive to the apply_layout. GH#354
207 37   100     181 $tokens ||= {};
208 37   100     219 $options ||= {};
209              
210             ## FIXME - Look into PR 654 so we fix the problem here as well!
211              
212             $content =
213             $view
214             ? $self->apply_renderer( $view, $tokens )
215 37 50       228 : delete $options->{content};
216              
217 32 50       300 defined $content
218             and $full_content = $self->apply_layout( $content, $tokens, $options );
219              
220 32 50       201 defined $full_content
221             and return $full_content;
222              
223 0           croak "Template did not produce any content";
224             }
225              
226             1;
227              
228             __END__
229              
230             =pod
231              
232             =encoding UTF-8
233              
234             =head1 NAME
235              
236             Dancer2::Core::Role::Template - Role for template engines
237              
238             =head1 VERSION
239              
240             version 1.0.0
241              
242             =head1 DESCRIPTION
243              
244             Any class that consumes this role will be able to be used as a template engine
245             under Dancer2.
246              
247             In order to implement this role, the consumer B<must> implement the method C<render>. This method will receive three arguments:
248              
249             =over 4
250              
251             =item $self
252              
253             =item $template
254              
255             =item $tokens
256              
257             =back
258              
259             Any template receives the following tokens, by default:
260              
261             =over 4
262              
263             =item * C<perl_version>
264              
265             Current version of perl, effectively C<$^V>.
266              
267             =item * C<dancer_version>
268              
269             Current version of Dancer2, effectively C<< Dancer2->VERSION >>.
270              
271             =item * C<settings>
272              
273             A hash of the application configuration.
274              
275             =item * C<request>
276              
277             The current request object.
278              
279             =item * C<params>
280              
281             A hash reference of all the parameters.
282              
283             Currently the equivalent of C<< $request->params >>.
284              
285             =item * C<vars>
286              
287             The list of request variables, which is what you would get if you
288             called the C<vars> keyword.
289              
290             =item * C<session>
291              
292             The current session data, if a session exists.
293              
294             =back
295              
296             =head1 ATTRIBUTES
297              
298             =head2 name
299              
300             The name of the template engine (e.g.: Simple).
301              
302             =head2 charset
303              
304             The charset. The default value is B<UTF-8>.
305              
306             =head2 default_tmpl_ext
307              
308             The default file extension. If not provided, B<tt> is used.
309              
310             =head2 views
311              
312             Path to the directory containing the views.
313              
314             =head2 layout
315              
316             Path to the directory containing the layouts.
317              
318             =head2 layout_dir
319              
320             Relative path to the layout directory.
321              
322             Default: B<layouts>.
323              
324             =head2 engine
325              
326             Contains the engine.
327              
328             =head1 METHODS
329              
330             =head2 view_pathname($view)
331              
332             Returns the full path to the requested view.
333              
334             =head2 layout_pathname($layout)
335              
336             Returns the full path to the requested layout.
337              
338             =head2 pathname_exists($pathname)
339              
340             Returns true if the requested pathname exists. Can be used for either views
341             or layouts:
342              
343             $self->pathname_exists( $self->view_pathname( 'some_view' ) );
344             $self->pathname_exists( $self->layout_pathname( 'some_layout' ) );
345              
346             =head2 render_layout($layout, \%tokens, \$content)
347              
348             Render the layout with the applied tokens
349              
350             =head2 apply_renderer($view, \%tokens)
351              
352             =head2 apply_layout($content, \%tokens, \%options)
353              
354             =head2 process($view, \%tokens, \%options)
355              
356             =head2 template($view, \%tokens, \%options)
357              
358             =head1 AUTHOR
359              
360             Dancer Core Developers
361              
362             =head1 COPYRIGHT AND LICENSE
363              
364             This software is copyright (c) 2023 by Alexis Sukrieh.
365              
366             This is free software; you can redistribute it and/or modify it under
367             the same terms as the Perl 5 programming language system itself.
368              
369             =cut