1#============================================================= -*-Perl-*-
2#
3# Template::Plugin::Filter
4#
5# DESCRIPTION
6#   Template Toolkit module implementing a base class plugin
7#   object which acts like a filter and can be used with the
8#   FILTER directive.
9#
10# AUTHOR
11#   Andy Wardley   <abw@wardley.org>
12#
13# COPYRIGHT
14#   Copyright (C) 2001-2009 Andy Wardley.  All Rights Reserved.
15#
16#   This module is free software; you can redistribute it and/or
17#   modify it under the same terms as Perl itself.
18#
19#============================================================================
20
21package Template::Plugin::Filter;
22
23use strict;
24use warnings;
25use base 'Template::Plugin';
26use Scalar::Util 'weaken';
27
28
29our $VERSION = 1.38;
30our $DYNAMIC = 0 unless defined $DYNAMIC;
31
32
33sub new {
34    my ($class, $context, @args) = @_;
35    my $config = @args && ref $args[-1] eq 'HASH' ? pop(@args) : { };
36
37    # look for $DYNAMIC
38    my $dynamic;
39    {
40        no strict 'refs';
41        $dynamic = ${"$class\::DYNAMIC"};
42    }
43    $dynamic = $DYNAMIC unless defined $dynamic;
44
45    my $self = bless {
46        _CONTEXT => $context,
47        _DYNAMIC => $dynamic,
48        _ARGS    => \@args,
49        _CONFIG  => $config,
50    }, $class;
51
52    return $self->init($config)
53        || $class->error($self->error());
54}
55
56
57sub init {
58    my ($self, $config) = @_;
59    return $self;
60}
61
62
63sub factory {
64    my $self = shift;
65    my $this = $self;
66
67    # This causes problems: https://rt.cpan.org/Ticket/Display.html?id=46691
68    # If the plugin is loaded twice in different templates (one INCLUDEd into
69    # another) then the filter gets garbage collected when the inner template
70    # ends (at least, I think that's what's happening).  So I'm going to take
71    # the "suck it and see" approach, comment it out, and wait for someone to
72    # complain that this module is leaking memory.
73
74    # weaken($this);
75
76    if ($self->{ _DYNAMIC }) {
77        return $self->{ _DYNAMIC_FILTER } ||= [ sub {
78            my ($context, @args) = @_;
79            my $config = ref $args[-1] eq 'HASH' ? pop(@args) : { };
80
81            return sub {
82                $this->filter(shift, \@args, $config);
83            };
84        }, 1 ];
85    }
86    else {
87        return $self->{ _STATIC_FILTER } ||= sub {
88            $this->filter(shift);
89        };
90    }
91}
92
93sub filter {
94    my ($self, $text, $args, $config) = @_;
95    return $text;
96}
97
98
99sub merge_config {
100    my ($self, $newcfg) = @_;
101    my $owncfg = $self->{ _CONFIG };
102    return $owncfg unless $newcfg;
103    return { %$owncfg, %$newcfg };
104}
105
106
107sub merge_args {
108    my ($self, $newargs) = @_;
109    my $ownargs = $self->{ _ARGS };
110    return $ownargs unless $newargs;
111    return [ @$ownargs, @$newargs ];
112}
113
114
115sub install_filter {
116    my ($self, $name) = @_;
117    $self->{ _CONTEXT }->define_filter( $name => $self->factory );
118    return $self;
119}
120
121
122
1231;
124
125__END__
126
127=head1 NAME
128
129Template::Plugin::Filter - Base class for plugin filters
130
131=head1 SYNOPSIS
132
133    package MyOrg::Template::Plugin::MyFilter;
134
135    use Template::Plugin::Filter;
136    use base qw( Template::Plugin::Filter );
137
138    sub filter {
139        my ($self, $text) = @_;
140
141        # ...mungify $text...
142
143        return $text;
144    }
145
146    # now load it...
147    [% USE MyFilter %]
148
149    # ...and use the returned object as a filter
150    [% FILTER $MyFilter %]
151      ...
152    [% END %]
153
154=head1 DESCRIPTION
155
156This module implements a base class for plugin filters.  It hides
157the underlying complexity involved in creating and using filters
158that get defined and made available by loading a plugin.
159
160To use the module, simply create your own plugin module that is
161inherited from the C<Template::Plugin::Filter> class.
162
163    package MyOrg::Template::Plugin::MyFilter;
164
165    use Template::Plugin::Filter;
166    use base qw( Template::Plugin::Filter );
167
168Then simply define your C<filter()> method.  When called, you get
169passed a reference to your plugin object (C<$self>) and the text
170to be filtered.
171
172    sub filter {
173        my ($self, $text) = @_;
174
175        # ...mungify $text...
176
177        return $text;
178    }
179
180To use your custom plugin, you have to make sure that the Template
181Toolkit knows about your plugin namespace.
182
183    my $tt2 = Template->new({
184        PLUGIN_BASE => 'MyOrg::Template::Plugin',
185    });
186
187Or for individual plugins you can do it like this:
188
189    my $tt2 = Template->new({
190        PLUGINS => {
191            MyFilter => 'MyOrg::Template::Plugin::MyFilter',
192        },
193    });
194
195Then you C<USE> your plugin in the normal way.
196
197    [% USE MyFilter %]
198
199The object returned is stored in the variable of the same name,
200'C<MyFilter>'.  When you come to use it as a C<FILTER>, you should add
201a dollar prefix.  This indicates that you want to use the filter
202stored in the variable 'C<MyFilter>' rather than the filter named
203'C<MyFilter>', which is an entirely different thing (see later for
204information on defining filters by name).
205
206    [% FILTER $MyFilter %]
207       ...text to be filtered...
208    [% END %]
209
210You can, of course, assign it to a different variable.
211
212    [% USE blat = MyFilter %]
213
214    [% FILTER $blat %]
215       ...text to be filtered...
216    [% END %]
217
218Any configuration parameters passed to the plugin constructor from the
219C<USE> directive are stored internally in the object for inspection by
220the C<filter()> method (or indeed any other method).  Positional
221arguments are stored as a reference to a list in the C<_ARGS> item while
222named configuration parameters are stored as a reference to a hash
223array in the C<_CONFIG> item.
224
225For example, loading a plugin as shown here:
226
227    [% USE blat = MyFilter 'foo' 'bar' baz = 'blam' %]
228
229would allow the C<filter()> method to do something like this:
230
231    sub filter {
232        my ($self, $text) = @_;
233
234        my $args = $self->{ _ARGS   };  # [ 'foo', 'bar' ]
235        my $conf = $self->{ _CONFIG };  # { baz => 'blam' }
236
237        # ...munge $text...
238
239        return $text;
240    }
241
242By default, plugins derived from this module will create static
243filters.  A static filter is created once when the plugin gets
244loaded via the C<USE> directive and re-used for all subsequent
245C<FILTER> operations.  That means that any argument specified with
246the C<FILTER> directive are ignored.
247
248Dynamic filters, on the other hand, are re-created each time
249they are used by a C<FILTER> directive.  This allows them to act
250on any parameters passed from the C<FILTER> directive and modify
251their behaviour accordingly.
252
253There are two ways to create a dynamic filter.  The first is to
254define a C<$DYNAMIC> class variable set to a true value.
255
256    package MyOrg::Template::Plugin::MyFilter;
257    use base 'Template::Plugin::Filter';
258    our $DYNAMIC = 1;
259
260The other way is to set the internal C<_DYNAMIC> value within the C<init()>
261method which gets called by the C<new()> constructor.
262
263    sub init {
264        my $self = shift;
265        $self->{ _DYNAMIC } = 1;
266        return $self;
267    }
268
269When this is set to a true value, the plugin will automatically
270create a dynamic filter.  The outcome is that the C<filter()> method
271will now also get passed a reference to an array of postional
272arguments and a reference to a hash array of named parameters.
273
274So, using a plugin filter like this:
275
276    [% FILTER $blat 'foo' 'bar' baz = 'blam' %]
277
278would allow the C<filter()> method to work like this:
279
280    sub filter {
281        my ($self, $text, $args, $conf) = @_;
282
283        # $args = [ 'foo', 'bar' ]
284        # $conf = { baz => 'blam' }
285    }
286
287In this case can pass parameters to both the USE and FILTER directives,
288so your filter() method should probably take that into account.
289
290    [% USE MyFilter 'foo' wiz => 'waz' %]
291
292    [% FILTER $MyFilter 'bar' biz => 'baz' %]
293       ...
294    [% END %]
295
296You can use the C<merge_args()> and C<merge_config()> methods to do a quick
297and easy job of merging the local (e.g. C<FILTER>) parameters with the
298internal (e.g. C<USE>) values and returning new sets of conglomerated
299data.
300
301    sub filter {
302        my ($self, $text, $args, $conf) = @_;
303
304        $args = $self->merge_args($args);
305        $conf = $self->merge_config($conf);
306
307        # $args = [ 'foo', 'bar' ]
308        # $conf = { wiz => 'waz', biz => 'baz' }
309        ...
310    }
311
312You can also have your plugin install itself as a named filter by
313calling the C<install_filter()> method from the C<init()> method.  You
314should provide a name for the filter, something that you might
315like to make a configuration option.
316
317    sub init {
318        my $self = shift;
319        my $name = $self->{ _CONFIG }->{ name } || 'myfilter';
320        $self->install_filter($name);
321        return $self;
322    }
323
324This allows the plugin filter to be used as follows:
325
326    [% USE MyFilter %]
327
328    [% FILTER myfilter %]
329       ...
330    [% END %]
331
332or
333
334    [% USE MyFilter name = 'swipe' %]
335
336    [% FILTER swipe %]
337       ...
338    [% END %]
339
340Alternately, you can allow a filter name to be specified as the
341first positional argument.
342
343    sub init {
344        my $self = shift;
345        my $name = $self->{ _ARGS }->[0] || 'myfilter';
346        $self->install_filter($name);
347        return $self;
348    }
349
350    [% USE MyFilter 'swipe' %]
351
352    [% FILTER swipe %]
353       ...
354    [% END %]
355
356=head1 EXAMPLE
357
358Here's a complete example of a plugin filter module.
359
360    package My::Template::Plugin::Change;
361    use Template::Plugin::Filter;
362    use base qw( Template::Plugin::Filter );
363
364    sub init {
365        my $self = shift;
366
367        $self->{ _DYNAMIC } = 1;
368
369        # first arg can specify filter name
370        $self->install_filter($self->{ _ARGS }->[0] || 'change');
371
372        return $self;
373    }
374
375    sub filter {
376        my ($self, $text, $args, $config) = @_;
377
378        $config = $self->merge_config($config);
379        my $regex = join('|', keys %$config);
380
381        $text =~ s/($regex)/$config->{ $1 }/ge;
382
383        return $text;
384    }
385
386    1;
387
388=head1 AUTHOR
389
390Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/>
391
392=head1 COPYRIGHT
393
394Copyright (C) 1996-2007 Andy Wardley.  All Rights Reserved.
395
396This module is free software; you can redistribute it and/or
397modify it under the same terms as Perl itself.
398
399=head1 SEE ALSO
400
401L<Template::Plugin>, L<Template::Filters>, L<Template::Manual::Filters>
402
403=cut
404
405# Local Variables:
406# mode: perl
407# perl-indent-level: 4
408# indent-tabs-mode: nil
409# End:
410#
411# vim: expandtab shiftwidth=4:
412