File Coverage

blib/lib/Plack/Middleware/TemplateToolkit.pm
Criterion Covered Total %
statement 194 199 97.4
branch 83 94 88.3
condition 31 43 72.0
subroutine 27 27 100.0
pod 5 5 100.0
total 340 368 92.3


line stmt bran cond sub pod time code
1             package Plack::Middleware::TemplateToolkit;
2             # ABSTRACT: Serve files with Template Toolkit and Plack
3             $Plack::Middleware::TemplateToolkit::VERSION = '0.27';
4 7     7   319968 use strict;
  7         19  
  7         236  
5 7     7   35 use warnings;
  7         13  
  7         181  
6 7     7   172 use 5.008_001;
  7         27  
  7         262  
7              
8 7     7   2500 use parent 'Exporter'; # for 'use Plack::Middleware::TemplateToolkit $version;'
  7         1020  
  7         38  
9 7     7   406 use parent 'Plack::Middleware';
  7         14  
  7         33  
10 7     7   67065 use Plack::Request 0.994;
  7         547076  
  7         215  
11 7     7   15667 use Plack::MIME;
  7         5723  
  7         270  
12 7     7   6090 use Template 2;
  7         176529  
  7         222  
13 7     7   73 use Scalar::Util qw(blessed);
  7         13  
  7         416  
14 7     7   9768 use HTTP::Status qw(status_message);
  7         26183  
  7         922  
15 7     7   6814 use Time::HiRes;
  7         12997  
  7         28  
16 7     7   6780 use Plack::Middleware::Debug::Timer;
  7         137656  
  7         240  
17 7     7   7349 use Encode;
  7         76880  
  7         726  
18 7     7   75 use Carp;
  7         13  
  7         907  
19              
20             # Configuration options as described in Template::Manual::Config
21             our @TT_CONFIG;
22             our @DEPRECATED;
23              
24             BEGIN {
25 7     7   65 @TT_CONFIG = qw(START_TAG END_TAG TAG_STYLE PRE_CHOMP POST_CHOMP TRIM
26             INTERPOLATE ANYCASE INCLUDE_PATH DELIMITER ABSOLUTE RELATIVE DEFAULT
27             BLOCKS VIEWS AUTO_RESET RECURSION VARIABLES CONSTANTS
28             CONSTANT_NAMESPACE NAMESPACE PRE_PROCESS POST_PROCESS PROCESS WRAPPER
29             ERROR EVAL_PERL OUTPUT OUTPUT_PATH STRICT DEBUG DEBUG_FORMAT
30             CACHE_SIZE STAT_TTL COMPILE_EXT COMPILE_DIR PLUGINS PLUGIN_BASE
31             LOAD_PERL FILTERS LOAD_TEMPLATES LOAD_PLUGINS LOAD_FILTERS
32             TOLERANT SERVICE CONTEXT STASH PARSER GRAMMAR
33             );
34              
35             # the following ugly code is only needed to catch deprecated accessors
36 7         18 @DEPRECATED = qw(pre_process process eval_perl interpolate post_chomp);
37 7     7   34 no strict 'refs';
  7         15  
  7         1545  
38 7         14 my $module = "Plack::Middleware::TemplateToolkit";
39 7         18 foreach my $name (@DEPRECATED) {
40 35         509 *{ $module . "::$name" } = sub {
41 2     2   52 my $correct = uc($name);
42 2         55 carp $module. "$name is deprecated, use ::$correct";
43 2         1556 my $method = $module . "::$correct";
44 2         13 &$method(@_);
45             }
46 35         117 }
47              
48             sub new {
49 21     21 1 16165 my $self = Plack::Component::new(@_);
50              
51             # Support 'root' config (matches MW::Static etc)
52             # if INCLUDE_PATH hasn't been defined
53 21 50 66     366 $self->INCLUDE_PATH( $self->root )
54             if !$self->INCLUDE_PATH() && $self->root;
55              
56 21         1022 foreach ( grep { defined $self->{$_} } @DEPRECATED ) {
  105         207  
57 1         4 $self->$_;
58             }
59 21         143 $self;
60             }
61             }
62              
63             use Plack::Util::Accessor (
64 7         94 qw(dir_index path extension content_type default_type tt root timer
65             pass_through decode_request encode_response vars request_vars),
66             @TT_CONFIG
67 7     7   47 );
  7         15  
68              
69             sub prepare_app {
70 35     35 1 56405 my ($self) = @_;
71              
72 35 100       148 $self->dir_index('index.html') unless $self->dir_index;
73 35 100       422 $self->pass_through(0) unless defined $self->pass_through;
74 35 100       384 $self->default_type('text/html') unless $self->default_type;
75 35 100       334 $self->decode_request('utf8') unless defined $self->decode_request;
76 35 100       337 $self->encode_response('utf8') unless defined $self->encode_response;
77 35 100       339 $self->request_vars( [] ) unless defined $self->request_vars;
78              
79 35 100       361 if ( not $self->vars ) {
    100          
    50          
80 17     46   217 $self->vars( sub { return { params => shift->query_parameters } } );
  46         313  
81             } elsif ( ref $self->vars eq 'HASH' ) {
82 1         12 my $vars = $self->vars;
83 1     2   10 $self->vars( sub { return $vars; } );
  2         15  
84             } elsif ( ref $self->vars ne 'CODE' ) {
85 0         0 die 'vars must be a code or hash reference, if defined';
86             }
87              
88 35         429 my $config = {};
89 35         95 foreach (@TT_CONFIG) {
90 1715 100       10148 next unless $self->$_;
91 30         175 $config->{$_} = $self->$_;
92 30         175 $self->$_(undef); # don't initialize twice
93             }
94              
95 35 100       283 if ( $self->tt ) {
96 17 50       123 die 'tt must be a Template instance'
97             unless UNIVERSAL::isa( $self->tt, 'Template' );
98 17 50       196 die 'Either specify a template with tt or Template options, not both'
99             if %$config;
100             } else {
101 18 50       126 die 'No INCLUDE_PATH supplied' unless $config->{INCLUDE_PATH};
102 18         183 $self->tt( Template->new($config) );
103             }
104             }
105              
106             sub call { # adapted from Plack::Middleware::Static
107 45     45 1 239659 my ( $self, $env ) = @_;
108              
109 45 50       165 my $start = [ Time::HiRes::gettimeofday ] if $self->timer;
110 45         385 my $res = $self->_handle_template($env);
111              
112 45 100 100     252 if ( !$res or ( $self->pass_through and $res->[0] == 404 ) ) {
      66        
113 4 100       55 if ( $self->app ) {
114             # pass to the next middleware/app
115 1         8 $res = $self->app->($env);
116             # if ( $self->catch_errors and $res->[0] =~ /^[45]/ ) {
117             # TODO: process error message (but better use callback)
118             # }
119             } else {
120 3         40 my $req = Plack::Request->new($env);
121 3         36 $res = $self->process_error( 404, 'Not found', 'text/plain', $req );
122             }
123             }
124              
125 45 50       355 if ($self->timer) {
126 0         0 my $end = [ Time::HiRes::gettimeofday ];
127 0         0 $env->{'tt.start'} = Plack::Middleware::Debug::Timer->format_time($start);
128 0         0 $env->{'tt.end'} = Plack::Middleware::Debug::Timer->format_time($end);
129 0         0 $env->{'tt.elapsed'}
130             = sprintf '%.6f s', Time::HiRes::tv_interval $start, $end;
131             }
132              
133 45         541 $res;
134             }
135              
136             sub process_template {
137 51     51 1 142 my ( $self, $template, $code, $vars ) = @_;
138              
139 51         62 my ( $content, $res );
140 51 100       151 if ( $self->tt->process( $template, $vars, \$content ) ) {
141 38   66     247447 my $type = $self->content_type || do {
142             Plack::MIME->mime_type($1) if $template =~ /(\.\w{1,6})$/;
143             }
144             || $self->default_type;
145 38 100       619 if ( $self->encode_response ) {
146 37         274 $content = encode( $self->encode_response, $content );
147             }
148 38         1409 $res = [ $code, [ 'Content-Type' => $type ], [$content] ];
149             } else {
150 13         5587 $res = $self->tt->error->as_string;
151             }
152              
153 51         347 return $res;
154             }
155              
156             sub process_error {
157 24     24 1 34624 my ( $self, $code, $error, $type, $req ) = @_;
158              
159 24 100 66     177 $code = 500 unless $code && $code =~ /^\d\d\d$/;
160 24 100       81 $error = status_message($code) unless $error;
161 24 100 100     85 $type = ( $self->content_type || $self->default_type || 'text/plain' )
162             unless $type;
163              
164             # plain error without template
165 24 100 100     219 return [ $code, [ 'Content-Type' => $type ], [$error] ]
166             unless $self->{$code} and $self->tt;
167              
168 12 100 66     195 $req = Plack::Request->new( { 'tt.vars' => {} } )
169             unless blessed $req && $req->isa('Plack::Request');
170 12         57 eval { $self->_set_vars($req); };
  12         39  
171              
172 12         44 $req->env->{'tt.vars'}->{'error'} = $error;
173 12         78 $req->env->{'tt.vars'}->{'path'} = $req->path_info;
174 12         100 $req->env->{'tt.vars'}->{'request'} = $req;
175              
176 12         51 my $tpl = $self->{$code};
177 12         31 my $res = $self->process_template( $tpl, $code, $req->env->{'tt.vars'} );
178              
179 12 100       36 unless ( ref $res ) {
180              
181             # processing error document failed: result in a 500 error
182 4 100       11 if ( $code eq 500 ) {
183 2         9 $res = [ 500, [ 'Content-Type' => $type ], [$res] ];
184 2         4 $tpl = undef;
185             } else {
186 2 50       10 if ( ref $req->logger ) {
187 2         24 $req->logger->( { level => 'warn', message => $res } );
188             }
189 2         21 ( $res, $tpl ) = $self->process_error( 500, $res, $type, $req );
190             }
191             }
192              
193 12 100       61 return wantarray ? ( $res, $tpl ) : $res;
194             }
195              
196             sub _set_vars {
197 53     53   89 my ( $self, $req ) = @_;
198 53         171 my $env = $req->env;
199              
200             # we must not copy the vars by reference because
201             # otherwise we might modify the same object
202 53 50       305 my (%vars) = %{ $self->vars->($req) } if defined $self->vars;
  53         371  
203              
204 51         2914 my $rv = $self->request_vars;
205 51 100       332 unless ( exists $vars{request} ) {
206 50 100 66     363 if ( $rv eq 'all' ) {
    100          
207 1         3 $vars{request} = $req;
208             } elsif ( ref $rv and @$rv ) {
209 4         12 $vars{request} = {};
210 4         7 foreach ( @{ $self->request_vars } ) {
  4         15  
211 8 100       73 next unless $req->can($_);
212 6         24 my $value = $req->$_;
213              
214             # request vars should also be byte strings, so we must decode it
215 6 50       298 if ( $self->decode_request ) {
216 6         46 my $encoding = $self->decode_request;
217              
218 6 100 66     79 if ( blessed($value) and $value->isa('Hash::MultiValue') )
219             {
220 4         19 my @values = $value->values;
221 4         35 @values = map { decode( $encoding, $_ ) } @values;
  4         20  
222 4         148 my $hash = Hash::MultiValue->new;
223 4         106 foreach my $key ( $value->keys ) {
224 4         37 $key = decode( $encoding, $key );
225 4         104 $hash->add( $key, shift @values );
226             }
227 4         155 $value = $hash;
228             } else {
229 2         9 $value = decode( $encoding, $value );
230             }
231             }
232              
233 6         102 $vars{request}->{$_} = $value;
234             }
235             }
236             }
237              
238 51 100       140 if ( $env->{'tt.vars'} ) {
239             # add to existing vars
240 14         41 foreach ( keys %vars ) {
241 14         68 $env->{'tt.vars'}->{$_} = $vars{$_};
242             }
243             } else {
244 37         129 $env->{'tt.vars'} = \%vars;
245             }
246             }
247              
248             # core function called once in 'call'
249             sub _handle_template {
250 45     45   68 my ( $self, $env ) = @_;
251              
252 45 100       134 if ( not $env->{'tt.template'} ) {
253 43   66     172 my $path = $env->{'tt.path'} || do {
254             $env->{'tt.path'} = $env->{PATH_INFO} || '/';
255             };
256              
257 43   100     151 my $path_match = $self->path || '/';
258 43         353 for ($path) {
259 43 100       259 my $matched
260             = 'CODE' eq ref $path_match
261             ? $path_match->($_)
262             : $_ =~ $path_match;
263 43 100       189 if (not $matched) {
264 3         9 delete $env->{'tt.path'};
265 3         10 return;
266             }
267             }
268              
269 40 100       162 $path .= $self->dir_index if $path =~ /\/$/;
270 40         160 $path =~ s{^/}{}; # Do not want to enable absolute paths
271              
272 40         134 my $extension = $self->extension;
273 40 100 100     306 if ( $extension and $path !~ /${extension}$/ ) {
274             # This 404 will be catched in method call if pass_through is set
275 1         6 my ($res, $tpl) = $self->process_error(
276             404, 'Not found', 'text/plain', Plack::Request->new($env) );
277 1         3 $env->{'tt.template'} = $tpl;
278 1         2 return $res;
279             }
280              
281 39         116 $env->{'tt.template'} = $path;
282             } else {
283 2         7 delete $env->{'tt.path'};
284             }
285              
286 41         47 my ($res, $tpl);
287 41         287 my $req = Plack::Request->new($env);
288 41         368 eval { $self->_set_vars($req); };
  41         152  
289 41 100       109 if ( $@ ) {
290 2         5 my $error = "error setting template variables: $@";
291 2   33     7 my $type = $self->content_type || $self->default_type;
292 2         27 ( $res, $tpl ) = $self->process_error( 500, $error, $type, $req );
293 2         6 $env->{'tt.template'} = $tpl;
294             } else {
295 39         165 $res = $self->process_template(
296             $env->{'tt.template'}, 200, $env->{'tt.vars'} );
297             }
298              
299 41 100       136 unless ( ref $res ) {
300 9   33     60 my $type = $self->content_type || $self->default_type;
301 9 100       154 if ( $res =~ /file error .+ not found/ ) {
302 6         25 ( $res, $tpl ) = $self->process_error( 404, $res, $type, $req );
303             } else {
304 3 50       16 if ( ref $req->logger ) {
305 3         40 $req->logger->( { level => 'warn', message => $res } );
306             }
307 3         32 ( $res, $tpl ) = $self->process_error( 500, $res, $type, $req );
308             }
309 9         25 $env->{'tt.template'} = $tpl;
310             }
311              
312 41         181 return $res;
313             }
314              
315             1;
316              
317             __END__