| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Data::Xslate; | 
| 2 | 4 |  |  | 4 |  | 793854 | use 5.008001; | 
|  | 4 |  |  |  |  | 26 |  | 
| 3 | 4 |  |  | 4 |  | 18 | use strict; | 
|  | 4 |  |  |  |  | 5 |  | 
|  | 4 |  |  |  |  | 73 |  | 
| 4 | 4 |  |  | 4 |  | 16 | use warnings; | 
|  | 4 |  |  |  |  | 6 |  | 
|  | 4 |  |  |  |  | 136 |  | 
| 5 |  |  |  |  |  |  | our $VERSION = '0.09'; | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 4 |  |  | 4 |  | 2025 | use Text::Xslate; | 
|  | 4 |  |  |  |  | 43825 |  | 
|  | 4 |  |  |  |  | 173 |  | 
| 8 | 4 |  |  | 4 |  | 26 | use Carp qw( croak ); | 
|  | 4 |  |  |  |  | 7 |  | 
|  | 4 |  |  |  |  | 165 |  | 
| 9 | 4 |  |  | 4 |  | 2239 | use Storable qw( freeze thaw ); | 
|  | 4 |  |  |  |  | 10929 |  | 
|  | 4 |  |  |  |  | 260 |  | 
| 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 |  | 27 | use base 'Tie::Hash'; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 5449 |  | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | sub TIEHASH { | 
| 20 | 458 |  |  | 458 |  | 732 | my ($class, $sub) = @_; | 
| 21 | 458 |  |  |  |  | 1304 | return bless {sub=>$sub}, $class; | 
| 22 |  |  |  |  |  |  | } | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | sub FETCH { | 
| 25 | 7 |  |  | 7 |  | 6932 | my ($self, $key) = @_; | 
| 26 |  |  |  |  |  |  |  | 
| 27 | 7 |  |  |  |  | 30 | return $self->{sub}->( $key ); | 
| 28 |  |  |  |  |  |  | } | 
| 29 |  |  |  |  |  |  | } | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | sub new { | 
| 32 | 153 |  |  | 153 | 0 | 309036 | my $class = shift; | 
| 33 |  |  |  |  |  |  |  | 
| 34 | 153 | 50 |  |  |  | 461 | die if @_ % 2 != 0; | 
| 35 | 153 |  |  |  |  | 426 | my $args = { @_ }; | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 153 |  |  |  |  | 452 | my $defaults = { | 
| 38 |  |  |  |  |  |  | substitution_tag => '=', | 
| 39 |  |  |  |  |  |  | nested_key_tag   => '=', | 
| 40 |  |  |  |  |  |  | key_separator    => '.', | 
| 41 |  |  |  |  |  |  | }; | 
| 42 |  |  |  |  |  |  |  | 
| 43 | 153 |  |  |  |  | 473 | my $self = bless { %$defaults }, $class; | 
| 44 | 153 |  |  |  |  | 399 | foreach my $key (keys %$defaults) { | 
| 45 | 459 | 100 |  |  |  | 683 | next if ! exists $args->{$key}; | 
| 46 | 450 |  |  |  |  | 688 | $self->{$key} = delete $args->{$key}; | 
| 47 |  |  |  |  |  |  | } | 
| 48 |  |  |  |  |  |  |  | 
| 49 | 153 |  | 50 |  |  | 560 | my $function = delete( $args->{function} ) || {}; | 
| 50 | 153 |  | 50 |  |  | 576 | $function->{node} ||= \&_find_node_for_xslate; | 
| 51 | 153 |  |  |  |  | 626 | $self->{_xslate} = Text::Xslate->new( | 
| 52 |  |  |  |  |  |  | type     => 'text', | 
| 53 |  |  |  |  |  |  | function => $function, | 
| 54 |  |  |  |  |  |  | %$args, | 
| 55 |  |  |  |  |  |  | ); | 
| 56 |  |  |  |  |  |  |  | 
| 57 | 153 |  |  |  |  | 36265 | return $self; | 
| 58 |  |  |  |  |  |  | } | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | # Arguments. | 
| 61 | 458 |  |  | 458 | 1 | 714 | sub substitution_tag { $_[0]->{substitution_tag} } | 
| 62 | 458 |  |  | 458 | 1 | 666 | sub nested_key_tag { $_[0]->{nested_key_tag} } | 
| 63 | 458 |  |  | 458 | 1 | 635 | sub key_separator { $_[0]->{key_separator} } | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | # Attributes. | 
| 66 | 458 |  |  | 458 |  | 808 | 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 | 235719 | my ($self, $data) = @_; | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 458 |  |  |  |  | 1106 | $data = thaw( freeze( $data ) ); | 
| 83 |  |  |  |  |  |  |  | 
| 84 | 458 |  |  |  |  | 22604 | local $Carp::Internal{ (__PACKAGE__) } = 1; | 
| 85 |  |  |  |  |  |  |  | 
| 86 | 458 |  |  |  |  | 842 | local $XSLATE = $self->_xslate(); | 
| 87 |  |  |  |  |  |  |  | 
| 88 | 458 |  |  |  |  | 589 | my %vars; | 
| 89 | 458 |  |  |  |  | 1494 | tie %vars, 'Data::Xslate::Vars', \&_find_node_for_xslate; | 
| 90 | 458 |  |  |  |  | 654 | local $VARS = \%vars; | 
| 91 |  |  |  |  |  |  |  | 
| 92 | 458 |  |  |  |  | 576 | local $ROOT = $data; | 
| 93 | 458 |  |  |  |  | 572 | local $NODES = {}; | 
| 94 | 458 |  |  |  |  | 705 | local $SUBSTITUTION_TAG = $self->substitution_tag(); | 
| 95 | 458 |  |  |  |  | 745 | local $NESTED_KEY_TAG = $self->nested_key_tag(); | 
| 96 | 458 |  |  |  |  | 644 | local $KEY_SEPARATOR = $self->key_separator(); | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 458 |  |  |  |  | 752 | return _evaluate_node( 'root' => $data ); | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | sub _evaluate_node { | 
| 102 | 2632 |  |  | 2632 |  | 3885 | my ($path, $node) = @_; | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 2632 | 100 |  |  |  | 5647 | return $NODES->{$path} if exists $NODES->{$path}; | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 1847 | 100 |  |  |  | 3374 | if (!ref $node) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 107 | 931 | 50 |  |  |  | 1616 | if (defined $node) { | 
| 108 | 931 | 100 |  |  |  | 4618 | if ($node =~ m{^\Q$SUBSTITUTION_TAG\E\s*(.+?)\s*$}) { | 
| 109 | 157 |  |  |  |  | 306 | $node = _find_node( $1, $path ); | 
| 110 |  |  |  |  |  |  | } | 
| 111 |  |  |  |  |  |  | else { | 
| 112 | 774 |  |  |  |  | 1133 | local $PATH_FOR_XSLATE = $path; | 
| 113 | 774 |  |  |  |  | 3053 | $node = $XSLATE->render_string( $node, $VARS ); | 
| 114 |  |  |  |  |  |  | } | 
| 115 |  |  |  |  |  |  | } | 
| 116 | 931 |  |  |  |  | 1178929 | $NODES->{$path} = $node; | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  | elsif (ref($node) eq 'HASH') { | 
| 119 | 915 |  |  |  |  | 1484 | $NODES->{$path} = $node; | 
| 120 | 915 |  |  |  |  | 2530 | foreach my $key (sort keys %$node) { | 
| 121 | 1386 | 100 |  |  |  | 5243 | if ($key =~ m{^(.*)\Q$NESTED_KEY_TAG\E$}) { | 
| 122 | 152 |  |  |  |  | 433 | my $sub_path = "$path$KEY_SEPARATOR$1"; | 
| 123 | 152 |  |  |  |  | 306 | my $value = delete $node->{$key}; | 
| 124 | 152 |  |  |  |  | 310 | _set_node( $sub_path, $value ); | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  | else { | 
| 127 | 1234 |  |  |  |  | 2229 | my $sub_path = "$path$KEY_SEPARATOR$key"; | 
| 128 | 1234 |  |  |  |  | 2001 | $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 |  |  |  |  | 4 | map { _evaluate_node( "$path$KEY_SEPARATOR$_" => $node->[$_] ) } | 
|  | 3 |  |  |  |  | 16 |  | 
| 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 |  |  |  |  | 5798 | return $node; | 
| 144 |  |  |  |  |  |  | } | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | sub _load_node { | 
| 147 | 324 |  |  | 324 |  | 503 | my ($path) = @_; | 
| 148 |  |  |  |  |  |  |  | 
| 149 | 324 |  |  |  |  | 762 | my @parts = split(/\Q$KEY_SEPARATOR\E/, $path); | 
| 150 | 324 |  |  |  |  | 526 | my $built_path = shift( @parts ); # root | 
| 151 |  |  |  |  |  |  |  | 
| 152 | 324 |  |  |  |  | 450 | my $node = $ROOT; | 
| 153 | 324 |  |  |  |  | 496 | while (@parts) { | 
| 154 | 640 |  |  |  |  | 814 | my $key = shift( @parts ); | 
| 155 | 640 |  |  |  |  | 895 | $built_path .= "$KEY_SEPARATOR$key"; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 640 | 50 |  |  |  | 1046 | if (ref($node) eq 'HASH') { | 
|  |  | 0 |  |  |  |  |  | 
| 158 | 640 | 100 |  |  |  | 1090 | return undef if !exists $node->{$key}; | 
| 159 | 633 |  |  |  |  | 959 | $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 |  |  |  |  | 581 | return $node; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | sub _find_node { | 
| 174 | 317 |  |  | 317 |  | 601 | my ($path, $from_path) = @_; | 
| 175 |  |  |  |  |  |  |  | 
| 176 | 317 | 100 |  |  |  | 1126 | if ($path =~ m{^\Q$KEY_SEPARATOR\E(.+)}) { | 
| 177 | 1 |  |  |  |  | 3 | $path = $1; | 
| 178 | 1 |  |  |  |  | 3 | $from_path = "root${KEY_SEPARATOR}root_sub_key_that_is_not_used_for_absolute_keys"; | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  |  | 
| 181 | 317 |  |  |  |  | 1047 | my @parts = split(/\Q$KEY_SEPARATOR\E/, $from_path); | 
| 182 | 317 |  |  |  |  | 475 | pop( @parts ); | 
| 183 |  |  |  |  |  |  |  | 
| 184 | 317 |  |  |  |  | 677 | while (@parts) { | 
| 185 | 324 |  |  |  |  | 671 | my $sub_path = join($KEY_SEPARATOR, @parts); | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 324 |  |  |  |  | 751 | my $node = _load_node( "$sub_path$KEY_SEPARATOR$path" ); | 
| 188 | 324 | 100 |  |  |  | 1203 | return $node if $node; | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 7 |  |  |  |  | 12 | pop( @parts ); | 
| 191 |  |  |  |  |  |  | } | 
| 192 |  |  |  |  |  |  |  | 
| 193 | 0 |  |  |  |  | 0 | return _load_node( $path ); | 
| 194 |  |  |  |  |  |  | } | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub _find_node_for_xslate { | 
| 197 | 160 |  |  | 160 |  | 210823 | my ($path) = @_; | 
| 198 | 160 |  |  |  |  | 341 | return _find_node( $path, $PATH_FOR_XSLATE ); | 
| 199 |  |  |  |  |  |  | } | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | sub _set_node { | 
| 202 | 152 |  |  | 152 |  | 279 | my ($path, $value) = @_; | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 152 |  |  |  |  | 537 | my @parts = split(/\Q$KEY_SEPARATOR\E/, $path); | 
| 205 | 152 |  |  |  |  | 280 | my $built_path = shift( @parts ); # root | 
| 206 | 152 |  |  |  |  | 200 | my $last_part = pop( @parts ); | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 152 |  |  |  |  | 199 | my $node = $ROOT; | 
| 209 | 152 |  |  |  |  | 303 | while (@parts) { | 
| 210 | 152 |  |  |  |  | 220 | my $key = shift( @parts ); | 
| 211 | 152 |  |  |  |  | 243 | $built_path .= "$KEY_SEPARATOR$key"; | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 152 | 50 |  |  |  | 304 | if (ref($node) eq 'HASH') { | 
|  |  | 0 |  |  |  |  |  | 
| 214 | 152 | 50 |  |  |  | 285 | return 0 if !exists $node->{$key}; | 
| 215 | 152 |  |  |  |  | 298 | $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 |  |  |  |  | 277 | delete $NODES->{$path}; | 
| 227 | 152 |  |  |  |  | 229 | $value = _evaluate_node( $path => $value ); | 
| 228 |  |  |  |  |  |  |  | 
| 229 | 152 | 50 |  |  |  | 336 | if (ref($node) eq 'HASH') { | 
|  |  | 0 |  |  |  |  |  | 
| 230 | 152 |  |  |  |  | 240 | $node->{$last_part} = $value; | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  | elsif (ref($node) eq 'ARRAY') { | 
| 233 | 0 |  |  |  |  | 0 | $node->[$last_part] = $value; | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 | 152 |  |  |  |  | 366 | return 1; | 
| 237 |  |  |  |  |  |  | } | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | 1; | 
| 240 |  |  |  |  |  |  | __END__ |