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