File Coverage

blib/lib/Template/Multipass.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package Template::Multipass;
4 4     4   262003 use base qw/Template/;
  4         9  
  4         5233  
5              
6 4     4   131287 use strict;
  4         96  
  4         139  
7 4     4   22 use warnings;
  4         14  
  4         180  
8              
9             our $VERSION = "0.03";
10              
11 4     4   2154 use Data::Visitor::Callback;
  0            
  0            
12              
13             use Template::Multipass::Provider;
14              
15             sub _init {
16             my ( $self, $config, @args ) = @_;
17              
18             # this config is captured and then localized.
19             # no guarantee as to which parts in TT respect a change of config, but it works for most values (e.g. START_TAG, END_TAG)
20             $self->{_multipass}{captured_config} = $config;
21              
22             # this is where meta config overrides are stored
23             my $opts = $config->{MULTIPASS};
24             $self->{_multipass}{config} = $opts;
25              
26             $self->{_multipass}{vars} = $opts->{VARS} || {};
27              
28             my $overlay = {
29             $self->default_meta_options,
30             %$opts,
31             };
32              
33             delete $overlay->{VARS};
34              
35             $self->{_multipass}{config_overlay} = $overlay;
36              
37             $self->SUPER::_init( $config, @args );
38              
39             Data::Visitor::Callback->new(
40             ignore_return_values => 1,
41             'Template::Base' => "visit_ref",
42             'Template::Provider' => sub { $_ = Template::Multipass::Provider->new( provider => $_, template => $self, config => $config ) },
43             )->visit( $self );
44              
45             return $self;
46             }
47              
48             sub default_meta_options {
49             my $self = shift;
50              
51             return (
52             START_TAG => '{%',
53             END_TAG => '%}',
54             WRAPPER => undef,
55             );
56             }
57              
58             # this is called by the top level code. It steals some of the multipass specific options and keeps them safe.
59             # it also lets the normal template have the meta vars.
60             sub process {
61             my ($self, $template, $vars, $outstream, @opts) = @_;
62              
63             my $options = (@opts == 1) && UNIVERSAL::isa($opts[0], 'HASH')
64             ? shift(@opts) : { @opts };
65              
66             my $meta_vars = {
67             %{ $self->{_multipass}{vars} }, # captured by _init
68             %{ $options->{meta_vars} || {} }
69             };
70              
71             local $self->{_multipass}{captured_process_opts} = $options;
72             local $self->{_multipass}{merged_meta_vars} = $meta_vars;
73              
74             $self->SUPER::process(
75             $template,
76             { %$meta_vars, %$vars }, # merge all the meta vars so that the normal template can also see them
77             $outstream,
78             $options,
79             );
80             }
81              
82             # called by the provider from within the process
83             # this will wrap all returned documents from the real provider, and run it
84             # through the meta pass, creating a document from the result of processing the
85             # original
86             sub process_meta_template {
87             my ( $self, $provider, $method, @args ) = @_;
88              
89             # ignore all other providers in the recursion
90             local $self->context->{LOAD_TEMPLATES} = [ $provider ];
91             local $self->context->{PREFIX_MAP} = {};
92              
93             # process( ...., { meta_opts => { blah } } ) causes { blah } to be given to the inner process used on the meta template
94             my $opts = $self->{_multipass}{captured_process_opts}{meta_opts} || {};
95              
96             # calculate the configuration and variables for the meta pass
97             my $overlay = $self->{_multipass}{config_overlay}; # constructed at _init
98             local @{ $self->{_multipass}{captured_config} }{ keys %$overlay } = values %$overlay; # START_TAG, END_TAG etc
99             my $vars = $self->{_multipass}{merged_meta_vars}; # merged by process at the top of the call chain
100              
101             local $@;
102              
103             # dispatch the original method on the provider, getting the original result
104             my ( $doc, $error ) = $provider->$method( @args ); # method is _fetch or _load, or in the case of scalar refs a coderef prepared by the wrapper provider
105              
106             my $out;
107              
108             # reconfigure WRAPPER, PRE_PROCES, PROCESS etc for the meta pass by
109             # localizing and reinitializing with the localized config
110             my $service = $self->service;
111             local @{ $service }{ keys %$service } = ( values %$service );
112             $service->_init($self->{_multipass}{captured_config});
113              
114             # Perform the actual meta pass here:
115              
116             if ( !$error && eval { $self->process( $doc, $vars, \$out, $opts ) } ) {
117             return ({ name => $doc->{name}, path => $doc->{path}, time => $doc->{modtime}, text => $out, load => 0 }, $error );
118             } else {
119             return ( $doc, $error );
120             }
121             }
122              
123             __PACKAGE__;
124              
125             __END__
126              
127             =pod
128              
129             =head1 NAME
130              
131             Template::Multipass - Add a meta template pass to TT
132              
133             =head1 SYNOPSIS
134              
135            
136             my $t = Template::Multipass->new(
137             INCLUDE_PATH => [ file(__FILE__)->parent->subdir("templates")->stringify ],
138             COMPILE_EXT => "c",
139             MULTIPASS => {
140             VARS => {
141             lang => "en",
142             loc => sub { $lang_handle->maketext(@_) },
143             }
144             },
145             );
146              
147             $t->process( ... );
148              
149              
150             # ORIGINAL TT
151              
152             [% loc("hello") %] [% user.name %],
153             [% loc("instructions") %]
154              
155              
156             # MULTIPASS TEMPLATE
157              
158             {% loc("hello") %} [% user.name %],
159             {% loc("instructions") %}
160              
161              
162             # RESULTING CACHED TEMPLATE
163              
164             Hello, [% user.name %]
165             Please select a Moose
166              
167             =head1 DESCRIPTION
168              
169             This module was written to precompute the static parts of templates (text based
170             on variables which are not runtime dependant, and can thus be precomputed).
171              
172             The most prominent example of this is localization of constant strings. This is
173             demonstrated in the L</SYNOPSIS>.
174              
175             Template::Multipass will first process the template with only the meta
176             variables, and cache the result. Then it will process the file again with all
177             of the variables. Subsequent runs will not have to recompute the meta var run
178             unless the variables have been changed.
179              
180             =head1 CONFIGURATION
181              
182             The configuration values inside C<MULTIPASS> in the top level config will be
183             overlayed on top of the normal config during meta template processing. This
184             works for values such as C<START_TAG> and C<END_TAG> (which default to C<{%>
185             and C<%}>), and may work for other values.
186              
187             Additionallly the C<MULTIPASS> hash can take a C<VARS> hash to be used as the
188             meta vars in all runs.
189              
190             This var hash can be further added by passing them as an option to process:
191              
192             $t->process( $template, $vars, $output, { meta_vars => { ... more vars ... } } );
193              
194             Values in options will override those in the configuration.
195              
196             Lastly the L<MANGLE_METHOD> and L<MANGLE_HASH_VARS> values may also be set in
197             the C<MULTIPASS> configuration, and will be discussed in L</CACHING>.
198              
199             =head1 METHODS
200              
201             See L<Template>. The API is unchanged except for:
202              
203             =over 4
204              
205             =item process
206              
207             Also accepts the C<meta_vars> option.
208              
209             =back
210              
211             =head1 CAVEAT
212              
213             =head2 Wrappers
214              
215             Wrappers processed at meta time (C<{% WRAPPER blah %}...{% END %}>) will
216             require the use of C<{% %}> tags to embed the content. Otherwise the content is
217             lost till the C<[% %]> run starts.
218              
219             =head1 CACHING
220              
221             Caching is done using the native L<Template> caching mechanism (C<COMPILE_EXT>, etc).
222              
223             See L<Template/Caching_and_Compiling_Options>.
224              
225             The only difference is that meta templates are cached using filenames that
226             incorperate their meta vars.
227              
228             There are two methods to mangle the file name, using a recursive MD5 hash of
229             the variable hash, or just the top level non reference ones (the default). This
230             is controlled using C<MANGLE_HASH_VARS>.
231              
232             In order to properly utilize the default method to ease debugging (clearer file
233             names) pass in simple top level values. For example if you have a C<loc>
234             function or a C<lang_handle> var to run L<Locale::Maketext> localization, also
235             add a C<lang> top level var containing the language code. The resulting file
236             name will be C<lang-$lang,$template_name.ttc>.
237              
238             If your meta templates require caching based on values inside references or
239             you'd rather not bother with creating top level strings simply enable
240             C<MANGLE_HASH_VARS>, which will result in a file name like
241             C<8a1fad1d1f3313b3647ac90b29eaac95-$template_name.ttc>.
242              
243             =head1 INTERNALS OVERVIEW
244              
245             The way this module works is by wrapping all the providers in a normally
246             instantiated L<Template> object with L<Template::Multipass::Provider>. This
247             provider will delegate to it's wrapped provider and then call back to the top
248             level object for meta template processing. It'll then cache the result with the
249             mangled name, and return the output of the template run as the input of the
250             original template.
251              
252             =cut