Commit | Line | Data |
3fea05b9 |
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 | |
21 | package Template::Plugin::Filter; |
22 | |
23 | use strict; |
24 | use warnings; |
25 | use base 'Template::Plugin'; |
26 | use Scalar::Util 'weaken'; |
27 | |
28 | |
29 | our $VERSION = 1.38; |
30 | our $DYNAMIC = 0 unless defined $DYNAMIC; |
31 | |
32 | |
33 | sub 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 | |
57 | sub init { |
58 | my ($self, $config) = @_; |
59 | return $self; |
60 | } |
61 | |
62 | |
63 | sub 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 | |
93 | sub filter { |
94 | my ($self, $text, $args, $config) = @_; |
95 | return $text; |
96 | } |
97 | |
98 | |
99 | sub merge_config { |
100 | my ($self, $newcfg) = @_; |
101 | my $owncfg = $self->{ _CONFIG }; |
102 | return $owncfg unless $newcfg; |
103 | return { %$owncfg, %$newcfg }; |
104 | } |
105 | |
106 | |
107 | sub merge_args { |
108 | my ($self, $newargs) = @_; |
109 | my $ownargs = $self->{ _ARGS }; |
110 | return $ownargs unless $newargs; |
111 | return [ @$ownargs, @$newargs ]; |
112 | } |
113 | |
114 | |
115 | sub install_filter { |
116 | my ($self, $name) = @_; |
117 | $self->{ _CONTEXT }->define_filter( $name => $self->factory ); |
118 | return $self; |
119 | } |
120 | |
121 | |
122 | |
123 | 1; |
124 | |
125 | __END__ |
126 | |
127 | =head1 NAME |
128 | |
129 | Template::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 | |
156 | This module implements a base class for plugin filters. It hides |
157 | the underlying complexity involved in creating and using filters |
158 | that get defined and made available by loading a plugin. |
159 | |
160 | To use the module, simply create your own plugin module that is |
161 | inherited 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 | |
168 | Then simply define your C<filter()> method. When called, you get |
169 | passed a reference to your plugin object (C<$self>) and the text |
170 | to be filtered. |
171 | |
172 | sub filter { |
173 | my ($self, $text) = @_; |
174 | |
175 | # ...mungify $text... |
176 | |
177 | return $text; |
178 | } |
179 | |
180 | To use your custom plugin, you have to make sure that the Template |
181 | Toolkit knows about your plugin namespace. |
182 | |
183 | my $tt2 = Template->new({ |
184 | PLUGIN_BASE => 'MyOrg::Template::Plugin', |
185 | }); |
186 | |
187 | Or 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 | |
195 | Then you C<USE> your plugin in the normal way. |
196 | |
197 | [% USE MyFilter %] |
198 | |
199 | The 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 |
201 | a dollar prefix. This indicates that you want to use the filter |
202 | stored in the variable 'C<MyFilter>' rather than the filter named |
203 | 'C<MyFilter>', which is an entirely different thing (see later for |
204 | information on defining filters by name). |
205 | |
206 | [% FILTER $MyFilter %] |
207 | ...text to be filtered... |
208 | [% END %] |
209 | |
210 | You 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 | |
218 | Any configuration parameters passed to the plugin constructor from the |
219 | C<USE> directive are stored internally in the object for inspection by |
220 | the C<filter()> method (or indeed any other method). Positional |
221 | arguments are stored as a reference to a list in the C<_ARGS> item while |
222 | named configuration parameters are stored as a reference to a hash |
223 | array in the C<_CONFIG> item. |
224 | |
225 | For example, loading a plugin as shown here: |
226 | |
227 | [% USE blat = MyFilter 'foo' 'bar' baz = 'blam' %] |
228 | |
229 | would 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 | |
242 | By default, plugins derived from this module will create static |
243 | filters. A static filter is created once when the plugin gets |
244 | loaded via the C<USE> directive and re-used for all subsequent |
245 | C<FILTER> operations. That means that any argument specified with |
246 | the C<FILTER> directive are ignored. |
247 | |
248 | Dynamic filters, on the other hand, are re-created each time |
249 | they are used by a C<FILTER> directive. This allows them to act |
250 | on any parameters passed from the C<FILTER> directive and modify |
251 | their behaviour accordingly. |
252 | |
253 | There are two ways to create a dynamic filter. The first is to |
254 | define 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 | |
260 | The other way is to set the internal C<_DYNAMIC> value within the C<init()> |
261 | method 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 | |
269 | When this is set to a true value, the plugin will automatically |
270 | create a dynamic filter. The outcome is that the C<filter()> method |
271 | will now also get passed a reference to an array of postional |
272 | arguments and a reference to a hash array of named parameters. |
273 | |
274 | So, using a plugin filter like this: |
275 | |
276 | [% FILTER $blat 'foo' 'bar' baz = 'blam' %] |
277 | |
278 | would 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 | |
287 | In this case can pass parameters to both the USE and FILTER directives, |
288 | so 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 | |
296 | You can use the C<merge_args()> and C<merge_config()> methods to do a quick |
297 | and easy job of merging the local (e.g. C<FILTER>) parameters with the |
298 | internal (e.g. C<USE>) values and returning new sets of conglomerated |
299 | data. |
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 | |
312 | You can also have your plugin install itself as a named filter by |
313 | calling the C<install_filter()> method from the C<init()> method. You |
314 | should provide a name for the filter, something that you might |
315 | like 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 | |
324 | This allows the plugin filter to be used as follows: |
325 | |
326 | [% USE MyFilter %] |
327 | |
328 | [% FILTER myfilter %] |
329 | ... |
330 | [% END %] |
331 | |
332 | or |
333 | |
334 | [% USE MyFilter name = 'swipe' %] |
335 | |
336 | [% FILTER swipe %] |
337 | ... |
338 | [% END %] |
339 | |
340 | Alternately, you can allow a filter name to be specified as the |
341 | first 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 | |
358 | Here'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 | |
390 | Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/> |
391 | |
392 | =head1 COPYRIGHT |
393 | |
394 | Copyright (C) 1996-2007 Andy Wardley. All Rights Reserved. |
395 | |
396 | This module is free software; you can redistribute it and/or |
397 | modify it under the same terms as Perl itself. |
398 | |
399 | =head1 SEE ALSO |
400 | |
401 | L<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: |