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