File Coverage

blib/lib/Template/Resolver.pm
Criterion Covered Total %
statement 100 108 92.5
branch 28 42 66.6
condition 14 23 60.8
subroutine 17 17 100.0
pod 2 2 100.0
total 161 192 83.8


line stmt bran cond sub pod time code
1 2     2   13075 use strict;
  2         5  
  2         63  
2 2     2   11 use warnings;
  2         5  
  2         111  
3              
4             package Template::Resolver;
5             $Template::Resolver::VERSION = '1.14';
6             # ABSTRACT: A powerful, and simple, library for resolving placeholders in templated files
7             # PODNAME: Template::Resolver
8              
9 2     2   13 use Carp;
  2         5  
  2         132  
10 2     2   13 use Log::Any;
  2         5  
  2         11  
11 2     2   106 use Scalar::Util qw(blessed);
  2         5  
  2         139  
12 2     2   646 use Template::Transformer;
  2         6  
  2         2442  
13              
14             my $logger = Log::Any->get_logger();
15              
16             sub new {
17 11     11 1 16142 return bless( {}, shift )->_init(@_);
18             }
19              
20             sub _entity_to_properties {
21 98     98   153 my ( $entity, $properties, $prefix ) = @_;
22              
23 98 100       207 $properties = {} unless $properties;
24              
25 98         139 my $ref = ref($entity);
26 98 100 100     354 if ( ( $ref && $ref eq 'HASH' ) || blessed($entity) ) {
    100 66        
    50 66        
27 63         77 foreach my $key ( keys( %{$entity} ) ) {
  63         149  
28 81 100       210 _entity_to_properties( $entity->{$key}, $properties,
29             ( $prefix ? "$prefix.$key" : $key ) );
30             }
31             }
32             elsif ( $ref && $ref eq 'ARRAY' ) {
33 3         4 my $index = 0;
34 3         4 foreach my $array_entity ( @{$entity} ) {
  3         6  
35 6 50       23 _entity_to_properties( $array_entity, $properties,
36             ( $prefix ? "$prefix\[$index\]" : "[$index]" ) );
37 6         9 $index++;
38             }
39             }
40             elsif ($ref) {
41 0         0 croak("unsupported ref type '$ref'");
42             }
43             else {
44 32         81 $properties->{$prefix} = $entity;
45             }
46              
47 98         221 return $properties;
48             }
49              
50             sub _get_property {
51 27     27   78 my ( $self, $value, $transform ) = @_;
52 27         90 my $transformed = $self->{transformer}->transform( $value, $transform );
53 27 50       689 croak( "undefined value $value" . ( $transform ? ", using transform $transform" : '' ) )
    100          
54             unless ( defined($transformed) );
55 26         106 return $transformed;
56             }
57              
58             sub _init {
59 11     11   31 my ( $self, $entity, %options ) = @_;
60              
61 11   33     72 my $os = $options{os} || $^O;
62              
63 11         56 $logger->debug('creating new Resolver');
64              
65 11         671 $self->{entity} = $entity;
66             $self->{transformer} = Template::Transformer->new(
67             $os,
68             _entity_to_properties($entity),
69             ( $options{additional_transforms}
70             ? ( additional_transforms => $options{additional_transforms} )
71 11 50       44 : ()
72             )
73             );
74              
75 11         75 return $self;
76             }
77              
78             sub _resolve_loop {
79 9     9   30 my ( $self, $template_key, $loop_name, $property_name, $content ) = @_;
80 9         16 my $property_value = $self->_get_value($property_name);
81 9         12 my $result = '';
82 9         15 my $ref = ref($property_value);
83 9         11 my ( $replacer, $key_match, @keys );
84              
85 9 100 66     37 if ( $ref && $ref eq 'HASH' ) {
    50 33        
    0          
86 6 100   14   18 $replacer = sub { return $_[1] ? $_[0] : "${property_name}.${_[0]}" };
  14         46  
87 6         9 $key_match = "key";
88 6         21 @keys = sort( keys(%$property_value) );
89             }
90             elsif ( $ref && $ref eq 'ARRAY' ) {
91 3 50   14   12 $replacer = sub { return $_[1] ? $_[0] : "${property_name}[${_[0]}]" };
  14         49  
92 3         4 $key_match = "ix";
93 3         11 @keys = keys(@$property_value);
94             }
95             elsif ($ref) {
96 0         0 croak("'$property_name': cannot loop on unsupported ref type '$ref'");
97             }
98             else {
99 0         0 croak("'$property_name': does not exist");
100             }
101              
102             my $resolve_template = sub {
103 40     40   75 my ( $text, $key ) = @_;
104 40 50       90 if ( $text eq "\$\{\Q$template_key\E<\Q$loop_name\E\.\Q$key_match\E\}\}" ) {
105 0         0 $text = $key;
106             }
107             else {
108 40         169 $text =~ s/<\Q$loop_name\E(\.\Q$key_match\E)?>/$replacer->($key,$1)/egs;
  28         44  
109             }
110 40         122 return $text;
111 9         28 };
112              
113 9         16 foreach my $key (@keys) {
114 18         29 my $line = $content;
115 18         106 $line =~ s/\$\{$template_key<\Q$loop_name\E\.\Q$key_match\E>\}/$key/egs;
  20         48  
116 18         71 $line =~ s/(\$\{$template_key.*?\}\})/$resolve_template->($1, $key)/egs;
  40         62  
117 18         43 $result = $result . $line;
118             }
119              
120 9         64 return $result;
121             }
122              
123             sub _resolve_loops {
124 12     12   28 my ( $self, $key, $content ) = @_;
125 12         24 my $done = 0;
126 12         56 while ( !$done ) {
127 18         170 my $converted = $content
128 9         28 =~ s/\$\{$key<(\S+)>:\{(.*?)\}\}(.*?)\$\{$key<\1>:end\}/$self->_resolve_loop($key,$1,$2,$3)/egs;
129 18         53 $done = ( $converted == 0 );
130             }
131 12         27 return $content;
132             }
133              
134             sub _get_value {
135 9     9   18 my ( $self, $key ) = @_;
136 9         13 my $val = $self->{entity};
137 9         24 for my $token ( split( /\./, $key ) ) {
138 19         67 my ( $name, $indices ) = $token =~ /^(\w+)?((?:\[\d+\])*)$/;
139 19 0 33     35 croak("Invalid entity: '$key'") if ( !$name && !$indices );
140 19 50       38 $val = $val->{$name} if ($name);
141 19 100       32 if ($indices) {
142 2         8 for my $index ( split( /\]\[/, substr( $indices, 1, length($indices) - 2 ) ) ) {
143 2         6 $val = $val->[$index];
144             }
145             }
146             }
147 9         15 return $val;
148             }
149              
150             sub resolve {
151 12     12 1 50 my ( $self, %options ) = @_;
152              
153 12   100     36 my $key = $options{key} || 'TEMPLATE';
154              
155 12         17 my $content;
156 12 100       42 if ( $options{content} ) {
    50          
    50          
157 3         4 $content = $options{content};
158             }
159             elsif ( $options{handle} ) {
160 0         0 $content = do { local ($/) = undef; <$options{handle}> };
  0         0  
  0         0  
161             }
162             elsif ( $options{filename} ) {
163 9         10 $content = do { local ( @ARGV, $/ ) = $options{filename}; <> };
  9         52  
  9         675  
164             }
165             else {
166 0         0 croak('Must provide one of [content, handle, filename]');
167             }
168 12         48 $content = $self->_resolve_loops( $key, $content );
169 12         113 $content =~ s/\$\{$key(?:_(.*?))?\{(.*?)\}\}/$self->_get_property($2,$1)/egs;
  27         65  
170 11         104 return $content;
171             }
172              
173             1;
174              
175             __END__