File Coverage

blib/lib/Dancer2/Core/Role/Template.pm
Criterion Covered Total %
statement 75 79 94.9
branch 21 30 70.0
condition 7 9 77.7
subroutine 19 20 95.0
pod 7 9 77.7
total 129 147 87.7


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 = '2.1.0';
5 129     129   359220 use Dancer2::Core::Types;
  129         348  
  129         2241  
6 129     129   2022559 use Path::Tiny ();
  129         18013  
  129         5132  
7 129     129   940 use Carp 'croak';
  129         378  
  129         12233  
8 129     129   5016 use Ref::Util qw< is_ref >;
  129         5260  
  129         8724  
9 129     129   963 use Scalar::Util qw< blessed >;
  129         350  
  129         7594  
10              
11 129     129   1471 use Moo::Role;
  129         32112  
  129         1638  
12             with 'Dancer2::Core::Role::Engine';
13              
14             sub hook_aliases {
15             {
16 118     118 0 1183 before_template_render => 'engine.template.before_render',
17             after_template_render => 'engine.template.after_render',
18             before_layout_render => 'engine.template.before_layout_render',
19             after_layout_render => 'engine.template.after_layout_render',
20             }
21             }
22              
23 30     30 0 71 sub supported_hooks { values %{ shift->hook_aliases } }
  30         361  
24              
25 0     0   0 sub _build_type {'Template'}
26              
27             requires 'render';
28              
29             has log_cb => (
30             is => 'ro',
31             isa => CodeRef,
32             default => sub { sub {1} },
33             );
34              
35             has name => (
36             is => 'ro',
37             lazy => 1,
38             builder => 1,
39             );
40              
41             sub _build_name {
42 2     2   1969 ( my $name = ref shift ) =~ s/^Dancer2::Template:://;
43 2         16 $name;
44             }
45              
46             has charset => (
47             is => 'ro',
48             isa => Str,
49             default => sub {'UTF-8'},
50             );
51              
52             has default_tmpl_ext => (
53             is => 'ro',
54             isa => Str,
55             default => sub { shift->config->{extension} || 'tt' },
56             );
57              
58             has engine => (
59             is => 'ro',
60             isa => Object,
61             lazy => 1,
62             builder => 1,
63             );
64              
65             has settings => (
66             is => 'ro',
67             isa => HashRef,
68             lazy => 1,
69             default => sub { +{} },
70             writer => 'set_settings',
71             );
72              
73             # The attributes views, layout and layout_dir have triggers in
74             # Dancer2::Core::App that enable their values to be modified by
75             # the `set` keyword. As such, these are defined as read-write attrs.
76              
77             has views => (
78             is => 'rw',
79             isa => Maybe [Str],
80             trigger => sub { $_[0]->_clear_views_path },
81             );
82              
83             has layout => (
84             is => 'rw',
85             isa => Maybe [Str],
86             );
87              
88             has layout_dir => (
89             is => 'rw',
90             isa => Maybe [Str],
91             );
92              
93             has _views_path => (
94             is => 'rw',
95             lazy => 1,
96             builder => '_build_views_path',
97             clearer => '_clear_views_path',
98             init_arg => undef,
99             );
100              
101             sub _build_views_path {
102 46     46   493 my $self = shift;
103 46         1036 return Path::Tiny::path( $self->views );
104             }
105              
106             sub _template_name {
107 172     172   493 my ( $self, $view ) = @_;
108 172         754 my $def_tmpl_ext = $self->default_tmpl_ext();
109 172 100       2213 $view .= ".$def_tmpl_ext" if $view !~ /\.\Q$def_tmpl_ext\E$/;
110 172         650 return $view;
111             }
112              
113             sub view_pathname {
114 142     142 1 1397 my ( $self, $view ) = @_;
115              
116 142         507 $view = $self->_template_name($view);
117 142         3515 return $self->_views_path->child($view)->stringify;
118             }
119              
120             sub layout_pathname {
121 2     2 1 9 my ( $self, $layout ) = @_;
122              
123 2 50       52 my @parts = defined $self->layout_dir ? ( $self->layout_dir ) : ();
124 2         107 return $self->_views_path->child(
125             @parts,
126             $self->_template_name($layout),
127             )->stringify;
128             }
129              
130             sub pathname_exists {
131 120     120 1 11406 my ( $self, $pathname ) = @_;
132 120 50       457 return $pathname->is_file if blessed($pathname);
133 120         452 return Path::Tiny::path($pathname)->is_file;
134             }
135              
136             sub render_layout {
137 6     6 1 24 my ( $self, $layout, $tokens, $content ) = @_;
138              
139 6         30 $layout = $self->layout_pathname($layout);
140              
141             # FIXME: not sure if I can "just call render"
142 6         505 $self->render( $layout, { %$tokens, content => $content } );
143             }
144              
145             sub apply_renderer {
146 41     41 1 110 my ( $self, $view, $tokens ) = @_;
147 41 100       289 $view = $self->view_pathname($view) if !is_ref($view);
148 41         1579 $tokens = $self->_prepare_tokens_options( $tokens );
149              
150 41         232 $self->execute_hook( 'engine.template.before_render', $tokens );
151              
152 37         462 my $content = $self->render( $view, $tokens );
153 35         224 $self->execute_hook( 'engine.template.after_render', \$content );
154              
155             # make sure to avoid ( undef ) in list context return
156 35 50       440 defined $content and return $content;
157 0         0 return;
158             }
159              
160             sub apply_layout {
161 35     35 1 114 my ( $self, $content, $tokens, $options ) = @_;
162              
163 35         123 $tokens = $self->_prepare_tokens_options( $tokens );
164              
165             # If 'layout' was given in the options hashref, use it if it's a true value,
166             # or don't use a layout if it was false (0, or undef); if layout wasn't
167             # given in the options hashref, go with whatever the current layout setting
168             # is.
169             my $layout =
170             exists $options->{layout}
171             ? ( $options->{layout} ? $options->{layout} : undef )
172 35 50 66     792 : ( $self->layout || $self->config->{layout} );
    100          
173              
174             # that should only be $self->config, but the layout ain't there ???
175              
176 35 50       480 defined $content or return;
177 35 100       142 defined $layout or return $content;
178              
179 6         34 $self->execute_hook(
180             'engine.template.before_layout_render',
181             $tokens, \$content
182             );
183              
184 6         61 my $full_content = $self->render_layout( $layout, $tokens, $content );
185              
186 6         56 $self->execute_hook( 'engine.template.after_layout_render',
187             \$full_content );
188              
189             # make sure to avoid ( undef ) in list context return
190 6 50       74 defined $full_content and return $full_content;
191 0         0 return;
192             }
193              
194             sub _prepare_tokens_options {
195 76     76   192 my ( $self, $tokens ) = @_;
196              
197             # these are the default tokens provided for template processing
198 76   50     224 $tokens ||= {};
199 76         198 $tokens->{perl_version} = $^V;
200 76         615 $tokens->{dancer_version} = Dancer2->VERSION;
201 76         1990 $tokens->{settings} = $self->settings;
202              
203             # no request when template is called as a global keyword
204 76 100       951 if ( $self->has_request ) {
205 74         284 $tokens->{request} = $self->request;
206 74         410 $tokens->{params} = $self->request->params;
207 74         302 $tokens->{vars} = $self->request->vars;
208              
209             # a session can not exist if there is no request
210 74 100       454 $tokens->{session} = $self->session->data
211             if $self->has_session;
212             }
213              
214 76         211 return $tokens;
215             }
216              
217             sub process {
218 41     41 1 141 my ( $self, $view, $tokens, $options ) = @_;
219 41         85 my ( $content, $full_content );
220              
221             # it's important that $tokens is not undef, so that things added to it via
222             # a before_template in apply_renderer survive to the apply_layout. GH#354
223 41   100     256 $tokens ||= {};
224 41   100     242 $options ||= {};
225              
226             ## FIXME - Look into PR 654 so we fix the problem here as well!
227              
228             $content =
229             $view
230             ? $self->apply_renderer( $view, $tokens )
231 41 50       274 : delete $options->{content};
232              
233 35 50       216 defined $content
234             and $full_content = $self->apply_layout( $content, $tokens, $options );
235              
236 35 50       259 defined $full_content
237             and return $full_content;
238              
239 0           croak "Template did not produce any content";
240             }
241              
242             1;
243              
244             __END__