File Coverage

blib/lib/Template/Plugin/VMethods.pm
Criterion Covered Total %
statement 64 65 98.4
branch 15 16 93.7
condition 9 12 75.0
subroutine 11 11 100.0
pod 1 1 100.0
total 100 105 95.2


line stmt bran cond sub pod time code
1             package Template::Plugin::VMethods;
2              
3 1     1   66801 use strict;
  1         2  
  1         49  
4             #use warnings;
5              
6 1     1   877 use Template::Plugin::VMethods::VMethodContainer;
  1         3  
  1         31  
7              
8             # set up the default hash before we go adding things to it
9 1     1   6 use Template::Stash;
  1         2  
  1         21  
10              
11 1     1   787 use Template::Plugin;
  1         672  
  1         31  
12 1     1   8 use vars qw(@ISA $VERSION);
  1         1  
  1         74  
13             @ISA = qw(Template::Plugin);
14              
15             $VERSION = "0.03";
16              
17 1     1   5 use constant OPS => [qw{ LIST_OPS SCALAR_OPS HASH_OPS }];
  1         2  
  1         215  
18              
19             =head1 NAME
20              
21             Template::Plugin::VMethods - install vmethods
22              
23             =head1 SYNOPSIS
24              
25             package Template::Plugin::ReverseVMethod;
26             use base qw(Template::Plugin::VMethods);
27             @SCALAR_OPS = qw(reverse);
28              
29             sub reverse
30             {
31             my $string = shift;
32             scalar reverse $string;
33             }
34              
35             1;
36              
37             =head1 DESCRIPTION
38              
39             Simple base class to allow your module to install and remove
40             virtual methods into the Template Toolkit.
41              
42             All you need to do in your package is declare one or more of the
43             variables @LIST_OPS, @SCALAR_OPS or @HASH_OPS to indicate what
44             virtual methods you want to export.
45              
46             These can either be the names of functions in your package, or
47             name/subroutine reference pairs.
48              
49             For example, using named functions:
50              
51             package Template::Plugin::HexVMethod;
52             use base qw(Template::Plugin::VMethods);
53             @SCALAR_OPS = ( "hex" );
54             sub hex { sprintf "%x", $_[0] };
55             1;
56              
57             For example, using the name and subroutine ref pairs:
58              
59             package Template::Plugin::DoubleVMethod;
60             use base qw(Template::Plugin::VMethods);
61             @SCALAR_OPS = ( double => \&double_string);
62             @LIST_OPS = ( double => \&double_list);
63             sub double_string { $_[0]x2 }
64             sub double_list { [ (@{ $_[0] }) x 2] }
65             1;
66              
67             For example, mixing the two freely:
68              
69             package CaesarVMethod;
70             use base qw(Template::Plugin::VMethods);
71             @SCALAR_OPS = ( "caesar",
72             "rot13" => sub { caesar($_[0],"13") } );
73             sub caesar
74             {
75             $string = shift;
76             $string =~ tr/A-Za-z/B-ZAb-za/ for 1..$_[0];
77             return $string;
78             }
79              
80             =head2 Using VMethods Based Plugins
81              
82             Once you've done this people can use your plugin just like they would
83             any other:
84              
85             [% USE CaesarVMethod %]
86             [% foo = "Crbcyr jub yvxr gur pbybhe benatr ner fvyyl" %]
87             The secret phrase is [% foo.rot13 %]
88              
89             The vmethods will remain in effect till the end of the template,
90             meaning all templates called from within this template (i.e. via
91             PROCESS, INCLUDE, WRAPPER, etc) will be able to access the VMethods.
92              
93             It's possible to permanently install the vmethods from perl space, so
94             that all instances of Template everywhere will always have access
95             to all the vmethods by using your plugin like so:
96              
97             use Template::Plugin::CaesarVMethod 'install';
98              
99             =head2 Using subroutines from other classes.
100              
101             Instead of writing virtual methods, you might be using the
102             B class:
103              
104             package Template::Plugin::MD5;
105             use base qw(Template::Plugin::Procedural);
106             use Digest::MD5;
107             sub md5 { Digest::MD5::md5_hex(@_) };
108             sub md5_base64 { Digest::MD5::md5_base64(@_) };
109             1;
110              
111             And you'll be calling the methods as so from within the template:
112              
113             [% USE MD5 %]
114             [% MD5.md5(foo) %]
115              
116             You'd rather use VMethods though, to do things like this:
117              
118             [% USE MD5VMethods %]
119             [% foo.md5 %]
120              
121             The obvious way to do this is to load the original class and take
122             references to those subroutines:
123              
124             package Template::Plugin::MD5VMethods;
125             use base qw(Template::Plugin::VMethods);
126             use Template::Plugin::MD5;
127             @SCALAR_OPS = (md5 => \&Template::Plugin::MD5::md5,
128             md5_base64 => \&Template::Plugin::MD5::md5_base64);
129             1;
130              
131             This can get awfully tedious very soon when you're attempting to
132             re-wrap a class with many subroutines in it. Because of this
133             B allows you to use a special variable
134             B<$VMETHOD_PACKAGE> which can be used to alter the package this module
135             uses to find named VMethods.
136              
137             package Template::Plugin::MD5VMethods;
138             use base qw(Template::Plugin::VMethods);
139             use Template::Plugin::MD5;
140             $VMETHOD_PACKAGE = 'Template::Plugin::MD5';
141             @SCALAR_OPS = qw(md5 md5_base64);
142             1;
143              
144             =cut
145              
146             #### simple inheritable methods
147              
148             sub new
149             {
150 9     9 1 25097 my $class = shift;
151 9         19 my $context = shift;
152              
153             # check no-one is trying to create us directly
154 9 50       33 if ($class eq __PACKAGE__)
155 0         0 { die "This class must be subclassed, not instantiated directly" }
156              
157             # create a new object
158 9         29 my $this = bless {}, $class;
159              
160             # install the vmethods
161 9         38 $this->_install($context);
162              
163             # return the object
164 9         28 return $this;
165             }
166              
167             sub import
168             {
169 5     5   38373 my $class = shift;
170              
171 5 100 66     382 if (defined $_[0] && $_[0] eq "install")
172 1         5 { $class->_install() }
173             }
174              
175             sub _install
176             {
177 10     10   18 my $class = shift;
178 10 100       33 $class = ref $class if ref $class;
179              
180             # use another class?
181 10         11 my $destclass;
182             {
183 1     1   6 no strict 'refs';
  1         3  
  1         79  
  10         14  
184 10   66     14 $destclass = ${ $class . '::'. 'VMETHOD_PACKAGE' } || $class;
185             }
186              
187 10         16 my $context = shift;
188              
189             # right, get data structure that we can work with
190 10         12 my $data;
191 10         18 foreach my $op (@{ OPS() })
  10         33  
192             {
193             # we need to access variables by name now
194             # (like exporter does)
195 1     1   5 no strict 'refs';
  1         1  
  1         374  
196              
197             # work out if we've got any ops declared
198 30         56 my $varname = $class.'::'.$op;
199 30 100       32 next unless @{$varname};
  30         212  
200              
201             # work out where we're going to stick them
202 12         19 my $hashref = ${'Template::Stash::'.$op};
  12         38  
203              
204             # process each thingy
205 12         14 my $count = 0;
206 12         17 while ($count < @{$varname})
  24         108  
207             {
208 12         40 my $vmethname = $varname->[ $count ];
209 12         16 $count++;
210              
211             # either get the subroutine from the namespace or
212             # from the list
213 12         15 my $sub;
214 12 100 66     73 if (ref($varname->[ $count ]) && ref($varname->[ $count ]) eq "CODE")
215             {
216             # ah we've got a ref in the list, use that.
217 4         13 $sub = $varname->[ $count ];
218 4         5 $count++;
219             }
220             else
221             {
222 8         10 $sub = \&{ $destclass . '::' . $vmethname };
  8         32  
223             }
224              
225             # do we want to be able to remove them again?
226 12 100       29 if ($context)
227             {
228             # get the stash
229 11         39 my $stash = $context->stash;
230              
231             # remember what was there originally if there was something there
232 11 100 100     128 if (!defined $hashref->{ $vmethname })
    100          
233             {
234             #print STDERR "Creating empty ref $op $vmethname\n";
235              
236 6         42 $stash->set("origvmethod$op$vmethname",
237             Template::Plugin::VMethods::VMethodContainer->new(
238             $op,
239             $vmethname,
240             $stash,
241             ));
242             }
243             elsif (!$stash->get("origvmethod$op$vmethname") ||
244             !$stash->get("origvmethod$op$vmethname")->stashmatch($stash))
245             {
246             #print STDERR "Creating sub ref $op $vmethname\n";
247              
248 4         59 $stash->set("origvmethod$op$vmethname",
249             Template::Plugin::VMethods::VMethodContainer->new(
250             $op,
251             $vmethname,
252             $stash,
253             $hashref->{ $vmethname },
254              
255             ));
256             }
257             }
258              
259             # print what we're installing
260             #use B::Deparse;
261             #my $deparse = B::Deparse->new("-p", "-sC");
262             #print STDERR "installing as a $op in $vmethname:\n";
263             #print STDERR $deparse->coderef2text($sub)."\n";
264              
265             # install the new op
266 12         35 $hashref->{ $vmethname } = $sub;
267             }
268             }
269             }
270              
271             =head1 BUGS
272              
273             This module has an 'import' and a 'new' method; If you implement any of
274             these in your subclass you'll need to chain the methods like this:
275              
276             sub new
277             {
278             my $class = shift;
279             my $this = $class->SUPER::new(@_);
280              
281             ...
282             }
283              
284             Even if you manually redefine a VMethod that this module has defined
285             (by manually assigning to the $Template::Stash::*_OPS variables) then
286             that VMethod will still be restored to the value that it had before
287             the USE statement that installed the VMethod you are overriding as soon
288             as you leave the template that that USE method was declared in.
289              
290             Sharing $Template::Stash::*_OPS across threads will really screw
291             this whole system up; But you weren't going to do that anyway, were
292             you?
293              
294             Further bugs (and requests for new features) can be reported to the
295             author though the CPAN RT system:
296             L
297              
298             =head1 AUTHOR
299              
300             Written by Mark Fowler Emark@twoshortplanks.comE
301              
302             Copyright Mark Fowler 2003. All Rights Reserved.
303              
304             This program is free software; you can redistribute it
305             and/or modify it under the same terms as Perl itself.
306              
307             Caesar code example adapted from Crypt::Caesar by Juerd
308              
309             =head1 SEE ALSO
310              
311             L
312              
313             =cut
314              
315             1;