File Coverage

lib/Any/Template.pm
Criterion Covered Total %
statement 35 66 53.0
branch 4 30 13.3
condition 1 6 16.6
subroutine 9 13 69.2
pod 4 6 66.6
total 53 121 43.8


line stmt bran cond sub pod time code
1             ###############################################################################
2             # Purpose : Consistent interface to templating modules
3             # Author : John Alden
4             # Created : Dec 2004
5             # CVS : $Header: /home/cvs/software/cvsroot/any_template/lib/Any/Template.pm,v 1.15 2006/04/18 08:32:26 johna Exp $
6             ###############################################################################
7              
8             package Any::Template;
9              
10 1     1   36157 use strict;
  1         3  
  1         28  
11 1     1   5 use Carp;
  1         2  
  1         57  
12 1     1   15 use File::Spec;
  1         2  
  1         19  
13 1     1   4 use File::Find;
  1         1  
  1         43  
14              
15 1     1   4 use vars qw($VERSION);
  1         1  
  1         812  
16             $VERSION = sprintf"%d.%03d", q$Revision: 1.15 $ =~ /: (\d+)\.(\d+)/;
17              
18             sub new {
19 1     1 1 45 my($class, $options) = @_;
20 1         3 DUMP("Any::Template constructor - options", $options);
21 1 50       5 $options->{Options} = {} unless defined ($options->{Options}); #Ensure this key is present
22              
23             #Input checking
24 1 50 33     191 my $backend = $options->{Backend} || $ENV{ANY_TEMPLATE_DEFAULT}
25             or croak("You must nominate a Backend or set the ANY_TEMPLATE_DEFAULT environment variable");
26 0 0       0 croak("Package name '$backend' looks incorrect") if($backend =~ /[^\w:]/);
27 0         0 my @sources = qw(Filename Filehandle String);
28 0 0       0 croak("You must supply one of: ".join(", ", @sources)) unless grep {defined $options->{$_}} @sources;
  0         0  
29            
30             #Load backend
31 0         0 $backend = join("::", __PACKAGE__, 'Backend', $backend);
32 0         0 eval "require $backend";
33 0 0       0 die($@) if($@);
34              
35             #Create object (containing backend)
36 0         0 my $self = {};
37 0         0 $self->{backend} = $backend->new($options);
38 0         0 DUMP({Level => 2}, "Any::Template object", $self);
39 0         0 return bless($self, $class);
40             }
41              
42             sub process {
43 0     0 1 0 my($self, $data, $collector) = @_;
44            
45             #Input checking
46 0 0       0 croak("You must supply a data structure") unless defined($data);
47 0 0       0 croak("Data structure should be a reference") unless(ref $data);
48            
49 0         0 my $string;
50 0 0       0 if(defined $collector) {
51 0         0 $self->_process($data, $collector);
52             } else {
53 0         0 $self->_process($data, \$string);
54             }
55 0         0 return $string;
56             }
57              
58             sub native_object {
59 0     0 1 0 my $self = shift;
60 0         0 return $self->{backend}->native_object();
61             }
62              
63             #Find all the available backends for Any::Template
64             sub available_backends {
65 1     1 1 38 my @possible_locations = grep {-d $_} map {File::Spec->catfile($_, split(/::/, __PACKAGE__), "Backend")} @INC;
  12         301  
  12         144  
66            
67 1         3 my @backends;
68             my $collector = sub {
69 28 100   28   662 return unless $_ =~ /\.pm$/;
70 16         17 my $file = $File::Find::name;
71 16         75 $file =~ s/\Q$File::Find::topdir\E//;
72 16         38 $file =~ s/\.pm$//;
73 16         75 my @dirs = File::Spec->splitdir($file);
74 16         18 shift @dirs;
75 16         29 $file = join("::", @dirs);
76 16         368 push @backends, $file;
77 1         6 };
78            
79 1         123 File::Find::find($collector, @possible_locations);
80 1         9 return \@backends;
81             }
82              
83             #
84             # Private functions
85             #
86              
87             sub _process {
88 0     0   0 my($self, $data, $collector) = @_;
89            
90             #Preprocess data if required
91 0         0 $data = $self->{backend}->preprocess($data);
92              
93             #Type-based dispatch
94 0 0 0     0 die("Hash or array refs not supported as sinks") if(ref $collector eq "HASH" || ref $collector eq "ARRAY");
95 0 0       0 return $self->{backend}->process_to_string($data, $collector) if(ref $collector eq "SCALAR");
96 0 0       0 return $self->{backend}->process_to_sub($data, $collector) if(ref $collector eq "CODE");
97 0 0       0 return $self->{backend}->process_to_filehandle($data, $collector) if(ref $collector eq "GLOB"); #Filehandle ref
98 0 0       0 return $self->{backend}->process_to_filehandle($data, \$collector) if(ref \$collector eq "GLOB"); #Filehandle
99 0 0       0 return $self->{backend}->process_to_file($data, $collector) if(not ref $collector); #Must come after check for glob
100 0         0 return $self->{backend}->process_to_filehandle($data, $collector); #object - treat as a filehandle
101             }
102              
103             #Log::Trace stubs
104 0     0 0 0 sub TRACE{}
105 1     1 0 1 sub DUMP{}
106              
107             =head1 NAME
108              
109             Any::Template - provide a consistent interface to a wide array of templating languages
110              
111             =head1 SYNOPSIS
112              
113             use Any::Template;
114             my $template = new Any::Template({
115             Backend => 'HTML::Template',
116             Filename => 'page.tmpl',
117             Options => {'strict' => 0}
118             });
119             my $output = $template->process($data);
120              
121             my $template2 = new Any::Template({
122             Backend => 'Text::Template',
123             String => $template2_content
124             });
125             $template->process($data, \*STDOUT);
126              
127             =head1 DESCRIPTION
128              
129             This module provides a simple, consistent interface to common templating engines so you can write code that
130             is agnostic to the template language used in the presentation layer. This means you can allow your interface
131             developers to work in the templating language they're happiest in or write code that works with legacy/in-house
132             templating modules but can also be released onto CPAN and work with more standard CPAN templating systems.
133              
134             By its very nature, this interface only exposes pretty much the lowest common denominator of the template engine APIs.
135             It does however provide a fairly rich set of input and output mechanisms, using native implementations where available
136             and providing some default implementations to extend the default set offered by some templating modules.
137              
138             If you need the quirky features of a particular templating engine, then this may not be for you.
139             Having said that, in some cases you may be able to encapsulate some of your logic in options passed into the adaptor classes
140             (either rolling your own adaptors, or improving ours) to pull the relevant strings on the backend module.
141              
142             Templateing languages supported by backends supplied with this distribution can be found in the README (remember there may be others out there and you can always roll your own).
143              
144             =head1 METHODS
145              
146             =over 4
147              
148             =item my $template = new Any::Template(\%options);
149              
150             See below for a list of options
151              
152             =item $template->process($data_structure, $sink);
153              
154             $sink can be:
155             - a scalar ref
156             - a filename (string)
157             - a filehandle (as a glob or glob ref) or an object offering a print method
158             - a coderef (output will be passed in as the first argument)
159              
160             =item $string = $template->process($data_structure);
161              
162             A convenience form, if no second argument is passed to C, equivalent to:
163            
164             my $string;
165             $template->process($data_structure, \$string);
166              
167             except data is passed by value rather than by reference.
168              
169             =item $templating_engine = $template->native_object();
170              
171             Allows the native templating engine to be accessed.
172             This completely breaks the abstraction of Any::Template so it's not recommended you use it
173             other than as a bridging strategy as part of a refectoring/migration process
174             (with a view to ultimately eliminating its use).
175              
176             =back
177              
178             =head1 SUBROUTINES
179              
180             =over
181              
182             =item available_backends
183              
184             $list_of_backends = Any::Template::available_backends();
185              
186             Scans @INC for a list of modules in the Any::Template::Backend:: namespace.
187              
188             =back
189              
190             =head1 ERROR HANDLING
191              
192             If an error occurs, an exception is raised with die(). You can use an eval block to handle the exception. $@ will contain an error message.
193              
194             =head1 CONSTRUCTOR OPTIONS
195              
196             =over 4
197              
198             =item Backend
199              
200             Backends distributed with this module are listed in the distribution README.
201              
202             See L for information on writing your own backends.
203             You should be able to create a new backend in a couple of dozen lines of code and
204             slot it into the unit test with a one or 2 line change.
205              
206             =item Filename
207              
208             Filename of the template file
209              
210             =item String
211              
212             String containing the template
213              
214             =item Filehandle
215              
216             Reference to a filehandle from which to read the template
217              
218             =item Options
219              
220             A hashref of options passed to the backend templating engine
221              
222             =back
223              
224             =head1 ENVIRONMENT
225              
226             If you don't supply a Backend to the constructor, Any::Template looks for a default Backend in the
227             ANY_TEMPLATE_DEFAULT environment variable. This allows you to retrofit Any::Template into legacy code without
228             hard-coding a default templating language or forcing a backwardly-incompatible change to the interface of the code you are retrofitting into.
229              
230             =head1 CACHING
231              
232             This module doesn't have built-in caching, however the objects it creates are intended to be cachable
233             (where possible the backends hold onto precompiled templates that can be fed any number of data structures).
234             It's therefore up to you what caching strategy you use. In the spirit of "if you liked this, you might also like..."
235             L and L offer a consistent interface to a number of different caching strategies.
236              
237             =head1 SEE ALSO
238              
239             =over 4
240              
241             =item *
242              
243             L
244              
245             =item *
246              
247             L
248              
249             =item *
250              
251             L