File Coverage

blib/lib/Template/Resolver.pm
Criterion Covered Total %
statement 100 108 92.5
branch 27 42 64.2
condition 14 23 60.8
subroutine 17 17 100.0
pod 2 2 100.0
total 160 192 83.3


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