File Coverage

lib/Log/Report/Template.pm
Criterion Covered Total %
statement 147 161 91.3
branch 36 66 54.5
condition 18 35 51.4
subroutine 29 32 90.6
pod 7 7 100.0
total 237 301 78.7


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Log-Report-Template version 1.04.
2             # The POD got stripped from this file by OODoc version 3.05.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2017-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11             #oodist: *** DO NOT USE THIS VERSION FOR PRODUCTION ***
12             #oodist: This file contains OODoc-style documentation which will get stripped
13             #oodist: during its release in the distribution. You can use this file for
14             #oodist: testing, however the code of this development version may be broken!
15              
16             package Log::Report::Template;{
17             our $VERSION = '1.04';
18             }
19              
20 7     7   2957534 use base 'Template';
  7         21  
  7         4018  
21              
22 7     7   148675 use warnings;
  7         27  
  7         486  
23 7     7   56 use strict;
  7         14  
  7         220  
24              
25 7     7   1200 use Log::Report 'log-report-template';
  7         235129  
  7         86  
26 7     7   7438 use Log::Report::Template::Textdomain ();
  7         23  
  7         319  
27             # use Log::Report::Template::Extract on demand.
28              
29 7     7   52 use File::Find qw/find/;
  7         16  
  7         554  
30 7     7   47 use Scalar::Util qw/blessed/;
  7         14  
  7         347  
31 7     7   4423 use Template::Filters ();
  7         33631  
  7         248  
32 7     7   88 use String::Print ();
  7         28  
  7         16860  
33              
34              
35             sub new
36 6     6 1 4685 { my $class = shift;
37              
38             # Template::Base gladly also calls _init() !!
39 6 50       89 my $self = $class->SUPER::new(@_) or panic $class->error;
40 6         63 $self;
41             }
42              
43             sub _init($)
44 6     6   190 { my ($self, $args) = @_;
45              
46 6 50       30 if(ref $self eq __PACKAGE__)
47             { # Instantiated directly
48 6         41 $self->SUPER::_init($args);
49             }
50             else
51             { # Upgrade from existing Template object
52 0         0 bless $self, __PACKAGE__;
53             }
54              
55 6   50     132417 my $delim = $self->{LRT_delim} = $args->{DELIMITER} || ':';
56 6   100     41 my $incl = $args->{INCLUDE_PATH} || [];
57 6 100       68 $self->{LRT_path} = ref $incl eq 'ARRAY' ? $incl : [ split $delim, $incl ];
58              
59 6   50     69 my $handle_errors = $args->{processing_errors} || 'NATIVE';
60 6 50       41 if($handle_errors eq 'EXCEPTION') { $self->{LRT_exceptions} = 1 }
  0 50       0  
61             elsif($handle_errors ne 'NATIVE')
62 0         0 { error __x"illegal value '{value}' for 'processing_errors' option", value => $handle_errors;
63             }
64              
65 6         35 $self->{LRT_formatter} = $self->_createFormatter($args);
66 6         24 $self->{LRT_trTo} = $args->{translate_to};
67 6   50     64 $self->{LRT_tdc} = $args->{textdomain_class} || 'Log::Report::Template::Textdomain';
68 6         31 $self->_defaultFilters;
69 6         45 $self;
70             }
71              
72             sub _createFormatter($)
73 6     6   20 { my ($self, $args) = @_;
74 6         17 my $formatter = $args->{formatter};
75 6 50       61 return $formatter if ref $formatter eq 'CODE';
76              
77 6   50     42 my $syntax = $args->{template_syntax} || 'HTML';
78 6         34 my $modifiers = $self->_collectModifiers($args);
79              
80 6 50       87 my $sp = String::Print->new(
81             encode_for => ($syntax eq 'HTML' ? $syntax : undef),
82             modifiers => $modifiers,
83             );
84              
85 6     0   690 sub { $sp->sprinti(@_) };
  0         0  
86             }
87              
88             #--------------------
89              
90 0     0 1 0 sub formatter() { $_[0]->{LRT_formatter} }
91              
92              
93             sub translateTo(;$)
94 8     8 1 16 { my $self = shift;
95 8         16 my $old = $self->{LRT_trTo};
96 8 50       34 @_ or return $old;
97              
98 0         0 my $lang = shift;
99              
100 0 0       0 return $lang # language unchanged?
    0          
    0          
101             if ! defined $lang ? ! defined $old : ! defined $old ? 0 : $lang eq $old;
102              
103 0         0 $_->translateTo($lang) for $self->domains;
104 0         0 $self->{LRT_trTo} = $lang;
105             }
106              
107             #--------------------
108              
109              
110             sub addTextdomain($%) {
111 9     9 1 9914 my ($self, %args) = @_;
112              
113 9 100       39 if(my $only = $args{only_in_directory})
114 3         7 { my $delim = $self->{LRT_delim};
115 3 50       53 $only = $args{only_in_directory} = [ split $delim, $only ]
116             if ref $only ne 'ARRAY';
117              
118 3         13 my @incl = $self->_incl_path;
119 3         38 foreach my $dir (@$only)
120 3 100       14 { next if grep $_ eq $dir, @incl;
121 1         4 error __x"directory {dir} not in INCLUDE_PATH, used by {option}", dir => $dir, option => 'addTextdomain(only_in_directory)';
122             }
123             }
124              
125 8   33     79 $args{templater} ||= $self;
126 8   33     59 $args{lang} ||= $self->translateTo;
127              
128 8         38 my $name = $args{name};
129 8         16 my $td_class= $self->{LRT_tdc};
130 8         16 my $domain;
131 8 100       39 if($domain = textdomain $name, 'EXISTS')
132 1         12 { $td_class->upgrade($domain, %args);
133             }
134             else
135 7         195 { $domain = textdomain($td_class->new(%args));
136             }
137              
138 7         216 my $func = $domain->function;
139 7 100       25 if((my $other) = grep $func eq $_->function, $self->domains)
140 1         3 { error __x"translation function '{func}' already in use by textdomain '{name}'", func => $func, name => $other->name;
141             }
142 6         16 $self->{LRT_domains}{$name} = $domain;
143              
144             # call as function or as filter
145 6         43 $self->_stash->{$func} = $domain->translationFunction($self->service);
146 6         112 $self->context->define_filter($func => $domain->translationFilter, 1);
147 6         170 $domain;
148             }
149              
150 4     4   7 sub _incl_path() { @{ $_[0]->{LRT_path}} }
  4         17  
151 6     6   33 sub _stash() { $_[0]->service->context->stash }
152              
153              
154 8     8 1 32 sub domains() { values %{$_[0]->{LRT_domains} } }
  8         67  
155              
156              
157 0     0 1 0 sub domain($) { $_[0]->{LRT_domains}{$_[1]} }
158              
159              
160             sub extract(%)
161 1     1 1 7 { my ($self, %args) = @_;
162              
163 1         109 eval "require Log::Report::Template::Extract";
164 1 50       35 panic $@ if $@;
165              
166 1   50     10 my $stats = $args{show_stats} || 0;
167 1   50     8 my $charset = $args{charset} || 'UTF-8';
168 1 50       5 my $write = exists $args{write_tables} ? $args{write_tables} : 1;
169              
170 1         3 my @filenames;
171 1 50 33     9 if(my $fns = $args{filenames} || $args{filename})
172 0 0       0 { push @filenames, ref $fns eq 'ARRAY' ? @$fns : $fns;
173             }
174             else
175 1   33     11 { my $match = $args{filename_match} || qr/\.tt2?$/;
176             my $filter = sub {
177 2     2   5 my $name = $File::Find::name;
178 2 100 66     265 push @filenames, $name if -f $name && $name =~ $match;
179 1         8 };
180 1         7 foreach my $dir ($self->_incl_path)
181 1         9 { trace "scan $dir for template files";
182 1     2   196 find { wanted => sub { $filter->($File::Find::name) }, no_chdir => 1}, $dir;
  2         11  
183             }
184             }
185              
186 1         14 foreach my $domain ($self->domains)
187 2         1482 { my $function = $domain->function;
188 2         15 my $name = $domain->name;
189              
190 2         60 trace "extracting msgids for '$function' from domain '$name'";
191              
192 2         190 my $extr = Log::Report::Template::Extract->new(
193             lexicon => $domain->lexicon,
194             domain => $name,
195             pattern => "TT2-$function",
196             charset => $charset,
197             );
198              
199             $extr->process($_)
200 2         15 for @filenames;
201              
202 2         18 $extr->showStats;
203 2 50       113 $extr->write if $write;
204             }
205             }
206              
207             #--------------------
208              
209             sub _cols_factory(@)
210 6     6   60218 { my $self = shift;
211 6 50       23 my $params = ref $_[-1] eq 'HASH' ? pop : undef;
212 6 100       23 my @blocks = @_ ? @_ : 'td';
213 6 100 100     49 if(@blocks==1 && $blocks[0] =~ /\$[1-9]/)
214 2         6 { my $pattern = shift @blocks;
215             return sub { # second syntax
216 2     2   51 my @cols = split /\t/, $_[0];
217 2 50       15 $pattern =~ s/\$([0-9]+)/$cols[$1-1] || ''/ge;
  3         21  
218 2         17 $pattern;
219             }
220 2         18 }
221              
222             sub { # first syntax
223 4     4   103 my @cols = split /\t/, $_[0];
224 4         13 my @wrap = @blocks;
225 4         8 my @out;
226 4         15 while(@cols)
227 8         26 { push @out, "<$wrap[0]>$cols[0]";
228 8         12 shift @cols;
229 8 100       118 shift @wrap if @wrap > 1;
230             }
231 4         49 join '', @out;
232             }
233 4         40 }
234              
235              
236             sub _br_factory(@)
237 3     3   9446 { my $self = shift;
238 3 50       12 my $params = ref $_[-1] eq 'HASH' ? pop : undef;
239             return sub {
240 3 50   3   73 my $templ = shift or return '';
241 3         9 for($templ)
242 3         15 { s/\A[\s\n]*\n//; # leading blank lines
243 3         16 s/\n[\s\n]*\n/\n/g; # double blank links
244 3         15 s/\n[\s\n]*\z/\n/; # trailing blank lines
245 3         20 s/\s*\n/
\n/gm; # trailing blanks per line
246             }
247 3         24 $templ;
248             }
249 3         20 }
250              
251             sub _defaultFilters()
252 6     6   44 { my $self = shift;
253 6         61 my $context = $self->context;
254 6         73 $context->define_filter(cols => \&_cols_factory, 1);
255 6         252 $context->define_filter(br => \&_br_factory, 1);
256 6         183 $self;
257             }
258              
259              
260             sub _collectModifiers($)
261 6     6   18 { my ($self, $args) = @_;
262              
263             # First match will be used
264 6 50       15 my @modifiers = @{$args->{modifiers} || []};
  6         54  
265              
266             # More default extensions expected here. String::Print already adds a bunch.
267 6         24 \@modifiers;
268             }
269              
270              
271             { # Log::Report exports 'error', and we use that. Our base-class
272             # 'Template' however, also has a method named error() as well.
273             # Gladly, they can easily be separated.
274              
275             # no warnings 'redefined' misbehaves, at least for perl 5.16.2
276 7     7   91 no warnings;
  7         14  
  7         1711  
277              
278             sub error()
279             {
280 2 50 33 2   96 blessed $_[0] && $_[0]->isa('Template')
281             or return Log::Report::error(@_);
282              
283             $_[0]->{LRT_exceptions}
284 0 0         or return shift->SUPER::error(@_);
285              
286 0 0         @_ or panic "inexpected call to collect errors()";
287              
288             # convert Template errors into Log::Report errors
289 0           Log::Report::error($_[1]);
290             }
291             }
292              
293              
294             #--------------------
295              
296             1;