File Coverage

blib/lib/Template/Plugin/StringTree.pm
Criterion Covered Total %
statement 89 97 91.7
branch 40 58 68.9
condition 7 15 46.6
subroutine 15 15 100.0
pod 7 9 77.7
total 158 194 81.4


line stmt bran cond sub pod time code
1             package Template::Plugin::StringTree;
2              
3             =pod
4              
5             =head1 NAME
6              
7             Template::Plugin::StringTree - Access tree-like groups of strings naturally in code and Templates
8              
9             =head1 SYNOPSIS
10              
11             use Template::Plugin::StringTree;
12            
13             # Create a StringTree and set some values
14             my $Tree = Template::Plugin::StringTree->new;
15             $Tree->set('foo', 'one');
16             $Tree->set('foo.bar', 'two');
17             $Tree->set('you.get.the.point' => 'right?');
18            
19             # Get simple hash of these variables for the template
20             my $vars = $Tree->variables;
21            
22             #######################################################
23             # Later that night in a Template
24            
25             After the number [% foo %] comes the number [% foo.bar %], [% you.get.the.point %]
26            
27             #######################################################
28             # Which of course produces
29            
30             After the number one comes the number two, right?
31              
32             =head1 DESCRIPTION
33              
34             For a couple of months, I had found it really annoying that when I wanted
35             to put a bunch of configuration options into a template, that I couldn't
36             use a natural [% IF show.pictures %][% IF show.pictures.dropshadow %] ...etc...
37             type of notation. Simply, to get "dot" formatting in template, you need
38             hashes. Which means stupid notation like [% show.pictures.at_all %]. ugh...
39              
40             As the size of the config tree I wanted to use grew and grew, it finally
41             started getting totally out of control, so I've created
42             Template::Plugin::StringTree, which lets you build tree structures in which
43             every node can have a value. And you can get at these naturally in templates.
44              
45             =head1 METHODS
46              
47             =cut
48              
49 2     2   23415 use 5.005;
  2         8  
  2         71  
50 2     2   11 use strict;
  2         4  
  2         61  
51 2     2   1173 use Template::Plugin::StringTree::Node ();
  2         5  
  2         41  
52              
53 2     2   17 use vars qw{$VERSION};
  2         3  
  2         76  
54             BEGIN {
55 2     2   4123 $VERSION = '0.08';
56             }
57              
58              
59              
60              
61              
62             #####################################################################
63             # Constructor
64              
65             =pod
66              
67             =head2 new
68              
69             The C constructor simply creates a new ::StringTree object and
70             returns it.
71              
72             =cut
73              
74             sub new {
75 5   33 5 1 45 bless {}, ref($_[0]) || $_[0];
76             }
77              
78             sub clone {
79 1 50   1 0 5 my $self = ref $_[0] ? shift : return undef;
80 1         3 ref($self)->thaw( $self->freeze );
81             }
82              
83              
84              
85              
86              
87             #####################################################################
88             # Main Methods
89              
90             =pod
91              
92             =head2 get $path
93              
94             Taking a single "this.is.a.path" argument, the C method returns the
95             value associated with the path, if there is one.
96              
97             Returns the value for the path, if one exists. Returns C if no value
98             exists at that path.
99              
100             =cut
101              
102             sub get {
103 19     19 1 24 my $self = shift;
104 19 50       38 my $path = $self->_path($_[0]) or return undef;
105              
106             # Walk the tree to find the value
107 19         22 my $cursor = $self;
108 19         29 foreach my $branch ( @$path ) {
109 49 50       108 return undef unless ref $cursor; # Last branch took us to a normal value
110 49 100       118 defined($cursor = $cursor->{$branch}) or return undef;
111             }
112              
113             # We have arrived at the value we want.
114 16 100       65 ref $cursor ? $cursor->__get : $cursor;
115             }
116              
117             =pod
118              
119             =head2 set $path, $value
120              
121             The C method takes a "this.is.a.path" style path and a value for that
122             path. C is valid as a value, erasing a single value at the node for
123             the path. ( It does not remove children of that node ).
124              
125             Returns true if the value is set correctly, or C on error.
126              
127             =cut
128              
129             sub set {
130 15     15 1 25 my $self = shift;
131 15 50       31 my $path = $self->_path(shift) or return undef;
132 15         19 my $value = shift;
133              
134             # Walk the tree to determine the location to set
135 15         19 my $cursor = $self;
136 15         23 my $leaf = pop @$path;
137 15         27 foreach my $branch ( @$path ) {
138 32 100       73 if ( ! defined $cursor->{$branch} ) {
    100          
139             # Create a new node for the branch
140 20         59 $cursor->{$branch} = Template::Plugin::StringTree::Node->__new;
141             } elsif ( ! ref $cursor->{$branch} ) {
142             # Convert the existing leaf into a node
143 6         28 $cursor->{$branch} = Template::Plugin::StringTree::Node->__new( $cursor->{$branch} );
144             }
145              
146             # Move down into the node
147 32         82 $cursor = $cursor->{$branch};
148             }
149              
150             # Now set the leaf
151 15 50 33     95 if ( exists $cursor->{$leaf} and ref $cursor->{$leaf} ) {
152             # Replace the node's value
153 0         0 $cursor->{$leaf}->__set($value);
154             } else {
155             # Create or replace a leaf
156 15         27 $cursor->{$leaf} = $value;
157             }
158              
159 15         57 1;
160             }
161              
162             =pod
163              
164             The C method is nearly identical to the normal C method,
165             except that the it expects there B to be an existing value in place.
166             Rather than overwrite an existing value, this method will return an error.
167              
168             Returns true if there is no existing value, and it is successfully set,
169             or C if there is an existing value, or an error while setting.
170              
171             =cut
172              
173             sub add {
174 2     2 0 3 my $self = shift;
175 2 50       4 my $path = $self->_path(shift) or return undef;
176 2         2 my $value = shift;
177              
178             # Walk the tree to determine the location to set
179 2         3 my $cursor = $self;
180 2         4 my $leaf = pop @$path;
181 2         3 foreach my $branch ( @$path ) {
182 3 50       14 if ( ! defined $cursor->{$branch} ) {
    50          
183             # Create a new node for the branch
184 0         0 $cursor->{$branch} = Template::Plugin::StringTree::Node->__new;
185             } elsif ( ! ref $cursor->{$branch} ) {
186             # Convert the existing leaf into a node
187 0         0 $cursor->{$branch} = Template::Plugin::StringTree::Node->__new( $cursor->{$branch} );
188             }
189              
190             # Move down into the node
191 3         6 $cursor = $cursor->{$branch};
192             }
193              
194             # Now set the leaf
195 2 100 66     11 if ( exists $cursor->{$leaf} and ref $cursor->{$leaf} ) {
196             # Fail if there is an existing value
197 1 50       4 return undef if defined $cursor->{$leaf}->__get($value);
198              
199             # Replace the node's value
200 1         5 $cursor->{$leaf}->__set($value);
201             } else {
202             # Fail if there is an existing value
203 1 50       8 return undef if defined $cursor->{$leaf};
204              
205             # Create or replace a leaf
206 0         0 $cursor->{$leaf} = $value;
207             }
208              
209 1         8 1;
210             }
211              
212              
213             =pod
214              
215             =head2 hash
216              
217             The C method produces a flat hash equivalent to the
218             Template::Plugin::StringTree object, which can be passed to the template
219             parser. You can manually add additional elements to the hash after it has
220             been produced, but you should not attempt to add anything to a hash key
221             the same as the first element in a path already added via the C
222             method earlier.
223              
224             Returns a reference to a HASH containing the tree of strings.
225              
226             =cut
227              
228 3     3 1 4 sub hash { my $hash = { %{$_[0]} }; $hash }
  3         9  
  3         11  
229              
230             =pod
231              
232             =head2 freeze
233              
234             Ever good structure can be serialized and deserialized, and this one is
235             no exception. The C method takes a ::StringTree object and converts
236             it into a string, which just so happens to be highly useful as a config
237             file format!
238              
239             foo: one
240             foo.bar: two
241             you.get.the.point: right?
242              
243             So terribly simple. To make life just a LITTLE more complicated though,
244             Template::Plugin::StringTree does a little bit of escaping if there's a
245             newline in the string. But since you'll probably never DO that, it won't
246             be a problem will it? :)
247              
248             =cut
249              
250             sub freeze {
251 6     6 1 806 my $self = shift;
252              
253             # Handle the special null case
254 6 100       29 return 'null' unless keys %$self;
255              
256             # Flatten and escape the tree
257 5         9 my %flat = ();
258 5         12 my @queue = ( [ '', $self ] );
259 5         19 while ( my $item = shift @queue ) {
260 40         46 my $base = $item->[0];
261 40         46 my $cursor = $item->[1];
262              
263 40         112 foreach my $key ( keys %$cursor ) {
264 45 100       82 my $path = length $base ? "$base.$key" : $key;
265 45 100       135 my $value = (ref $cursor->{$key})
266             ? $cursor->{$key}->__get
267             : $cursor->{$key};
268 45 100       74 if ( defined $value ) {
269             # Escape and add the value to the output
270 20         25 $value =~ s/([\\\n])/sprintf('\\%03d', ord($1))/ge;
  0         0  
271 20         38 $flat{$path} = $value;
272             }
273 45 100       244 push @queue, [ $path, $cursor->{$key} ] if ref $cursor->{$key};
274             }
275             }
276              
277             # Now convert the flattened tree to a single string
278 5         19 join '', map { "$_: $flat{$_}\n" } sort keys %flat;
  20         55  
279             }
280              
281             =pod
282              
283             =head2 thaw $string
284              
285             The C method is the reverse of the C method, taking the same
286             format string turning it back into a Template::Plugin::StringTree object.
287             THIS is where using this module as a config file -> template mechanism
288             really comes into it's own. Each entry is the config file is available
289             using the same path in Template Toolkit templates.
290             Template::Plugin::StringTree takes care of all the details or making it work
291             across the different models transparently.
292              
293             If the string is formatted correctly, returns a new
294             Template::Plugin::StringTree object. Returns C on error, probably
295             because the string wasn't formatted correctly.
296              
297             =cut
298              
299             sub thaw {
300 2 50   2 1 6 my $class = ref $_[0] ? ref shift : shift;
301 2 50       8 my $string = shift or return undef;
302 2         7 my $self = $class->new;
303              
304             # Handle the special case
305 2 50       8 return $self if $string eq 'null';
306              
307 2         13 foreach ( split /\n/, $string ) {
308 8 50       53 return undef unless /^([\w\.]+)\:\s*(.*)$/;
309 8         17 my $key = $1;
310 8         10 my $value = $2;
311              
312             # Unescape the value
313 8         9 $value =~ s/\\(\d\d\d)/chr($1)/ge;
  0         0  
314 8 50       18 $self->set($key, $value) or return undef;
315             }
316              
317 2         8 $self;
318             }
319              
320             =pod
321              
322             =head2 equal $path, $value
323              
324             The C method provides a quick and convenient bit of shorthand to
325             let you see if a particular path equals a particular value. And the
326             method is totally undef-safe. You can test for a value of C,
327             and test a value against a path which returns C quite safely.
328              
329             Returns true if the value matches the path, or false otherwise.
330              
331             =cut
332              
333             sub equal {
334 6     6 1 7 my $self = shift;
335 6         11 my $left = $self->get(shift);
336 6         7 my $right = shift;
337 6 100 66     35 defined $left ? (defined($right) and $left eq $right) : ! defined $right;
338             }
339              
340              
341              
342              
343              
344             #####################################################################
345             # Support Methods
346              
347             sub _path {
348             # Check the value before we begin processing it
349 38 50 33 38   177 my $value = (defined $_[1] and ! ref $_[1]) ? $_[1] : return undef;
350 38 50       165 $value =~ /^[^\W\d]\w*(?:\.[^\W\d]\w*)*$/ or return undef;
351              
352             # Split the path
353 38         113 my @path = split /\./, $value;
354 38 50       55 if ( grep { $_ eq 'DESTROY' } @path ) {
  105         198  
355             # Illegal value, clashes with the Node DESTROY method
356 0         0 warn "The use of 'DESTROY' as a path node is forbidden";
357 0         0 return undef;
358             }
359              
360 38         122 \@path;
361             }
362              
363             1;
364              
365             =pod
366              
367             =head1 SUPPORT
368              
369             Bugs should be submitted via the CPAN bug tracker, located at
370              
371             http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Template-Plugin-StringTree
372              
373             For other issues, contact the author
374              
375             =head1 AUTHOR
376              
377             Adam Kennedy Eadamk@cpan.orgE
378              
379             =head1 COPYRIGHT
380              
381             Copyright 2004, 2008 Adam Kennedy.
382              
383             This program is free software; you can redistribute
384             it and/or modify it under the same terms as Perl itself.
385              
386             The full text of the license can be found in the
387             LICENSE file included with this module.
388              
389             =cut