File Coverage

lib/Any/Renderer.pm
Criterion Covered Total %
statement 82 82 100.0
branch 21 28 75.0
condition 2 3 66.6
subroutine 12 12 100.0
pod 4 6 66.6
total 121 131 92.3


line stmt bran cond sub pod time code
1             # Purpose : Provide a common interface to a variety of rendering formats
2             # Author : Matt Wilson - Original version by John Alden
3             # Created : March 2006
4             # CVS : $Header: /home/cvs/software/cvsroot/any_renderer/lib/Any/Renderer.pm,v 1.14 2006/09/04 12:15:52 johna Exp $
5              
6             package Any::Renderer;
7              
8 1     1   92572 use strict;
  1         2  
  1         44  
9 1     1   5 use File::Find;
  1         2  
  1         142  
10 1     1   5 use File::Spec;
  1         6  
  1         23  
11              
12 1     1   4 use vars qw($VERSION %Formats @LowPriProviders);
  1         1  
  1         2267  
13             $VERSION = sprintf"%d.%03d", q$Revision: 1.14 $ =~ /: (\d+)\.(\d+)/;
14              
15             #Modules that provide an extensible set of formats that could clash with A::R native providers
16             @LowPriProviders = qw(Data::Serializer);
17              
18             sub new
19             {
20 4     4 1 24922 my ( $class, $format, $options ) = @_;
21 4 100 66     34 die("You must specify a format in the Any::Renderer constructor") unless(defined $format && length $format);
22 3 100       9 $options = {} unless defined $options;
23              
24             #Update the list of formats if the format isn't found to discover any new modules
25 3 100       10 unless($Formats{ $format }) {
26 1         5 my $formats = available_formats();
27 1 50       13 die ( "Unrecognised format - " . $format. ". Known formats are:" . join(" ", @$formats) ) unless $Formats{ $format };
28             }
29              
30 2         10 TRACE ( "Any::Renderer::new w/format '$format'" );
31 2         6 DUMP ( "options", $options );
32              
33 2         8 my $self = {
34             'format' => $format,
35             'options' => $options,
36             };
37            
38 2         6 bless $self, $class;
39              
40 2         6 return $self;
41             }
42              
43             sub render
44             {
45 2     2 1 19 my ( $self, $data ) = @_;
46              
47 2         9 my $format = $self->{ 'format' };
48 2         9 TRACE ( "Rendering to '" . $format . "'" );
49 2         5 DUMP ( "Rendering data", $data );
50            
51             # determine which module we need
52 2         6 my $module = _load_module($Formats { $format });
53 2         13 my $renderer = $module->new ( $format, $self->{ 'options' } );
54              
55 2         8 return $renderer->render ( $data );
56             }
57              
58             sub requires_template
59             {
60             # allow use as either method or function
61 1     1 1 7 my $format = pop;
62 1         2 my $req_template = 0;
63              
64             # Reload the list of formats if we don't already know about it
65 1 50       7 Any::Renderer::available_formats() unless ($Formats { $format });
66 1 50       4 die( "Unable to find any providers for '$format'" ) unless ( $Formats { $format } );
67            
68 1         4 my $module = _load_module($Formats { $format });
69 1 50       12 my $func = $module->can ( "requires_template" )
70             or die ( "${module}::requires_template method could not be found." );
71 1         4 $req_template = &$func ( $format );
72              
73 1         7 TRACE ( "Does '$format' require a template? $req_template" );
74              
75 1         3 return $req_template;
76             }
77              
78             # butchered from Any::Template
79             sub available_formats
80             {
81 2     2 1 977 TRACE ( "Generating list of all possible formats" );
82              
83 2         5 my @possible_locations = grep { -d $_ } map { File::Spec->catdir ( $_, split ( /::/, __PACKAGE__ ) ) } @INC;
  26         583  
  26         161  
84              
85 2         8 my %found;
86             my $collector = sub
87             {
88 88 100   88   2760 return unless $_ =~ /\.pm$/;
89              
90 64         90 my $file = $File::Find::name;
91 64         671 $file =~ s/\Q$File::Find::topdir\E//;
92 64         366 $file =~ s/\.pm$//;
93              
94 64         366 my @dirs = File::Spec->splitdir ( $file );
95 64         86 shift @dirs;
96 64         115 $file = join ( "::", @dirs );
97              
98 64         1592 $found{ $file }=1;
99 2         15 };
100              
101 2         212 File::Find::find ( $collector, @possible_locations );
102              
103             #Ensure that modules adapting other multi-format modules have lower precedence
104             #than native Any::Renderer backends in the event of both offering the same format
105 2         6 my @backends = ();
106 2         14 foreach (@LowPriProviders) {
107 2 50       32 push @backends, $_ if delete $found{$_}; #ensure these are at the front of the list
108             }
109 2         12 push @backends, keys %found; #Higher precendence modules go later in the list
110              
111 2         9 %Formats = (); #Clear and rebuild
112 2         6 foreach my $file ( @backends )
113             {
114             # load the module and discover which formats it presents us with,
115             # including whether the formats need a template
116 16         58 TRACE ( "Testing $file" );
117            
118 16         20 my ($module, $func);
119 16         21 eval {
120 16         40 $module = _load_module($file);
121 2         25 $func = $module->can( "available_formats" );
122             };
123 16 100       1200 warn($@) if($@); #Warn if there are compilation problems with backend modules
124            
125 16 100       60 next unless $func;
126            
127 2         8 my $formats_offered = &$func;
128              
129 2         11 DUMP ( "${module}::available_formats", $formats_offered );
130              
131 2         5 foreach ( @$formats_offered )
132             {
133 2         58 $Formats { $_ } = $file;
134             }
135             }
136              
137 2         40 return [ sort keys %Formats ];
138             }
139              
140             #Loads an Any::Renderer backend (safely)
141             sub _load_module {
142 19     19   25 my $file = shift;
143 19 50       109 die ("Backend module name $file looks dodgy - will not load") unless($file =~ /^[\w:]+$/); #Protect against code injection
144              
145 19         31 my $module = "Any::Renderer::" . $file;
146 19 50       67 unless($INC{"Any/Renderer/$file.pm"}) {
147 19         52 TRACE ( "Loading renderer backend '" . $module . "'" );
148 19         1141 eval "require " . $module;
149 19 100       147 die ("Any::Renderer - problem loading backend module: ". $@ ) if ( $@ );
150             }
151 5         15 return $module;
152             }
153              
154 42     42 0 56 sub TRACE {}
155 6     6 0 9 sub DUMP {}
156              
157             1;
158              
159             =head1 NAME
160              
161             Any::Renderer - Common API for modules that convert data structures into strings
162              
163             =head1 SYNOPSIS
164              
165             $renderer = new Any::Renderer ( $format, \%options );
166             $string = $renderer->render ( $structure );
167             $bool = Any::Renderer::requires_template ( $format );
168             $list_ref = Any::Renderer::available_formats ();
169              
170             =head1 DESCRIPTION
171              
172             A renderer in this context is something that turns a data structure into a
173             string. This includes templating engines, serialisers etc.
174              
175             This module provides a consistent API to these services so that your
176             application can generate output in an extensible variety of formats.
177             Formats currently supported include:
178              
179             - XML (via XML::Simple)
180             - XML+XSLT
181             - Data::Dumper
182             - Javascript, Javascript::Anon & JSON
183             - UrlEncoded
184             - The formats supported by Data::Serializer (e.g. Config::General and Config::Wrest)
185             - Any templating language supported by Any::Template
186              
187             The module will discover any backend modules and offer up their formats.
188             Once loaded, Any::Renderer will look for a module to handle any new formats it doesn't know about, so adding new formats in a persistent environment won't require the module to be reloaded.
189             However if you CHANGE which module provides a format you will need to reload Any::Renderer (e.g. send a SIGHUP to modperl).
190              
191             =head1 METHODS
192              
193             =over 4
194              
195             =item $r = new Any::Renderer($format,\%options)
196              
197             Create a new instance of an Any::Render object using a rendering format of
198             $format and the options listed in the hash %options (see individual rendering
199             module documentation for details of which options various modules accept).
200              
201             =item $string = $r->render($data_structure)
202              
203             Render the data structure $data_structure with the Any::Renderer object $r.
204             The resulting string will be returned.
205              
206             =item $bool = Any::Renderer::requires_template($format)
207              
208             Determine whether or not the rendering format $format requires a template to
209             be passed as an option to the object constructor. If the format does require a
210             template than 1 will be returned, otherwise 0.
211              
212             =item $list_ref = Any::Renderer::available_formats()
213              
214             Discover a list of all known rendering formats that the backend modules
215             provide, e.g. ( 'HTML::Template', 'JavaScript' [, ...]).
216              
217             =back
218              
219             =head1 GLOBAL VARIABLES
220              
221             =over 4
222              
223             =item @Any::Renderer::LowPriProviders
224              
225             A list of backend providers which have lower precedence (if there is more than one module which provides a given format).
226             The earlier things appear in this list, the lower the precedence.
227              
228             Defaults to C as this provides both XML::Simple and Data::Dumper (which have native Any::Renderer backends).
229              
230             =back
231              
232             =head1 WRITING A CUSTOM BACKEND
233              
234             Back-end modules should have the same public interface as Any::Renderer itself:
235              
236             =over 4
237              
238             =item $o = new Any::Renderer::MyBackend($format, \%options);
239              
240             =item $string = $o->render($data_structure);
241              
242             =item $bool = requires_template($format)
243              
244             =item $arrayref = available_formats()
245              
246             =back
247              
248             For example:
249              
250             package Any::Renderer::MyFormat;
251             sub new {
252             my ( $class, $format, $options ) = @_;
253             die("Invalid format $format") unless($format eq 'MyFormat');
254             return bless({}, $class); #More complex classes might stash away options and format
255             }
256            
257             sub render {
258             my ( $self, $data ) = @_;
259             return _my_format($data);
260             }
261            
262             sub requires_template {
263             die("Invalid format") unless($_[0] eq 'MyFormat');
264             return 0; #No template required
265             }
266            
267             sub handle_formats {
268             return [ 'MyFormat' ]; #Just the one
269             }
270              
271             =head1 SEE ALSO
272              
273             =over 4
274              
275             =item All the modules in the Any::Renderer:: distribution
276              
277             L.
278             Each module lists the formats it supports in the FORMATS section.
279             Many of also include sample output fragments.
280              
281             =item L
282              
283             A templating engine is a special case of a renderer (one that uses a template, usually from a file or string, to control formatting).
284             If you are considering exposing another templating language via Any::Renderer, instead consider exposing it via Any::Template.
285             All the templating engines supported by Any::Template are automatically available via Any::Renderer.
286              
287             =item L
288              
289             A serializer is a special case of a renderer which offers bidirectional processing (rendering == serialization, deserialisation does not map to the renderer interface).
290             If you are considering exposing another serialization mechanism via Any::Renderer, instead consider exposing it via Data::Serializer.
291             All the serializers supported by Data::Serializer are automatically available via Any::Renderer.
292              
293             =back
294            
295             =head1 VERSION
296              
297             $Revision: 1.14 $ on $Date: 2006/09/04 12:15:52 $ by $Author: johna $
298              
299             =head1 AUTHOR
300              
301             Matt Wilson (original version by John Alden)
302              
303             =head1 COPYRIGHT
304              
305             (c) BBC 2006. This program is free software; you can redistribute it and/or modify it under the GNU GPL.
306              
307             See the file COPYING in this distribution, or http://www.gnu.org/licenses/gpl.txt
308              
309             =cut