File Coverage

lib/Log/Report/Template.pm
Criterion Covered Total %
statement 150 160 93.7
branch 39 68 57.3
condition 17 30 56.6
subroutine 31 33 93.9
pod 4 4 100.0
total 241 295 81.6


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