File Coverage

blib/lib/Data/Xslate.pm
Criterion Covered Total %
statement 114 123 92.6
branch 25 42 59.5
condition 2 4 50.0
subroutine 20 20 100.0
pod 4 5 80.0
total 165 194 85.0


line stmt bran cond sub pod time code
1             package Data::Xslate;
2 4     4   708754 use 5.008001;
  4         24  
3 4     4   17 use strict;
  4         6  
  4         61  
4 4     4   16 use warnings;
  4         5  
  4         354  
5             our $VERSION = '0.08';
6              
7 4     4   2162 use Text::Xslate;
  4         46248  
  4         191  
8 4     4   34 use Carp qw( croak );
  4         8  
  4         284  
9 4     4   2436 use Storable qw( freeze thaw );
  4         10780  
  4         275  
10              
11             # A tied-hash class used to expose the data as the Xslate
12             # vars when processing the data.
13             {
14             package # NO INDEX
15             Data::Xslate::Vars;
16              
17 4     4   28 use base 'Tie::Hash';
  4         9  
  4         12200  
18              
19             sub TIEHASH {
20 458     458   784 my ($class, $sub) = @_;
21 458         1392 return bless {sub=>$sub}, $class;
22             }
23              
24             sub FETCH {
25 7     7   8909 my ($self, $key) = @_;
26              
27 7         42 return $self->{sub}->( $key );
28             }
29             }
30              
31             sub new {
32 153     153 0 329194 my $class = shift;
33              
34 153 50       661 die if @_ % 2 != 0;
35 153         517 my $args = { @_ };
36              
37 153         502 my $defaults = {
38             substitution_tag => '=',
39             nested_key_tag => '=',
40             key_separator => '.',
41             };
42              
43 153         484 my $self = bless { %$defaults }, $class;
44 153         409 foreach my $key (keys %$defaults) {
45 459 100       817 next if ! exists $args->{$key};
46 450         746 $self->{$key} = delete $args->{$key};
47             }
48              
49 153   50     798 my $function = delete( $args->{function} ) || {};
50 153   50     678 $function->{node} ||= \&_find_node_for_xslate;
51 153         737 $self->{_xslate} = Text::Xslate->new(
52             type => 'text',
53             function => $function,
54             %$args,
55             );
56              
57 153         38704 return $self;
58             }
59              
60             # Arguments.
61 458     458 1 853 sub substitution_tag { $_[0]->{substitution_tag} }
62 458     458 1 687 sub nested_key_tag { $_[0]->{nested_key_tag} }
63 458     458 1 677 sub key_separator { $_[0]->{key_separator} }
64              
65             # Attributes.
66 458     458   843 sub _xslate { $_[0]->{_xslate} }
67              
68             # State variables, only used during local() calls to maintain
69             # state in recursive function calls.
70             our $XSLATE;
71             our $VARS;
72             our $ROOT;
73             our $NODES;
74             our $SUBSTITUTION_TAG;
75             our $NESTED_KEY_TAG;
76             our $KEY_SEPARATOR;
77             our $PATH_FOR_XSLATE;
78              
79             sub render {
80 458     458 1 241474 my ($self, $data) = @_;
81              
82 458         1316 $data = thaw( freeze( $data ) );
83              
84 458         23940 local $Carp::Internal{ (__PACKAGE__) } = 1;
85              
86 458         976 local $XSLATE = $self->_xslate();
87              
88 458         569 my %vars;
89 458         1499 tie %vars, 'Data::Xslate::Vars', \&_find_node_for_xslate;
90 458         708 local $VARS = \%vars;
91              
92 458         748 local $ROOT = $data;
93 458         579 local $NODES = {};
94 458         747 local $SUBSTITUTION_TAG = $self->substitution_tag();
95 458         906 local $NESTED_KEY_TAG = $self->nested_key_tag();
96 458         658 local $KEY_SEPARATOR = $self->key_separator();
97              
98 458         805 return _evaluate_node( 'root' => $data );
99             }
100              
101             sub _evaluate_node {
102 2632     2632   3938 my ($path, $node) = @_;
103              
104 2632 100       5966 return $NODES->{$path} if exists $NODES->{$path};
105              
106 1847 100       3333 if (!ref $node) {
    100          
    50          
107 931 50       1438 if (defined $node) {
108 931 100       4957 if ($node =~ m{^\Q$SUBSTITUTION_TAG\E\s*(.+?)\s*$}) {
109 157         327 $node = _find_node( $1, $path );
110             }
111             else {
112 774         1172 local $PATH_FOR_XSLATE = $path;
113 774         3113 $node = $XSLATE->render_string( $node, $VARS );
114             }
115             }
116 931         1162322 $NODES->{$path} = $node;
117             }
118             elsif (ref($node) eq 'HASH') {
119 915         1509 $NODES->{$path} = $node;
120 915         2638 foreach my $key (sort keys %$node) {
121 1386 100       5350 if ($key =~ m{^(.*)\Q$NESTED_KEY_TAG\E$}) {
122 152         524 my $sub_path = "$path$KEY_SEPARATOR$1";
123 152         258 my $value = delete $node->{$key};
124 152         361 _set_node( $sub_path, $value );
125             }
126             else {
127 1234         2123 my $sub_path = "$path$KEY_SEPARATOR$key";
128 1234         1980 $node->{$key} = _evaluate_node( $sub_path, $node->{$key} );
129             }
130             }
131             }
132             elsif (ref($node) eq 'ARRAY') {
133 1         3 $NODES->{$path} = $node;
134             @$node = (
135 1         3 map { _evaluate_node( "$path$KEY_SEPARATOR$_" => $node->[$_] ) }
  3         13  
136             (0..$#$node)
137             );
138             }
139             else {
140 0         0 croak "The config node at $path is neither a hash, array, or scalar";
141             }
142              
143 1847         6135 return $node;
144             }
145              
146             sub _load_node {
147 324     324   539 my ($path) = @_;
148              
149 324         813 my @parts = split(/\Q$KEY_SEPARATOR\E/, $path);
150 324         479 my $built_path = shift( @parts ); # root
151              
152 324         455 my $node = $ROOT;
153 324         578 while (@parts) {
154 640         882 my $key = shift( @parts );
155 640         1022 $built_path .= "$KEY_SEPARATOR$key";
156              
157 640 50       1155 if (ref($node) eq 'HASH') {
    0          
158 640 100       1232 return undef if !exists $node->{$key};
159 633         1307 $node = _evaluate_node( $built_path => $node->{$key} );
160             }
161             elsif (ref($node) eq 'ARRAY') {
162 0 0       0 return undef if $key > $#$node;
163 0         0 $node = _evaluate_node( $built_path => $node->[$key] );
164             }
165             else {
166 0         0 croak "The config node at $path is neither a hash or array";
167             }
168             }
169              
170 317         606 return $node;
171             }
172              
173             sub _find_node {
174 317     317   711 my ($path, $from_path) = @_;
175              
176 317 100       1212 if ($path =~ m{^\Q$KEY_SEPARATOR\E(.+)}) {
177 1         2 $path = $1;
178 1         3 $from_path = "root${KEY_SEPARATOR}root_sub_key_that_is_not_used_for_absolute_keys";
179             }
180              
181 317         1382 my @parts = split(/\Q$KEY_SEPARATOR\E/, $from_path);
182 317         460 pop( @parts );
183              
184 317         745 while (@parts) {
185 324         628 my $sub_path = join($KEY_SEPARATOR, @parts);
186              
187 324         753 my $node = _load_node( "$sub_path$KEY_SEPARATOR$path" );
188 324 100       1278 return $node if $node;
189              
190 7         13 pop( @parts );
191             }
192              
193 0         0 return _load_node( $path );
194             }
195              
196             sub _find_node_for_xslate {
197 160     160   216403 my ($path) = @_;
198 160         478 return _find_node( $path, $PATH_FOR_XSLATE );
199             }
200              
201             sub _set_node {
202 152     152   378 my ($path, $value) = @_;
203              
204 152         567 my @parts = split(/\Q$KEY_SEPARATOR\E/, $path);
205 152         315 my $built_path = shift( @parts ); # root
206 152         256 my $last_part = pop( @parts );
207              
208 152         223 my $node = $ROOT;
209 152         352 while (@parts) {
210 152         226 my $key = shift( @parts );
211 152         354 $built_path .= "$KEY_SEPARATOR$key";
212              
213 152 50       388 if (ref($node) eq 'HASH') {
    0          
214 152 50       335 return 0 if !exists $node->{$key};
215 152         294 $node = _evaluate_node( $built_path => $node->{$key} );
216             }
217             elsif (ref($node) eq 'ARRAY') {
218 0 0       0 return 0 if $key > $#$node;
219 0         0 $node = _evaluate_node( $built_path => $node->[$key] );
220             }
221             else {
222 0         0 croak "The config node at $path is neither a hash or array";
223             }
224             }
225              
226 152         292 delete $NODES->{$path};
227 152         278 $value = _evaluate_node( $path => $value );
228              
229 152 50       415 if (ref($node) eq 'HASH') {
    0          
230 152         294 $node->{$last_part} = $value;
231             }
232             elsif (ref($node) eq 'ARRAY') {
233 0         0 $node->[$last_part] = $value;
234             }
235              
236 152         416 return 1;
237             }
238              
239             1;
240             __END__