1package Dancer::Template::TemplateToolkit;
2our $AUTHORITY = 'cpan:SUKRIA';
3#ABSTRACT: Template Toolkit wrapper for Dancer
4$Dancer::Template::TemplateToolkit::VERSION = '1.3513';
5use strict;
6use warnings;
7use Carp;
8use Dancer::Config 'setting';
9use Dancer::ModuleLoader;
10use Dancer::Exception qw(:all);
11
12use base 'Dancer::Template::Abstract';
13
14my $_engine;
15
16sub init {
17    my ($self) = @_;
18
19    my $class = $self->config->{subclass} || "Template";
20    raise core_template => "$class is needed by Dancer::Template::TemplateToolkit"
21      if !$class->can("process") and !Dancer::ModuleLoader->load($class);
22
23    my $charset = setting('charset') || '';
24    my @encoding = length($charset) ? ( ENCODING => $charset ) : ();
25
26    my $is_subclass = $class ne 'Template';
27
28    my @anycase  = $is_subclass ? () : ( ANYCASE  => 1 );
29    my @absolute = $is_subclass ? () : ( ABSOLUTE => 1 );
30
31    my @inc_path = $is_subclass ? ()
32        : ( INCLUDE_PATH => $self->config->{INCLUDE_PATH} || setting('views') );
33
34    my $start_tag = $is_subclass
35        ? $self->config->{start_tag}
36        : $self->config->{start_tag} || '<%';
37
38    my $stop_tag = $is_subclass
39        ? $self->config->{stop_tag} || $self->config->{end_tag}
40        : $self->config->{stop_tag} || $self->config->{end_tag} || '%>';
41
42    # TT expects quotemeta()'ed values here to be used as-is within
43    # its regexp-based tokenizer. To support existing Dancer users who
44    # prefer the default TT tags and who've already figured this out,
45    # let's skip this if the tags are already ok.
46    # Just FYI: TT hardcodes '\[%' and '%\]' as default.
47    #
48    my @start = ();
49    if (defined $start_tag) {
50        @start = ( START_TAG => $start_tag eq '\[%' || $start_tag eq '\[\%'
51            ? $start_tag
52            : quotemeta($start_tag)
53        );
54    }
55    my @stop = ();
56    if (defined $stop_tag) {
57        @stop = ( END_TAG => $stop_tag eq '%\]' || $stop_tag eq '\%\]'
58            ? $stop_tag
59            : quotemeta($stop_tag)
60        );
61    }
62    my @embedded = ();
63    if ($self->config->{embedded_templates}) {
64	Dancer::ModuleLoader->load('Template::Provider::FromDATA')
65	    or croak "The Package Template::Provider::FromDATA must be installed to use embedded_templates";
66
67	@embedded = ( LOAD_TEMPLATES => [Template::Provider::FromDATA->new()] );
68    }
69
70    my $tt_config = {
71        @anycase,
72        @absolute,
73        @encoding,
74        @inc_path,
75        @start,
76        @stop,
77        @embedded,
78        %{$self->config},
79    };
80
81    $_engine = $class->new(%$tt_config);
82}
83
84sub set_wrapper {
85	my ($self, $when, $file) = @_;
86	my $wrappers = $_engine->{SERVICE}->{WRAPPER};
87	unless (defined $file) {
88		$file = $when;
89		my @orig = @$wrappers;
90        $self->{orig_wrappers} = \@orig;
91		@$wrappers = ($file);
92        return;
93	}
94	if ($when eq 'outer') {
95        unshift @$wrappers => $file;
96    } elsif ($when eq 'inner') {
97        push @$wrappers => $file;
98    } else {
99        raise core_template => "'$when' isn't a valid identifier";
100    }
101}
102
103sub reset_wrapper {
104    my ($self) = @_;
105	my $wrappers = $_engine->{SERVICE}->{WRAPPER};
106    my $orig = $self->{orig_wrappers} || [];
107    my @old = @$wrappers;
108    @$wrappers = @$orig;
109    return @old;
110}
111
112sub unset_wrapper {
113    my ($self, $when) = @_;
114	my $wrappers = $_engine->{SERVICE}->{WRAPPER};
115	if ($when eq 'outer') {
116        return shift @$wrappers;
117    } elsif ($when eq 'inner') {
118        return pop @$wrappers;
119    } else {
120        raise core_template => "'$when' isn't a valid identifier";
121    }
122}
123
124sub render {
125    my ($self, $template, $tokens) = @_;
126
127    $self->view_exists($template) or raise core_template => "'$template' doesn't exist or not a regular file";
128
129    my $content = "";
130    my $charset = setting('charset') || '';
131    my @options = length($charset) ? ( binmode => ":encoding($charset)" ) : ();
132    $_engine->process($template, $tokens, \$content, @options) or raise core_template => $_engine->error;
133    return $content;
134}
135
136sub view_exists {
137    my ($self, $view) = @_;
138
139    return 1 if ref $view;
140
141    if ($self->config->{embedded_templates}) {
142	eval {
143	    $_engine->context->template($view);
144	};
145	return ! $@;
146    }
147
148    return -f $view;
149}
150
151sub view {
152    my ($self, $view) = @_;
153
154    if ($self->config->{embedded_templates}) {
155	return $view;
156    }
157    else {
158	$self->SUPER::view($view);
159    }
160}
161
1621;
163
164__END__
165
166=pod
167
168=encoding UTF-8
169
170=head1 NAME
171
172Dancer::Template::TemplateToolkit - Template Toolkit wrapper for Dancer
173
174=head1 VERSION
175
176version 1.3513
177
178=head1 DESCRIPTION
179
180This class is an interface between Dancer's template engine abstraction layer
181and the L<Template> module.
182
183This template engine is recommended for production purposes, but depends on the
184Template module.
185
186In order to use this engine, use the template setting:
187
188    template: template_toolkit
189
190This can be done in your config.yml file or directly in your app code with the
191B<set> keyword.
192
193Note that by default,  Dancer configures the Template::Toolkit engine to use
194<% %> brackets instead of its default [% %] brackets.  This can be changed
195within your config file - for example:
196
197    template: template_toolkit
198    engines:
199        template_toolkit:
200            start_tag: '[%'
201            stop_tag: '%]'
202
203You can also add any options you would normally add to the Template module's
204initialization. You could, for instance, enable saving the compiled templates:
205
206    engines:
207        template_toolkit:
208            COMPILE_DIR: 'caches/templates'
209            COMPILE_EXT: '.ttc'
210
211Note though that unless you change them, Dancer sets both of the Template
212options C<ANYCASE> and C<ABSOLUTE> on, as well as pointing C<INCLUDE_PATH>
213to your B<views> directory and setting C<ENCODING> to your B<charset>
214setting.
215
216=head1 SUBCLASSING
217
218By default, L<Template> is used, but you can configure Dancer to use a
219subclass of Template with the C<subclass> option.
220
221    engines:
222        template_toolkit:
223            subclass: My::Template
224
225When used like this, Dancer skips the defaults mentioned above.  Only those
226included in your config file are sent to the subclass.
227
228=head1 WRAPPER, META variables, SETs
229
230Dancer already provides a WRAPPER-like ability, which we call a "layout". The
231reason we do not use TT's WRAPPER (which also makes it incompatible with it) is
232because not all template systems support it. Actually, most don't.
233
234However, you might want to use it, and be able to define META variables and
235regular L<Template::Toolkit> variables.
236
237These few steps will get you there:
238
239=over 4
240
241=item * Disable the layout in Dancer
242
243You can do this by simply commenting (or removing) the C<layout> configuration
244in the F<config.yml> file.
245
246=item * Use Template Toolkit template engine
247
248Change the configuration of the template to Template Toolkit:
249
250    # in config.yml
251    template: "template_toolkit"
252
253=item * Tell the Template Toolkit engine who's your wrapper
254
255    # in config.yml
256    # ...
257    engines:
258        template_toolkit:
259            WRAPPER: layouts/main.tt
260
261=back
262
263Done! Everything will work fine out of the box, including variables and META
264variables.
265
266=head1 EMBEDDED TEMPLATES
267
268You can embed your templates in your script file, to get a self-contained dancer
269application in one file (inspired by L<http://advent.perldancer.org/2011/3>).
270
271To enable this:
272
273    # in app.pl
274    # ...
275    set engines => {
276        template_toolkit => {
277            embedded_templates => 1,
278        },
279    };
280    set template => 'template_toolkit';
281
282This feature requires L<Template::Provider::FromDATA>. Put your templates in the
283__DATA__ section, and start every template with __${templatename}__.
284
285=head1 USING TT'S WRAPPER STACK
286
287This engine provides three additional methods to access the WRAPPER stack of
288TemplateToolkit.
289
290=head2 set_wrapper
291
292Synopsis:
293
294    engine('template')->set_wrapper( inner => 'inner_layout.tt' );
295    engine('template')->set_wrapper( outer => 'outer_layout.tt' );
296    engine('template')->set_wrapper( 'only_layout.tt' );
297
298The first two lines pushes/unshifts layout files to the wrapper array.
299The third line overwrites the wrapper array with a single element.
300
301=head2 unset_wrapper
302
303Synopsis:
304
305    engine('template')->unset_wrapper('inner') # returns 'inner_layout.tt';
306    engine('template')->unset_wrapper('outer') # returns 'outer_layout.tt';
307
308These lines pops/shifts layout files from the wrapper array and returns the
309removed elements.
310
311=head2 reset_wrapper
312
313Synopsis:
314
315    engine('template')->reset_wrapper;
316
317This method restores the wrapper array after a set_wrapper call.
318
319=head1 SEE ALSO
320
321L<Dancer>, L<Template>
322
323=head1 AUTHOR
324
325Dancer Core Developers
326
327=head1 COPYRIGHT AND LICENSE
328
329This software is copyright (c) 2010 by Alexis Sukrieh.
330
331This is free software; you can redistribute it and/or modify it under
332the same terms as the Perl 5 programming language system itself.
333
334=cut
335