File Coverage

lib/Any/Renderer.pm
Criterion Covered Total %
statement 102 110 92.7
branch 21 28 75.0
condition 2 3 66.6
subroutine 18 20 90.0
pod 6 8 75.0
total 149 169 88.1


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