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.28';
4 7     7   179448 use strict;
  7         16  
  7         223  
5 7     7   26 use warnings;
  7         10  
  7         153  
6 7     7   134 use 5.008_001;
  7         19  
  7         212  
7              
8 7     7   1598 use parent 'Exporter'; # for 'use Plack::Middleware::TemplateToolkit $version;'
  7         879  
  7         34  
9 7     7   324 use parent 'Plack::Middleware';
  7         11  
  7         20  
10 7     7   39693 use Plack::Request 0.994;
  7         312787  
  7         181  
11 7     7   2976 use Plack::MIME;
  7         4308  
  7         270  
12 7     7   16048 use Template 2;
  7         132740  
  7         198  
13 7     7   50 use Scalar::Util qw(blessed);
  7         9  
  7         291  
14 7     7   3309 use HTTP::Status qw(status_message);
  7         18180  
  7         815  
15 7     7   3559 use Time::HiRes;
  7         8409  
  7         25  
16 7     7   3611 use Plack::Middleware::Debug::Timer;
  7         87177  
  7         193  
17 7     7   3866 use Encode;
  7         50490  
  7         596  
18 7     7   63 use Carp;
  7         8  
  7         754  
19              
20             # Configuration options as described in Template::Manual::Config
21             our @TT_CONFIG;
22             our @DEPRECATED;
23              
24             BEGIN {
25 7     7   40 @TT_CONFIG = qw(ENCODING START_TAG END_TAG OUTLINE_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         13 @DEPRECATED = qw(pre_process process eval_perl interpolate post_chomp);
37 7     7   30 no strict 'refs';
  7         10  
  7         1113  
38 7         9 my $module = "Plack::Middleware::TemplateToolkit";
39 7         10 foreach my $name (@DEPRECATED) {
40 35         384 *{ $module . "::$name" } = sub {
41 2     2   40 my $correct = uc($name);
42 2         33 carp $module. "$name is deprecated, use ::$correct";
43 2         1140 my $method = $module . "::$correct";
44 2         14 &$method(@_);
45             }
46 35         68 }
47              
48             sub new {
49 21     21 1 15125 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     281 $self->INCLUDE_PATH( $self->root )
54             if !$self->INCLUDE_PATH() && $self->root;
55              
56 21         820 foreach ( grep { defined $self->{$_} } @DEPRECATED ) {
  105         157  
57 1         4 $self->$_;
58             }
59 21         105 $self;
60             }
61             }
62              
63             use Plack::Util::Accessor (
64 7         59 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   35 );
  7         9  
68              
69             sub prepare_app {
70 35     35 1 40807 my ($self) = @_;
71              
72 35 100       116 $self->dir_index('index.html') unless $self->dir_index;
73 35 100       364 $self->pass_through(0) unless defined $self->pass_through;
74 35 100       264 $self->default_type('text/html') unless $self->default_type;
75 35 100       260 $self->decode_request('utf8') unless defined $self->decode_request;
76 35 100       241 $self->encode_response('utf8') unless defined $self->encode_response;
77 35 100       239 $self->request_vars( [] ) unless defined $self->request_vars;
78              
79 35 100       237 if ( not $self->vars ) {
    100          
    50          
80 17     46   128 $self->vars( sub { return { params => shift->query_parameters } } );
  46         217  
81             } elsif ( ref $self->vars eq 'HASH' ) {
82 1         11 my $vars = $self->vars;
83 1     2   10 $self->vars( sub { return $vars; } );
  2         12  
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         320 my $config = {};
89 35         78 foreach (@TT_CONFIG) {
90 1785 100       7265 next unless $self->$_;
91 30         131 $config->{$_} = $self->$_;
92 30         123 $self->$_(undef); # don't initialize twice
93             }
94              
95 35 100       193 if ( $self->tt ) {
96 17 50       86 die 'tt must be a Template instance'
97             unless UNIVERSAL::isa( $self->tt, 'Template' );
98 17 50       143 die 'Either specify a template with tt or Template options, not both'
99             if %$config;
100             } else {
101 18 50       101 die 'No INCLUDE_PATH supplied' unless $config->{INCLUDE_PATH};
102 18         165 $self->tt( Template->new($config) );
103             }
104             }
105              
106             sub call { # adapted from Plack::Middleware::Static
107 45     45 1 221642 my ( $self, $env ) = @_;
108              
109 45 50       135 my $start = [ Time::HiRes::gettimeofday ] if $self->timer;
110 45         291 my $res = $self->_handle_template($env);
111              
112 45 100 100     201 if ( !$res or ( $self->pass_through and $res->[0] == 404 ) ) {
      66        
113 4 100       44 if ( $self->app ) {
114             # pass to the next middleware/app
115 1         7 $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         28 my $req = Plack::Request->new($env);
121 3         33 $res = $self->process_error( 404, 'Not found', 'text/plain', $req );
122             }
123             }
124              
125 45 50       271 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         342 $res;
134             }
135              
136             sub process_template {
137 51     51 1 94 my ( $self, $template, $code, $vars ) = @_;
138              
139 51         46 my ( $content, $res );
140 51 100       142 if ( $self->tt->process( $template, $vars, \$content ) ) {
141 38   66     165506 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       500 if ( $self->encode_response ) {
146 37         198 $content = encode( $self->encode_response, $content );
147             }
148 38         1065 $res = [ $code, [ 'Content-Type' => $type ], [$content] ];
149             } else {
150 13         3410 $res = $self->tt->error->as_string;
151             }
152              
153 51         230 return $res;
154             }
155              
156             sub process_error {
157 24     24 1 23162 my ( $self, $code, $error, $type, $req ) = @_;
158              
159 24 100 66     148 $code = 500 unless $code && $code =~ /^\d\d\d$/;
160 24 100       63 $error = status_message($code) unless $error;
161 24 100 100     81 $type = ( $self->content_type || $self->default_type || 'text/plain' )
162             unless $type;
163              
164             # plain error without template
165 24 100 100     213 return [ $code, [ 'Content-Type' => $type ], [$error] ]
166             unless $self->{$code} and $self->tt;
167              
168 12 100 66     161 $req = Plack::Request->new( { 'tt.vars' => {} } )
169             unless blessed $req && $req->isa('Plack::Request');
170 12         40 eval { $self->_set_vars($req); };
  12         24  
171              
172 12         29 $req->env->{'tt.vars'}->{'error'} = $error;
173 12         58 $req->env->{'tt.vars'}->{'path'} = $req->path_info;
174 12         69 $req->env->{'tt.vars'}->{'request'} = $req;
175              
176 12         33 my $tpl = $self->{$code};
177 12         23 my $res = $self->process_template( $tpl, $code, $req->env->{'tt.vars'} );
178              
179 12 100       27 unless ( ref $res ) {
180              
181             # processing error document failed: result in a 500 error
182 4 100       6 if ( $code eq 500 ) {
183 2         5 $res = [ 500, [ 'Content-Type' => $type ], [$res] ];
184 2         2 $tpl = undef;
185             } else {
186 2 50       5 if ( ref $req->logger ) {
187 2         15 $req->logger->( { level => 'warn', message => $res } );
188             }
189 2         18 ( $res, $tpl ) = $self->process_error( 500, $res, $type, $req );
190             }
191             }
192              
193 12 100       40 return wantarray ? ( $res, $tpl ) : $res;
194             }
195              
196             sub _set_vars {
197 53     53   64 my ( $self, $req ) = @_;
198 53         116 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       214 my (%vars) = %{ $self->vars->($req) } if defined $self->vars;
  53         279  
203              
204 51         2329 my $rv = $self->request_vars;
205 51 100       304 unless ( exists $vars{request} ) {
206 50 100 66     300 if ( $rv eq 'all' ) {
    100          
207 1         3 $vars{request} = $req;
208             } elsif ( ref $rv and @$rv ) {
209 4         8 $vars{request} = {};
210 4         6 foreach ( @{ $self->request_vars } ) {
  4         8  
211 8 100       55 next unless $req->can($_);
212 6         18 my $value = $req->$_;
213              
214             # request vars should also be byte strings, so we must decode it
215 6 50       201 if ( $self->decode_request ) {
216 6         31 my $encoding = $self->decode_request;
217              
218 6 100 66     55 if ( blessed($value) and $value->isa('Hash::MultiValue') )
219             {
220 4         15 my @values = $value->values;
221 4         30 @values = map { decode( $encoding, $_ ) } @values;
  4         11  
222 4         95 my $hash = Hash::MultiValue->new;
223 4         75 foreach my $key ( $value->keys ) {
224 4         25 $key = decode( $encoding, $key );
225 4         71 $hash->add( $key, shift @values );
226             }
227 4         144 $value = $hash;
228             } else {
229 2         9 $value = decode( $encoding, $value );
230             }
231             }
232              
233 6         86 $vars{request}->{$_} = $value;
234             }
235             }
236             }
237              
238 51 100       112 if ( $env->{'tt.vars'} ) {
239             # add to existing vars
240 14         32 foreach ( keys %vars ) {
241 14         45 $env->{'tt.vars'}->{$_} = $vars{$_};
242             }
243             } else {
244 37         111 $env->{'tt.vars'} = \%vars;
245             }
246             }
247              
248             # core function called once in 'call'
249             sub _handle_template {
250 45     45   57 my ( $self, $env ) = @_;
251              
252 45 100       99 if ( not $env->{'tt.template'} ) {
253 43   66     117 my $path = $env->{'tt.path'} || do {
254             $env->{'tt.path'} = $env->{PATH_INFO} || '/';
255             };
256              
257 43   100     103 my $path_match = $self->path || '/';
258 43         245 for ($path) {
259 43 100       238 my $matched
260             = 'CODE' eq ref $path_match
261             ? $path_match->($_)
262             : $_ =~ $path_match;
263 43 100       146 if (not $matched) {
264 3         9 delete $env->{'tt.path'};
265 3         7 return;
266             }
267             }
268              
269 40 100       126 $path .= $self->dir_index if $path =~ /\/$/;
270 40         147 $path =~ s{^/}{}; # Do not want to enable absolute paths
271              
272 40         108 my $extension = $self->extension;
273 40 100 100     218 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         4 $env->{'tt.template'} = $tpl;
278 1         2 return $res;
279             }
280              
281 39         82 $env->{'tt.template'} = $path;
282             } else {
283 2         4 delete $env->{'tt.path'};
284             }
285              
286 41         43 my ($res, $tpl);
287 41         226 my $req = Plack::Request->new($env);
288 41         281 eval { $self->_set_vars($req); };
  41         96  
289 41 100       91 if ( $@ ) {
290 2         5 my $error = "error setting template variables: $@";
291 2   33     4 my $type = $self->content_type || $self->default_type;
292 2         22 ( $res, $tpl ) = $self->process_error( 500, $error, $type, $req );
293 2         3 $env->{'tt.template'} = $tpl;
294             } else {
295 39         108 $res = $self->process_template(
296             $env->{'tt.template'}, 200, $env->{'tt.vars'} );
297             }
298              
299 41 100       103 unless ( ref $res ) {
300 9   33     25 my $type = $self->content_type || $self->default_type;
301 9 100       111 if ( $res =~ /file error .+ not found/ ) {
302 6         16 ( $res, $tpl ) = $self->process_error( 404, $res, $type, $req );
303             } else {
304 3 50       11 if ( ref $req->logger ) {
305 3         27 $req->logger->( { level => 'warn', message => $res } );
306             }
307 3         19 ( $res, $tpl ) = $self->process_error( 500, $res, $type, $req );
308             }
309 9         18 $env->{'tt.template'} = $tpl;
310             }
311              
312 41         130 return $res;
313             }
314              
315             1;
316              
317             __END__