File Coverage

lib/Log/Report/Template.pm
Criterion Covered Total %
statement 148 158 93.6
branch 39 68 57.3
condition 17 30 56.6
subroutine 30 32 93.7
pod 4 4 100.0
total 238 292 81.5


line stmt bran cond sub pod time code
1             # Copyrights 2017-2018 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Log-Report-Template. Meta-POD processed
6             # with OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Log::Report::Template;
10 7     7   92011 use vars '$VERSION';
  7         15  
  7         316  
11             $VERSION = '0.13';
12              
13 7     7   30 use base 'Template';
  7         10  
  7         2850  
14              
15 7     7   104274 use warnings;
  7         14  
  7         137  
16 7     7   30 use strict;
  7         8  
  7         126  
17              
18 7     7   777 use Log::Report 'log-report-template';
  7         173253  
  7         40  
19 7     7   4315 use Log::Report::Template::Textdomain ();
  7         44  
  7         202  
20             # use Log::Report::Extract::Template on demand
21              
22 7     7   38 use File::Find qw(find);
  7         12  
  7         353  
23 7     7   34 use Scalar::Util qw(blessed);
  7         8  
  7         216  
24 7     7   2932 use Template::Filters ();
  7         21914  
  7         129  
25 7     7   39 use String::Print ();
  7         12  
  7         10192  
26              
27              
28             sub new
29 6     6 1 2791 { my $class = shift;
30 6 50       37 my $self = $class->SUPER::new(@_) or panic $class->error;
31 6         34 $self;
32             }
33              
34             sub _init($)
35 6     6   144 { my ($self, $args) = @_;
36              
37             # Add a filter object we can dynamically add new filters to
38 6         21 my $filters = $self->{LRT_filters} = {};
39              
40 6         10 push @{$args->{LOAD_FILTERS}}
  6         43  
41             , Template::Filters->new({ FILTERS => $filters });
42              
43 6         227 $self->SUPER::_init($args);
44              
45 6   50     80826 my $delim = $self->{LRT_delim} = $args->{DELIMITER} || ':';
46 6   100     25 my $incl = $args->{INCLUDE_PATH} || [];
47 6 100       44 $self->{LRT_path} = ref $incl eq 'ARRAY' ? $incl : [ split $delim, $incl ];
48              
49 6   50     29 my $handle_errors = $args->{processing_errors} || 'NATIVE';
50 6 50       23 if($handle_errors eq 'EXCEPTION') { $self->{LRT_exceptions} = 1 }
  0 50       0  
51             elsif($handle_errors ne 'NATIVE')
52 0         0 { error __x"illegal value '{value}' for 'processing_errors' option"
53             , value => $handle_errors;
54             }
55              
56 6         22 $self->{LRT_formatter} = $self->_createFormatter($args);
57 6         28 $self->_defaultFilters;
58 6         14 $self;
59             }
60              
61             sub _createFormatter($)
62 6     6   14 { my ($self, $args) = @_;
63 6         10 my $formatter = $args->{formatter};
64 6 50       17 return $formatter if ref $formatter eq 'CODE';
65              
66 6   50     24 my $syntax = $args->{template_syntax} || 'HTML';
67 6         16 my $modifiers = $self->_collectModifiers($args);
68              
69 6 50       57 my $sp = String::Print->new
70             ( encode_for => ($syntax eq 'HTML' ? $syntax : undef)
71             , modifiers => $modifiers
72             );
73              
74 6     0   459 sub { $sp->sprinti(@_) };
  0         0  
75             }
76              
77             #---------------
78              
79 0     0 1 0 sub formatter() { shift->{LRT_formatter} }
80              
81             #---------------
82              
83             sub addTextdomain($%) {
84 10     10 1 6051 my ($self, %args) = @_;
85              
86 10 100       30 if(my $only = $args{only_in_directory})
87 3         6 { my $delim = $self->{LRT_delim};
88 3 50       37 $only = $args{only_in_directory} = [ split $delim, $only ]
89             if ref $only ne 'ARRAY';
90              
91 3         9 my @incl = $self->_incl_path;
92 3         6 foreach my $dir (@$only)
93 3 100       14 { next if grep $_ eq $dir, @incl;
94 1         4 error __x"directory {dir} not in INCLUDE_PATH, used by {option}"
95             , dir => $dir, option => 'addTextdomain(only_in_directory)';
96             }
97             }
98              
99 9         16 my $name = $args{name};
100 9 100       27 ! textdomain $name, 'EXISTS'
101             or error __x"textdomain '{name}' already exists", name => $name;
102              
103             my $lexicon = delete $args{lexicon} || delete $args{lexicons}
104 8 100 66     96 or error __x"textdomain '{name}' does not specify the lexicon directory"
105             , name => $name;
106              
107 7 50       39 if(ref $lexicon eq 'ARRAY')
108 0 0       0 { @$lexicon < 2
109             or error __x"textdomain '{name}' has more than one lexicon directory"
110             , name => $name;
111              
112 0 0       0 $lexicon = $lexicon->[0]
113             or error __x"textdomain '{name}' does not specify the lexicon directory"
114             , name => $name;
115             }
116              
117 7 50       102 -d $lexicon
118             or error __x"lexicon directory {dir} for textdomain '{name}' does not exist"
119             , dir => $lexicon, name => $name;
120 7         20 $args{lexicon} = $lexicon;
121              
122 7         77 my $domain = Log::Report::Template::Textdomain->new(%args);
123 7         28 textdomain $domain;
124              
125 7         121 my $func = $domain->function;
126 7 100       18 if((my $other) = grep $func eq $_->function, $self->_domains)
127 1         3 { error __x"translation function '{func}' already in use by textdomain '{name}'"
128             , func => $func, name => $other->name;
129             }
130 6         13 $self->{LRT_domains}{$name} = $domain;
131              
132             # call as function or as filter
133 6         25 $self->_stash->{$func} = $domain->translationFunction($self->service);
134 6         68 $self->_filters->{$func} = [ $domain->translationFilter, 1 ];
135 6         27 $domain;
136             }
137              
138 4     4   4 sub _incl_path() { @{shift->{LRT_path}} }
  4         13  
139 12     12   27 sub _filters() { shift->{LRT_filters} }
140 6     6   16 sub _stash() { shift->service->context->stash }
141 8     8   11 sub _domains() { values %{$_[0]->{LRT_domains} } }
  8         42  
142              
143              
144              
145             sub extract(%)
146 1     1 1 5 { my ($self, %args) = @_;
147              
148 1         61 eval "require Log::Report::Extract::Template";
149 1 50       12859 panic $@ if $@;
150              
151 1   50     4 my $stats = $args{show_stats} || 0;
152 1   50     5 my $charset = $args{charset} || 'UTF-8';
153 1 50       4 my $write = exists $args{write_tables} ? $args{write_tables} : 1;
154              
155 1         2 my @filenames;
156 1 50 33     7 if(my $fns = $args{filenames} || $args{filename})
157 0 0       0 { push @filenames, ref $fns eq 'ARRAY' ? @$fns : $fns;
158             }
159             else
160 1   33     7 { my $match = $args{filename_match} || qr/\.tt2?$/;
161             my $filter = sub {
162 2     2   4 my $name = $File::Find::name;
163 2 100 66     134 push @filenames, $name if -f $name && $name =~ $match;
164 1         4 };
165 1         4 foreach my $dir ($self->_incl_path)
166 1         7 { trace "scan $dir for template files";
167 2     2   6 find { wanted => sub { $filter->($File::Find::name) }
168 1         127 , no_chdir => 1}, $dir;
169             }
170             }
171              
172 1         9 foreach my $domain ($self->_domains)
173 2         814 { my $function = $domain->function;
174 2         7 my $name = $domain->name;
175              
176 2         13 trace "extracting msgids for '$function' from domain '$name'";
177              
178 2         41 my $extr = Log::Report::Extract::Template->new
179             ( lexicon => $domain->lexicon
180             , domain => $name
181             , pattern => "TT2-$function"
182             , charset => $charset
183             );
184              
185 2         97 $extr->process($_) for @filenames;
186              
187 2 50       4380 $extr->showStats if $stats;
188 2 50       56 $extr->write if $write;
189             }
190             }
191              
192             #------------
193              
194             sub _cols_factory(@)
195 6     6   32625 { my $self = shift;
196 6 50       18 my $params = ref $_[-1] eq 'HASH' ? pop : undef;
197 6 100       16 my @blocks = @_ ? @_ : 'td';
198 6 100 100     29 if(@blocks==1 && $blocks[0] =~ /\$[1-9]/)
199 2         4 { my $pattern = shift @blocks;
200             return sub { # second syntax
201 2     2   34 my @cols = split /\t/, $_[0];
202 2 50       10 $pattern =~ s/\$([0-9]+)/$cols[$1-1] || ''/ge;
  3         15  
203 2         9 $pattern;
204             }
205 2         11 }
206              
207             sub { # first syntax
208 4     4   67 my @cols = split /\t/, $_[0];
209 4         9 my @wrap = @blocks;
210 4         5 my @out;
211 4         8 while(@cols)
212 8         19 { push @out, "<$wrap[0]>$cols[0]";
213 8         9 shift @cols;
214 8 100       19 shift @wrap if @wrap > 1;
215             }
216 4         26 join '', @out;
217             }
218 4         21 }
219              
220              
221             sub _br_factory(@)
222 3     3   5578 { my $self = shift;
223 3 50       8 my $params = ref $_[-1] eq 'HASH' ? pop : undef;
224             return sub {
225 3 50   3   49 my $templ = shift or return '';
226 3         5 for($templ)
227 3         11 { s/\A[\s\n]*\n//; # leading blank lines
228 3         11 s/\n[\s\n]*\n/\n/g; # double blank links
229 3         11 s/\n[\s\n]*\z/\n/; # trailing blank lines
230 3         10 s/\s*\n/
\n/gm; # trailing blanks per line
231             }
232 3         14 $templ;
233             }
234 3         13 }
235              
236             sub _defaultFilters()
237 6     6   9 { my $self = shift;
238 6         15 my $filter = $self->_filters;
239 6         14 $filter->{cols} = [ \&_cols_factory, 1 ];
240 6         15 $filter->{br} = [ \&_br_factory, 1 ];
241 6         7 $filter;
242             }
243              
244             #------------
245              
246              
247             sub _collectModifiers($)
248 6     6   12 { my ($self, $args) = @_;
249              
250             # First match will be used
251 6 50       10 my @modifiers = @{$args->{modifiers} || []};
  6         33  
252              
253             # More default extensions expected here. String::Print already
254             # adds a bunch.
255              
256 6         14 \@modifiers;
257             }
258              
259             #------------
260              
261              
262             { # Log::Report exports 'error', and we use that. Our base-class
263             # 'Template' however, also has a method named error() as well.
264             # Gladly, they can easily be separated.
265              
266             # no warnings 'redefined' misbehaves, at least for perl 5.16.2
267 7     7   47 no warnings;
  7         16  
  7         859  
268              
269             sub error()
270             {
271 4 50 33 4   190 return Log::Report::error(@_)
272             unless blessed $_[0] && $_[0]->isa('Template');
273              
274             return shift->SUPER::error(@_)
275 0 0         unless $_[0]->{LRT_exceptions};
276              
277 0 0         @_ or panic "inexpected call to collect errors()";
278              
279             # convert Template errors into Log::Report errors
280 0           Log::Report::error($_[1]);
281             }
282             }
283              
284              
285             #------------
286              
287             1;