File Coverage

blib/lib/Log/Report/Lexicon/POT.pm
Criterion Covered Total %
statement 149 166 89.7
branch 48 84 57.1
condition 17 39 43.5
subroutine 27 30 90.0
pod 15 16 93.7
total 256 335 76.4


line stmt bran cond sub pod time code
1             # Copyrights 2007-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-Lexicon. 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::Lexicon::POT;
10 4     4   161698 use vars '$VERSION';
  4         11  
  4         158  
11             $VERSION = '1.11';
12              
13 4     4   18 use base 'Log::Report::Lexicon::Table';
  4         6  
  4         848  
14              
15 4     4   21 use warnings;
  4         7  
  4         71  
16 4     4   16 use strict;
  4         4  
  4         73  
17              
18 4     4   15 use Log::Report 'log-report-lexicon';
  4         5  
  4         21  
19 4     4   1554 use Log::Report::Lexicon::PO ();
  4         7  
  4         75  
20              
21 4     4   18 use POSIX qw/strftime/;
  4         18  
  4         18  
22 4     4   236 use List::Util qw/sum/;
  4         7  
  4         159  
23 4     4   17 use Scalar::Util qw/blessed/;
  4         12  
  4         147  
24 4     4   17 use Encode qw/decode/;
  4         6  
  4         137  
25              
26 4     4   19 use constant MSGID_HEADER => '';
  4         6  
  4         6750  
27              
28              
29             sub init($)
30 2     2 0 6 { my ($self, $args) = @_;
31              
32 2         11 $self->{LRLP_fn} = $args->{filename};
33 2   50     23 $self->{LRLP_index} = $args->{index} || {};
34 2   50     7 $self->{LRLP_charset} = $args->{charset} || 'UTF-8';
35              
36 2         4 my $version = $args->{version};
37             my $domain = $args->{textdomain}
38 2 50       4 or error __"textdomain parameter is required";
39              
40 2         4 my $forms = $args->{plural_forms};
41 2 50       6 unless($forms)
42 2   50     8 { my $nrplurals = $args->{nr_plurals} || 2;
43 2   50     6 my $algo = $args->{plural_alg} || 'n!=1';
44 2         9 $forms = "nplurals=$nrplurals; plural=($algo);";
45             }
46              
47             $self->_createHeader
48             ( project => $domain . (defined $version ? " $version" : '')
49             , forms => $forms
50             , charset => $args->{charset}
51             , date => $args->{date}
52 2 50       16 );
53              
54 2         11 $self->setupPluralAlgorithm;
55 2         6 $self;
56             }
57              
58              
59             sub read($@)
60 3     3 1 1765 { my ($class, $fn, %args) = @_;
61 3         9 my $self = bless {LRLP_index => {}}, $class;
62              
63 3         4 my $charset = $args{charset};
64 3 50 66     14 $charset = $1
65             if !$charset && $fn =~ m!\.([\w-]+)(?:\@[^/\\]+)?\.po$!i;
66              
67 3         2 my $fh;
68 3 100       8 if(defined $charset)
69 2 50   1   60 { open $fh, "<:encoding($charset):crlf", $fn
  1         4  
  1         1  
  1         6  
70             or fault __x"cannot read in {cs} from file {fn}"
71             , cs => $charset, fn => $fn;
72             }
73             else
74 1 50       39 { open $fh, '<:raw:crlf', $fn
75             or fault __x"cannot read from file {fn} (unknown charset)", fn=>$fn;
76             }
77              
78 3         1007 local $/ = "\n\n";
79 3         4 my $linenr = 1; # $/ frustrates $fh->input_line_number
80 3         4 while(1)
81 43         70 { my $location = "$fn line $linenr";
82 43         258 my $block = <$fh>;
83 43 100       102 defined $block or last;
84              
85 40         88 $linenr += $block =~ tr/\n//;
86              
87 40         241 $block =~ s/\s+\z//s;
88 40 50       86 length $block or last;
89              
90 40 100       50 unless($charset)
91 1 50       8 { $charset = $block =~ m/\"content-type:.*?charset=["']?([\w-]+)/mi
92             ? $1 : error __x"cannot detect charset in {fn}", fn => $fn;
93 1         5 trace "auto-detected charset $charset for $fn";
94 1         30 binmode $fh, ":encoding($charset):crlf";
95              
96 1 50       2282 $block = decode $charset, $block
97             or error __x"unsupported charset {charset} in {fn}"
98             , charset => $charset, fn => $fn;
99             }
100              
101 40         120 my $po = Log::Report::Lexicon::PO->fromText($block, $location);
102 40 50       82 $self->add($po) if $po;
103             }
104              
105 3 50       39 close $fh
106             or fault __x"failed reading from file {fn}", fn => $fn;
107              
108 3         7 $self->{LRLP_fn} = $fn;
109 3         5 $self->{LRLP_charset} = $charset;
110              
111 3         14 $self->setupPluralAlgorithm;
112 3         18 $self;
113             }
114              
115              
116             sub write($@)
117 2     2 1 601 { my $self = shift;
118 2 100       17 my $file = @_%2 ? shift : $self->filename;
119 2         5 my %args = @_;
120              
121 2 50       5 defined $file
122             or error __"no filename or file-handle specified for PO";
123              
124 2         4 my $need_refs = $args{only_active};
125 2         11 my @opt = (nr_plurals => $self->nrPlurals);
126              
127 2         4 my $fh;
128 2 100       10 if(ref $file) { $fh = $file }
  1         2  
129             else
130 1         4 { my $layers = '>:encoding('.$self->charset.')';
131 1 50       51 open $fh, $layers, $file
132             or fault __x"cannot write to file {fn} with {layers}"
133             , fn => $file, layers => $layers;
134             }
135              
136 2         89 $fh->print($self->msgid(MSGID_HEADER)->toString(@opt));
137 2         27 my $index = $self->index;
138 2         13 foreach my $msgid (sort keys %$index)
139 15 100       138 { next if $msgid eq MSGID_HEADER;
140              
141 13         26 my $rec = $index->{$msgid};
142             my @recs = blessed $rec ? $rec # one record with $msgid
143 13 50       37 : @{$rec}{sort keys %$rec}; # multiple records, msgctxt
  0         0  
144              
145 13         22 foreach my $po (@recs)
146 13 50       23 { next if $po->useless;
147 13 50 33     31 next if $need_refs && !$po->references;
148 13         36 $fh->print("\n", $po->toString(@opt));
149             }
150             }
151              
152             $fh->close
153 2 50       25 or failure __x"write errors for file {fn}", fn => $file;
154              
155 2         44 $self;
156             }
157              
158             #-----------------------
159              
160 1     1 1 3 sub charset() {shift->{LRLP_charset}}
161 111     111 1 294 sub index() {shift->{LRLP_index}}
162 1     1 1 3 sub filename() {shift->{LRLP_fn}}
163              
164              
165 0 0   0 1 0 sub language() { shift->filename =~ m![/\\](\w+)[^/\\]*$! ? $1 : undef }
166              
167             #-----------------------
168              
169             sub msgid($;$)
170 50     50 1 80 { my ($self, $msgid, $msgctxt) = @_;
171 50 100       90 my $msgs = $self->index->{$msgid} or return;
172              
173 37 50 33     207 return $msgs
      33        
174             if blessed $msgs
175             && (!$msgctxt || $msgctxt eq $msgs->msgctxt);
176              
177 0         0 $msgs->{$msgctxt};
178             }
179              
180              
181             sub msgstr($;$$)
182 15     15 1 32 { my ($self, $msgid, $count, $msgctxt) = @_;
183 15 50       29 my $po = $self->msgid($msgid, $msgctxt)
184             or return undef;
185              
186 15   100     26 $count //= 1;
187 15         40 $po->msgstr($self->pluralIndex($count));
188             }
189              
190              
191             sub add($)
192 53     53 1 71 { my ($self, $po) = @_;
193 53         83 my $msgid = $po->msgid;
194 53         77 my $index = $self->index;
195              
196 53         82 my $h = $index->{$msgid};
197 53 50       159 $h or return $index->{$msgid} = $po;
198              
199 0 0 0     0 $h = $index->{$msgid} = +{ ($h->msgctxt // '') => $h }
200             if blessed $h;
201              
202 0   0     0 my $ctxt = $po->msgctxt // '';
203             error __x"translation already exists for '{msgid}' with '{ctxt}"
204             , msgid => $msgid, ctxt => $ctxt
205 0 0       0 if $h->{$ctxt};
206              
207 0         0 $h->{$ctxt} = $po;
208             }
209              
210              
211             sub translations(;$)
212 5     5 1 858 { my $self = shift;
213             @_ or return map +(blessed $_ ? $_ : values %$_)
214 5 50       12 , values %{$self->index};
  4 100       7  
215              
216 1 50       4 error __x"the only acceptable parameter is 'ACTIVE', not '{p}'", p => $_[0]
217             if $_[0] ne 'ACTIVE';
218              
219 1         4 grep $_->isActive, $self->translations;
220             }
221              
222              
223 2     2   97 sub _now() { strftime "%Y-%m-%d %H:%M%z", localtime }
224              
225             sub header($;$)
226 13     13 1 595 { my ($self, $field) = (shift, shift);
227 13 50       33 my $header = $self->msgid(MSGID_HEADER)
228             or error __x"no header defined in POT for file {fn}"
229             , fn => $self->filename;
230              
231 13 100       37 if(!@_)
232 8   50     22 { my $text = $header->msgstr(0) || '';
233 8 100       237 return $text =~ m/^\Q$field\E\:\s*([^\n]*?)\;?\s*$/im ? $1 : undef;
234             }
235              
236 5         9 my $content = shift;
237 5         12 my $text = $header->msgstr(0);
238              
239 5         12 for($text)
240 5 100       10 { if(defined $content)
241 4 100       78 { s/^\Q$field\E\:([^\n]*)/$field: $content/im # change
242             || s/\z/$field: $content\n/; # new
243             }
244             else
245 1         17 { s/^\Q$field\E\:[^\n]*\n?//im; # remove
246             }
247             }
248              
249 5         19 $header->msgstr(0, $text);
250 5         15 $content;
251             }
252              
253              
254             sub updated(;$)
255 2     2 1 4 { my $self = shift;
256 2   66     6 my $date = shift || _now;
257 2         9 $self->header('PO-Revision-Date', $date);
258 2         4 $date;
259             }
260              
261             ### internal
262             sub _createHeader(%)
263 2     2   9 { my ($self, %args) = @_;
264 2   66     10 my $date = $args{date} || _now;
265              
266 2         21 my $header = Log::Report::Lexicon::PO->new
267             ( msgid => MSGID_HEADER, msgstr => <<__CONFIG);
268             Project-Id-Version: $args{project}
269             Report-Msgid-Bugs-To:
270             POT-Creation-Date: $date
271             PO-Revision-Date: $date
272             Last-Translator:
273             Language-Team:
274             MIME-Version: 1.0
275             Content-Type: text/plain; charset=$args{charset}
276             Content-Transfer-Encoding: 8bit
277             Plural-Forms: $args{forms}
278             __CONFIG
279              
280 2   50     8 my $version = $Log::Report::VERSION || '0.0';
281 2         9 $header->addAutomatic("Header generated with ".__PACKAGE__." $version\n");
282              
283 2 50       9 $self->index->{&MSGID_HEADER} = $header
284             if $header;
285              
286 2         6 $header;
287             }
288              
289              
290             sub removeReferencesTo($)
291 1     1 1 3 { my ($self, $filename) = @_;
292 1         3 sum map $_->removeReferencesTo($filename), $self->translations;
293             }
294              
295              
296             sub keepReferencesTo($)
297 0     0 1 0 { my ($self, $keep) = @_;
298 0         0 sum map $_->keepReferencesTo($keep), $self->translations;
299             }
300              
301              
302             sub stats()
303 0     0 1 0 { my $self = shift;
304 0         0 my %stats = (msgids => 0, fuzzy => 0, inactive => 0);
305 0         0 foreach my $po ($self->translations)
306 0 0       0 { next if $po->msgid eq MSGID_HEADER;
307 0         0 $stats{msgids}++;
308 0 0       0 $stats{fuzzy}++ if $po->fuzzy;
309 0 0 0     0 $stats{inactive}++ if !$po->isActive && !$po->useless;
310             }
311 0         0 \%stats;
312             }
313              
314             1;