File Coverage

blib/lib/Locale/XGettext.pm
Criterion Covered Total %
statement 296 547 54.1
branch 119 236 50.4
condition 27 54 50.0
subroutine 35 56 62.5
pod 29 29 100.0
total 506 922 54.8


line stmt bran cond sub pod time code
1             #! /bin/false
2             # vim: ts=4:et
3              
4             # Copyright (C) 2016-2017 Guido Flohr ,
5             # all rights reserved.
6              
7             # This program is free software; you can redistribute it and/or modify it
8             # under the terms of the GNU Library General Public License as published
9             # by the Free Software Foundation; either version 2, or (at your option)
10             # any later version.
11              
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warrant y of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15             # Library General Public License for more details.
16              
17             # You should have received a copy of the GNU Library General Public
18             # License along with this program; if not, write to the Free Software
19             # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
20             # USA.
21              
22             # ABSTRACT: Extract Strings To PO Files
23              
24             package Locale::XGettext;
25             $Locale::XGettext::VERSION = '0.6';
26 15     15   134 use strict;
  15         34  
  15         592  
27              
28 15     15   97 use Locale::TextDomain 1.20 qw(Locale-XGettext);
  15         306  
  15         99  
29 15     15   2235 use File::Spec;
  15         35  
  15         486  
30 15     15   7920 use Locale::PO 0.27;
  15         63427  
  15         516  
31 15     15   129 use Scalar::Util qw(reftype blessed);
  15         41  
  15         782  
32 15     15   6956 use Locale::Recode;
  15         43512  
  15         676  
33 15     15   11190 use Getopt::Long 2.36 qw(GetOptionsFromArray);
  15         155678  
  15         361  
34              
35 15     15   9034 use Locale::XGettext::Util::POEntries;
  15         46  
  15         527  
36 15     15   6766 use Locale::XGettext::Util::Keyword;
  15         38  
  15         520  
37 15     15   6690 use Locale::XGettext::Util::Flag;
  15         43  
  15         49686  
38              
39             # Helper method, not exported!
40             sub __empty($) {
41 439     439   824 my ($what) = @_;
42              
43 439 100 66     1551 return if defined $what && length $what;
44              
45 222         616 return 1;
46             }
47              
48             sub new {
49 52     52 1 27512 my ($class, $options, @files) = @_;
50              
51 52         98 my $self;
52 52 50       142 if (ref $class) {
53 0         0 $self = $class;
54             } else {
55 52         122 $self = bless {}, $class;
56             }
57              
58 52         157 $self->{__options} = $options;
59 52         114 $self->{__comment_tag} = undef;
60 52         138 $self->{__files} = [@files];
61 52         109 $self->{__exclude} = {};
62              
63 52 50       173 if (__PACKAGE__ eq ref $self) {
64 0         0 require Carp;
65 0         0 Carp::croak(__x("{package} is an abstract base class and must not"
66             . " be instantiated directly",
67             package => __PACKAGE__));
68             }
69            
70 52 100       189 $options->{default_domain} = 'messages' if __empty $options->{default_domain};
71 52 50       155 $options->{from_code} = 'ASCII' if __empty $options->{default_domain};
72 52 100       160 $options->{output_dir} = '.' if __empty $options->{output_dir};
73              
74 52 50       191 if (exists $options->{add_location}) {
75 0         0 my $option = $options->{add_location};
76 0 0       0 if (__empty $option) {
77 0         0 $option = 'full';
78             }
79 0 0 0     0 die __"The argument to '--add-location' must be 'full', 'file', or 'never'.\n"
      0        
80             if $option ne 'full' && $option ne 'file' && $option ne 'never';
81             }
82              
83 52 100       129 if (exists $options->{add_comments}) {
84 4 50 33     13 if (!ref $options->{add_comments}
85             && 'ARRAY' ne $options->{add_comments}) {
86 0         0 die __"Option 'add_comments' must be an array reference.\n";
87             }
88            
89 4         6 foreach my $comment (@{$options->{add_comments}}) {
  4         9  
90 5         15 $comment =~ s/^[ \t\n\f\r\013]+//;
91 5         13 $comment = quotemeta $comment;
92             }
93             }
94            
95 52 50       132 $options->{from_code} = 'ASCII' if __empty $options->{from_code};
96              
97 52         119 my $from_code = $options->{from_code};
98 52         237 my $cd = Locale::Recode->new(from => $from_code,
99             to => 'utf-8');
100 52 50       80348 if ($cd->getError) {
101 0         0 warn __x("warning: '{from_code}' is not a valid encoding name. "
102             . "Using ASCII as fallback.",
103             from_code => $from_code);
104 0         0 $options->{from_code} = 'ASCII';
105             } else {
106             $options->{from_code} =
107 52         408 Locale::Recode->resolveAlias($options->{from_code});
108             }
109            
110 52         1113 $self->__readFilesFrom($options->{files_from});
111 52 100       191 if ($self->needInputFiles) {
112             $self->__usageError(__"no input file given")
113 39 0 33     61 if !@{$self->{__files}} && !@{$options->{files_from}};
  39         116  
  0         0  
114             }
115            
116 52         231 $self->{__keywords} = $self->__setKeywords($options->{keyword});
117 52         204 $self->{__flags} = $self->__setFlags($options->{flag});
118              
119 52 100 66     174 if (exists $options->{exclude_file} && !ref $options->{exclude_file}) {
120 1         4 $options->{exclude_file} = [$options->{exclude_file}];
121             }
122              
123 52         253 $self->__readExcludeFiles($options->{exclude_file});
124              
125 52         517 return $self;
126             }
127              
128             sub newFromArgv {
129 0     0 1 0 my ($class, $argv) = @_;
130              
131 0         0 my $self;
132 0 0       0 if (ref $class) {
133 0         0 $self = $class;
134             } else {
135 0         0 $self = bless {}, $class;
136             }
137              
138 0         0 my %options = eval { $self->__getOptions($argv) };
  0         0  
139 0 0       0 if ($@) {
140 0         0 $self->__usageError($@);
141             }
142            
143 0 0       0 $self->__displayUsage if $options{help};
144            
145 0 0       0 if ($options{version}) {
146 0         0 print $self->versionInformation;
147 0         0 exit 0;
148             }
149            
150 0         0 return $class->new(\%options, @$argv);
151             }
152              
153             sub defaultKeywords {
154 52     52 1 154 return [];
155             }
156              
157             sub defaultFlags {
158 52     52 1 125 return [];
159             }
160              
161             sub run {
162 45     45 1 316 my ($self) = @_;
163              
164 45 50       165 if ($self->{__run}++) {
165 0         0 require Carp;
166 0         0 Carp::croak(__"Attempt to re-run extractor");
167             }
168              
169 45         308 my $po = $self->{__po} = Locale::XGettext::Util::POEntries->new;
170            
171 45 100       212 if ($self->option('join_existing')) {
172 1         4 my $output_file = $self->__outputFilename;
173 1 50       4 if ('-' eq $output_file) {
174 0         0 $self->__usageError(__"--join-existing cannot be used when output"
175             . " is written to stdout");
176             }
177 1         6 $self->readPO($output_file);
178             }
179            
180 45         73 foreach my $filename (@{$self->{__files}}) {
  45         110  
181 40 50       126 my $path = $self->resolveFilename($filename)
182             or die __x("Error resolving file name '{filename}': {error}!\n",
183             filename => $filename, error => $!);
184 40 50       244 if ($path =~ /\.pot?$/i) {
185 0         0 $self->readPO($path);
186             } else {
187 40         163 $self->readFile($path);
188             }
189             }
190              
191 45         270 $self->extractFromNonFiles;
192            
193             # FIXME! Sort po!
194            
195 45 100 33     163 if (($po->entries || $self->{__options}->{force_po})
      66        
196             && !$self->{__options}->{omit_header}) {
197 44         179 $po->prepend($self->__poHeader);
198             }
199              
200 45         127 foreach my $entry ($po->entries) {
201 98         278 $self->recodeEntry($entry);
202             }
203              
204 45         267 return $self;
205             }
206              
207 32     32 1 53 sub extractFromNonFiles { shift }
208              
209             sub resolveFilename {
210 40     40 1 87 my ($self, $filename) = @_;
211            
212 40   100     189 my $directories = $self->{__options}->{directory} || [''];
213 40         81 foreach my $directory (@$directories) {
214 41 100       136 my $path = length $directory
215             ? File::Spec->catfile($directory, $filename) : $filename;
216 41 100       887 stat $path && return $path;
217             }
218            
219 0         0 return;
220             }
221              
222             sub po {
223 36     36 1 124 shift->{__po}->entries;
224             }
225              
226             sub readPO {
227 1     1 1 3 my ($self, $path) = @_;
228            
229 1 50       6 my $entries = Locale::PO->load_file_asarray($path)
230             or die __x("error reading '{filename}': {error}!\n",
231             filename => $path, error => $!);
232            
233 1         951 foreach my $entry (@$entries) {
234 2 100 66     6 if ('""' eq $entry->msgid
235             && __empty $entry->dequote($entry->msgctxt)) {
236 1         3 next;
237             }
238 1         8 $self->addEntry($entry);
239             }
240            
241 1         5 return $self;
242             }
243              
244             sub addEntry {
245 55     55 1 253 my ($self, $entry) = @_;
246              
247 55 50       199 if (!$self->{__run}) {
248 0         0 require Carp;
249             # TRANSLATORS: run() is a method that should be invoked first.
250 0         0 Carp::croak(__"Attempt to add entries before run");
251             }
252              
253             # Simplify calling from languages that do not have hashes.
254 55 50       169 if (!ref $entry) {
255 0         0 $entry = {splice @_, 1};
256             }
257              
258 55         127 my $comment = delete $entry->{automatic};
259 55         181 $entry = $self->__promoteEntry($entry);
260              
261 55 100       130 if (defined $comment) {
262             # Does it contain an "xgettext:" comment? The original implementation
263             # is quite relaxed here, even recogizing comments like "exgettext:".
264 6         8 my $cleaned = '';
265 6         35 $comment =~ s{
266             (.*?)xgettext:(.*?(?:\n|\Z))
267             }{
268 2         9 my ($lead, $string) = ($1, $2);
269 2         4 my $valid;
270            
271 2         16 my @tokens = split /[ \x09-\x0d]+/, $string;
272            
273 2         5 foreach my $token (@tokens) {
274 13 50       45 if ($token eq 'fuzzy') {
    50          
    50          
    100          
275 0         0 $entry->fuzzy(1);
276 0         0 $valid = 1;
277             } elsif ($token eq 'no-wrap') {
278 0         0 $entry->add_flag('no-wrap');
279 0         0 $valid = 1;
280             } elsif ($token eq 'wrap') {
281 0         0 $entry->add_flag('wrap');
282 0         0 $valid = 1;
283             } elsif ($token =~ /^[a-z]+-(?:format|check)$/) {
284 1         5 $entry->add_flag($token);
285 1         20 $valid = 1;
286             }
287             }
288            
289 2 100       21 $cleaned .= "${lead}xgettext:${string}" if !$valid;
290             }exg;
291              
292 6         13 $cleaned .= $comment;
293 6         10 $comment = $cleaned;
294              
295 6         24 my $comment_keywords = $self->option('add_comments');
296 6 100 66     13 if (!__empty $comment && defined $comment_keywords) {
297 5         7 my @automatic;
298 5         10 foreach my $keyword (@$comment_keywords) {
299 6 100       71 if ($comment =~ /($keyword.*)/s) {
300 5         17 push @automatic, $1;
301 5         10 last;
302             }
303             }
304            
305 5         16 my $old_automatic = $entry->automatic;
306 5 100       26 push @automatic, $entry->dequote($old_automatic) if !__empty $old_automatic;
307 5 50       37 $entry->automatic(join "\n", @automatic) if @automatic;
308             }
309             }
310            
311 55         152 my ($msgid) = $entry->msgid;
312 55 50       326 if (!__empty $msgid) {
313 55         173 my $ctx = $entry->msgctxt;
314 55 50       324 $ctx = '' if __empty $ctx;
315            
316 55 100       268 return $self if exists $self->{__exclude}->{$msgid}->{$ctx};
317             }
318            
319 54         300 $self->{__po}->add($entry);
320              
321 54         129 return $self;
322             }
323              
324             sub keywords {
325 7     7 1 12 my ($self) = @_;
326              
327 7         13 my %keywords = %{$self->{__keywords}};
  7         23  
328              
329 7         16 return \%keywords;
330             }
331              
332             sub keywordOptionStrings {
333 0     0 1 0 my ($self) = @_;
334              
335 0         0 my @keywords;
336 0         0 my $keywords = $self->keywords;
337 0         0 foreach my $function (keys %$keywords) {
338 0         0 push @keywords, $keywords->{$function}->dump;
339             }
340              
341 0         0 return \@keywords;
342             }
343              
344             sub flags {
345 7     7 1 15 my ($self) = @_;
346              
347 7         9 my @flags = @{$self->{__flags}};
  7         16  
348              
349 7         15 return \@flags;
350             }
351              
352             sub flagOptionStrings {
353 0     0 1 0 my ($self) = @_;
354              
355 0         0 my @flags;
356 0         0 my $flags = $self->flags;
357 0         0 foreach my $flag (@$flags) {
358 0         0 push @flags, $flag->dump;
359             }
360              
361 0         0 return \@flags;
362             }
363              
364             sub recodeEntry {
365 98     98 1 184 my ($self, $entry) = @_;
366            
367 98         189 my $from_code = $self->option('from_code');
368 98 100       253 $from_code = 'US-ASCII' if __empty $from_code;
369 98         345 $from_code = Locale::Recode->resolveAlias($from_code);
370            
371 98         1088 my $cd;
372 98 50 33     250 if ($from_code ne 'US-ASCII' && $from_code ne 'UTF-8') {
373 0         0 $cd = Locale::Recode->new(from => $from_code, to => 'utf-8');
374 0 0       0 die $cd->getError if defined $cd->getError;
375             }
376              
377             my $toString = sub {
378 98     98   191 my ($entry) = @_;
379              
380 98 100       244 return join '', map { defined $_ ? $_ : '' }
  392         2051  
381             $entry->msgid, $entry->msgid_plural,
382             $entry->msgctxt, $entry->comment;
383 98         460 };
384            
385 98 50       248 if ($from_code eq 'US-ASCII') {
    0          
386             # Check that everything is 7 bit.
387 98         201 my $flesh = $toString->($entry);
388 98 50       526 if ($flesh !~ /^[\000-\177]*$/) {
389 0         0 die __x("Non-ASCII string at '{reference}'.\n"
390             . " Please specify the source encoding through "
391             . "'--from-code'.\n",
392             reference => $entry->reference);
393             }
394             } elsif ($from_code eq 'UTF-8') {
395             # Check that utf-8 is valid.
396 0         0 require utf8; # [SIC!]
397            
398 0         0 my $flesh = $toString->($entry);
399 0 0       0 if (!utf8::valid($flesh)) {
400 0         0 die __x("{reference}: invalid multibyte sequence\n",
401             reference => $entry->reference);
402             }
403             } else {
404             # Convert.
405 0         0 my $msgid = Locale::PO->dequote($entry->msgid);
406 0 0       0 if (!__empty $msgid) {
407 0 0       0 $cd->recode($msgid)
408             or $self->__conversionError($entry->reference, $cd);
409 0         0 $entry->msgid($msgid);
410             }
411            
412 0         0 my $msgid_plural = Locale::PO->dequote($entry->msgid_plural);
413 0 0       0 if (!__empty $msgid_plural) {
414 0 0       0 $cd->recode($msgid_plural)
415             or $self->__conversionError($entry->reference, $cd);
416 0         0 $entry->msgid($msgid_plural);
417             }
418            
419 0         0 my $msgstr = Locale::PO->dequote($entry->msgstr);
420 0 0       0 if (!__empty $msgstr) {
421 0 0       0 $cd->recode($msgstr)
422             or $self->__conversionError($entry->reference, $cd);
423 0         0 $entry->msgid($msgstr);
424             }
425            
426 0         0 my $msgstr_n = Locale::PO->dequote($entry->msgstr_n);
427 0 0       0 if ($msgstr_n) {
428 0         0 my $msgstr_0 = Locale::PO->dequote($msgstr_n->{0});
429 0 0       0 $cd->recode($msgstr_0)
430             or $self->__conversionError($entry->reference, $cd);
431 0         0 my $msgstr_1 = Locale::PO->dequote($msgstr_n->{1});
432 0 0       0 $cd->recode($msgstr_1)
433             or $self->__conversionError($entry->reference, $cd);
434 0         0 $entry->msgstr_n({
435             0 => $msgstr_0,
436             1 => $msgstr_1,
437             })
438             }
439            
440 0         0 my $comment = $entry->comment;
441 0 0       0 $cd->recode($comment)
442             or $self->__conversionError($entry->reference, $cd);
443 0         0 $entry->comment($comment);
444             }
445              
446 98         443 return $self;
447             }
448              
449             sub options {
450 0     0 1 0 shift->{__options};
451             }
452              
453             sub option {
454 149     149 1 287 my ($self, $key) = @_;
455              
456 149 100       414 return if !exists $self->{__options}->{$key};
457            
458 98         217 return $self->{__options}->{$key};
459             }
460              
461             sub setOption {
462 0     0 1 0 my ($self, $key, $value) = @_;
463              
464 0         0 $self->{__options}->{$key} = $value;
465              
466 0         0 return $self;
467             }
468              
469             sub output {
470 9     9 1 17 my ($self) = @_;
471            
472 9 50       21 if (!$self->{__run}) {
473 0         0 require Carp;
474 0         0 Carp::croak(__"Attempt to output from extractor before run");
475             }
476            
477 9 50       22 if (!$self->{__po}) {
478 0         0 require Carp;
479 0         0 Carp::croak(__"No PO data");
480             }
481            
482 9 0 33     21 return if !$self->{__po}->entries && !$self->{__options}->{force_po};
483              
484 9         24 my $options = $self->{__options};
485 9         40 my $filename = $self->__outputFilename;
486              
487 9 50       586 open my $fh, '>', $filename
488             or die __x("Error writing '{file}': {error}.\n",
489             file => $filename, error => $!);
490            
491 9         54 foreach my $entry ($self->{__po}->entries) {
492 18 50       2750 print $fh $entry->dump
493             or die __x("Error writing '{file}': {error}.\n",
494             file => $filename, error => $!);
495             }
496 9 50       1651 close $fh
497             or die __x("Error writing '{file}': {error}.\n",
498             file => $filename, error => $!);
499            
500 9         86 return $self;
501             }
502              
503       0 1   sub languageSpecificOptions {}
504              
505             # In order to simplify the code in other languages, we allow returning
506             # a flat list instead of an array of arrays. This wrapper checks the
507             # return value and converts it accordingly.
508             sub __languageSpecificOptions {
509 0     0   0 my ($self) = @_;
510              
511 0         0 my @options = $self->languageSpecificOptions;
512 0 0       0 return $options[0] if @options & 0x3;
513              
514             # Number of items is a multiple of 4.
515 0         0 my @retval;
516 0         0 while (@options) {
517 0         0 push @retval, [splice @options, 0, 4];
518             }
519              
520 0         0 return \@retval;
521             }
522              
523             sub printLanguageSpecificUsage {
524 0     0 1 0 my ($self) = @_;
525            
526 0         0 my $options = $self->__languageSpecificOptions;
527            
528 0 0       0 foreach my $optspec (@{$options || []}) {
  0         0  
529 0         0 my ($optstring, $optvar,
530             $usage, $description) = @$optspec;
531            
532 0         0 print " $usage ";
533 0         0 my $pos = 3 + length $usage;
534            
535 0         0 my @description = split /[ \x09-\x0d]+/, $description;
536 0         0 my $lineno = 0;
537 0         0 while (@description) {
538 0 0       0 my $limit = $lineno ? 31 : 29;
539 0 0       0 if ($pos < $limit) {
540 0         0 print ' ' x ($limit - $pos);
541 0         0 $pos = $limit;
542             }
543            
544 0         0 while (@description) {
545 0         0 my $word = shift @description;
546 0         0 print " $word";
547 0         0 $pos += 1 + length $word;
548 0 0 0     0 if (@description && $pos > 77 - length $description[-1]) {
549 0         0 ++$lineno;
550 0         0 print "\n";
551 0         0 $pos = 0;
552 0         0 last;
553             }
554             }
555             }
556 0         0 print "\n";
557             }
558            
559 0         0 return $self;
560             }
561              
562       0 1   sub fileInformation {}
563              
564       0 1   sub bugTrackingAddress {}
565              
566             sub versionInformation {
567 0     0 1 0 my ($self) = @_;
568            
569 0         0 my $package = ref $self;
570              
571 0         0 my $version;
572             {
573             ## no critic
574 15     15   162 no strict 'refs';
  15         72  
  15         48013  
  0         0  
575              
576 0         0 my $varname = "${package}::VERSION";
577 0         0 $version = ${$varname};
  0         0  
578             };
579              
580 0 0       0 $version = '' if !defined $version;
581            
582 0         0 $package =~ s/::/-/g;
583            
584 0         0 return __x('{program} ({package}) {version}
585             Please see the source for copyright information!
586             ', program => $self->programName, package => $package, version => $version);
587             }
588              
589             sub canExtractAll {
590 0     0 1 0 return;
591             }
592              
593             sub canKeywords {
594 0     0 1 0 shift;
595             }
596              
597             sub canFlags {
598 0     0 1 0 shift;
599             }
600              
601             sub needInputFiles {
602 39     39 1 105 shift;
603             }
604              
605             sub __readExcludeFiles {
606 52     52   139 my ($self, $files) = @_;
607            
608 52 100       132 return $self if !$files;
609            
610 1         4 foreach my $file (@$files) {
611 1 50       7 my $entries = Locale::PO->load_file_asarray($file)
612             or die __x("error reading '{filename}': {error}!\n",
613             filename => $file, error => $!);
614            
615 1         641 foreach my $entry (@$entries) {
616 2         10 my $msgid = $entry->msgid;
617 2 50       13 next if __empty $msgid;
618            
619 2         6 my $ctx = $entry->msgctxt;
620 2 100       27 $ctx = '' if __empty $ctx;
621            
622 2         13 $self->{__exclude}->{$msgid}->{$ctx} = $entry;
623             }
624             }
625            
626 1         3 return $self;
627             }
628              
629             sub __promoteEntry {
630 55     55   109 my ($self, $entry) = @_;
631            
632 55 100       221 if (!blessed $entry) {
633 54         276 my %entry = %$entry;
634 54         294 my $po_entry = Locale::PO->new;
635              
636 54         2139 my $keyword = delete $entry{keyword};
637 54 100       145 if (defined $keyword) {
638 7         22 my $keywords = $self->keywords;
639 7 50       18 if (exists $keywords->{$keyword}) {
640 7         30 my $comment = $keywords->{$keyword}->comment;
641 7 100       21 $entry{automatic} = $comment if !__empty $comment;
642              
643 7         25 my $flags = $self->flags;
644 7         19 my $sg_arg = $keywords->{$keyword}->singular;
645 7   100     18 my $pl_arg = $keywords->{$keyword}->plural || 0;
646 7         24 foreach my $flag (@$flags) {
647 9 100       87 next if $keyword ne $flag->function;
648 7 100 100     15 next if $flag->arg != $sg_arg && $flag->arg != $pl_arg;
649 6         14 my $flag_name = $flag->flag;
650 6 50       13 $flag_name = 'no-' . $flag_name if $flag->no;
651 6         15 $po_entry->add_flag($flag_name);
652             }
653             }
654             }
655              
656 54         156 my $flags = delete $entry{flags};
657 54 100       493 if (defined $flags) {
658 2         12 my @flags = split /[ \t\r\n]*,[ \t\r\n]*/, $flags;
659 2         4 foreach my $flag (@flags) {
660 3 50       34 $po_entry->add_flag($flag)
661             if !$po_entry->has_flag($flag);
662             }
663             }
664              
665 54         238 foreach my $method (keys %entry) {
666 101         165 eval { $po_entry->$method($entry{$method}) };
  101         343  
667             warn __x("error calling method '{method}' with value '{value}'"
668             . " on Locale::PO instance: {error}.\n",
669 101 50       1355 method => $method, value => $entry{$method},
670             error => $@) if $@;
671             }
672              
673 54         134 $entry = $po_entry;
674             }
675              
676 55         112 return $entry;
677             }
678              
679             sub __conversionError {
680 0     0   0 my ($self, $reference, $cd) = @_;
681            
682 0         0 die __x("{reference}: {conversion_error}\n",
683             reference => $reference,
684             conversion_error => $cd->getError);
685             }
686              
687             sub __outputFilename {
688 10     10   17 my ($self) = @_;
689            
690 10         16 my $options = $self->{__options};
691 10 100       33 if (exists $options->{output}) {
    100          
692 3 100 66     28 if (File::Spec->file_name_is_absolute($options->{output})
693             || '-' eq $options->{output}) {
694 1         4 return $options->{output};
695             } else {
696             return File::Spec->catfile($options->{output_dir},
697             $options->{output})
698 2         22 }
699             } elsif ('-' eq $options->{default_domain}) {
700 1         3 return '-';
701             } else {
702             return File::Spec->catfile($options->{output_dir},
703 6         84 $options->{default_domain} . '.po');
704             }
705            
706             # NOT REACHED!
707             }
708             sub __poHeader {
709 44     44   96 my ($self) = @_;
710              
711 44         83 my $options = $self->{__options};
712            
713 44         80 my $user_info;
714 44 100       104 if ($options->{foreign_user}) {
715 3         6 $user_info = <
716             This file is put in the public domain.
717             EOF
718             } else {
719 41         87 my $copyright = $options->{copyright_holder};
720 41 100       109 $copyright = "THE PACKAGE'S COPYRIGHT HOLDER" if !defined $copyright;
721            
722 41         134 $user_info = <
723             Copyright (C) YEAR $copyright
724             This file is distributed under the same license as the PACKAGE package.
725             EOF
726             }
727 44         97 chomp $user_info;
728            
729 44         127 my $entry = Locale::PO->new;
730 44         1585 $entry->fuzzy(1);
731 44         1727 $entry->comment(<
732             SOME DESCRIPTIVE TITLE.
733             $user_info
734             FIRST AUTHOR , YEAR.
735             EOF
736 44         273 $entry->msgid('');
737              
738 44         567 my @fields;
739            
740 44         91 my $package_name = $options->{package_name};
741 44 100       102 if (defined $package_name) {
742 2         5 my $package_version = $options->{package_version};
743 2 100 66     12 $package_name .= ' ' . $package_version
744             if defined $package_version && length $package_version;
745             } else {
746 42         69 $package_name = 'PACKAGE VERSION'
747             }
748            
749 44         106 push @fields, "Project-Id-Version: $package_name";
750              
751 44         83 my $msgid_bugs_address = $options->{msgid_bugs_address};
752 44 100       109 $msgid_bugs_address = '' if !defined $msgid_bugs_address;
753 44         95 push @fields, "Report-Msgid-Bugs-To: $msgid_bugs_address";
754            
755 44         107 push @fields, 'Last-Translator: FULL NAME ';
756 44         93 push @fields, 'Language-Team: LANGUAGE ';
757 44         77 push @fields, 'Language: ';
758 44         80 push @fields, 'MIME-Version: ';
759             # We always write utf-8.
760 44         75 push @fields, 'Content-Type: text/plain; charset=UTF-8';
761 44         69 push @fields, 'Content-Transfer-Encoding: 8bit';
762            
763 44         279 $entry->msgstr(join "\n", @fields);
764 44         990 return $entry;
765             }
766              
767             sub __getEntriesFromFile {
768 0     0   0 my ($self, $filename) = @_;
769              
770 0 0       0 open my $fh, '<', $filename
771             or die __x("Error reading '{filename}': {error}!\n",
772             filename => $filename, error => $!);
773            
774 0         0 my @entries;
775 0         0 my $chunk = '';
776 0         0 my $last_lineno = 1;
777 0         0 while (my $line = <$fh>) {
778 0 0       0 if ($line =~ /^[\x09-\x0d ]*$/) {
779 0 0       0 if (length $chunk) {
780 0         0 my $entry = Locale::PO->new;
781 0         0 chomp $chunk;
782 0         0 $entry->msgid($chunk);
783 0         0 $entry->reference("$filename:$last_lineno");
784 0         0 push @entries, $entry;
785             }
786 0         0 $last_lineno = $. + 1;
787 0         0 $chunk = '';
788             } else {
789 0         0 $chunk .= $line;
790             }
791             }
792            
793 0 0       0 if (length $chunk) {
794 0         0 my $entry = Locale::PO->new;
795 0         0 $entry->msgid($chunk);
796 0         0 chomp $chunk;
797 0         0 $entry->reference("$filename:$last_lineno");
798 0         0 push @entries, $entry;
799             }
800              
801 0         0 return @entries;
802             }
803              
804             sub __readFilesFrom {
805 52     52   155 my ($self, $list) = @_;
806            
807 52         103 my %seen;
808             my @files;
809 52         85 foreach my $file (@{$self->{__files}}) {
  52         140  
810 42         241 my $canonical = File::Spec->canonpath($file);
811 42 50       206 push @files, $file if !$seen{$canonical}++;
812             }
813            
814             # This matches the format expected by GNU xgettext. Lines where the
815             # first non-whitespace character is a hash sign, are ignored. So are
816             # empty lines (after whitespace stripping). All other lines are treated
817             # as filenames with trailing (not leading!) space stripped off.
818 52         127 foreach my $potfile (@$list) {
819 3 50       119 open my $fh, '<', $potfile
820             or die __x("Error opening '{file}': {error}!\n",
821             file => $potfile, error => $!);
822 3         54 while (my $file = <$fh>) {
823 6 50       19 next if $file =~ /^[ \x09-\x0d]*#/;
824 6         33 $file =~ s/[ \x09-\x0d]+$//;
825 6 50       16 next if !length $file;
826            
827 6         25 my $canonical = File::Spec->canonpath($file);
828 6 100       35 next if $seen{$canonical}++;
829              
830 5         65 push @files, $file;
831             }
832             }
833            
834 52         119 $self->{__files} = \@files;
835            
836 52         119 return $self;
837             }
838              
839             sub __getOptions {
840 0     0   0 my ($self, $argv) = @_;
841            
842 0         0 my %options;
843            
844 0         0 my $lang_options = $self->__languageSpecificOptions;
845 0         0 my %lang_options;
846            
847 0         0 foreach my $optspec (@$lang_options) {
848 0         0 my ($optstring, $optvar,
849             $usage, $description) = @$optspec;
850 0         0 $lang_options{$optstring} = \$options{$optvar};
851             }
852            
853 0         0 Getopt::Long::Configure('bundling');
854             $SIG{__WARN__} = sub {
855 0     0   0 $SIG{__WARN__} = 'DEFAULT';
856 0         0 die shift;
857 0         0 };
858             GetOptionsFromArray($argv,
859             # Are always overridden by standard options.
860             %lang_options,
861            
862             # Input file location:
863             'f|files-from=s@' => \$options{files_from},
864             'D|directory=s@' => \$options{directory},
865              
866             # Output file location:
867             'd|default-domain=s' => \$options{default_domain},
868             'o|output=s' => \$options{output},
869             'p|output-dir=s' => \$options{output_dir},
870              
871             # Input file interpretation.
872             'from-code=s' => \$options{from_code},
873            
874             # Operation mode:
875             'j|join-existing' => \$options{join_existing},
876            
877             # We allow multiple files.
878             'x|exclude-file=s@' => \$options{exclude_file},
879             'c|add-comments:s@' => \$options{add_comments},
880              
881             # Language specific options:
882             'a|extract-all' => \$options{extract_all},
883             'k|keyword:s@' => \$options{keyword},
884             'flag:s@' => \$options{flag},
885            
886             # Output details:
887             'force-po' => \$options{force_po},
888             'no-location' => \$options{no_location},
889             'n|add-location' => \$options{add_location},
890             's|sort-output' => \$options{sort_output},
891             'F|sort-by-file' => \$options{sort_by_file},
892             'omit-header' => \$options{omit_header},
893             'copyright-holder=s' => \$options{copyright_holder},
894             'foreign-user' => \$options{foreign_user},
895             'package_name=s' => \$options{package_name},
896             'package_version=s' => \$options{package_version},
897             'msgid-bugs-address=s' => \$options{msgid_bugs_address},
898             'm|msgstr-prefix:s' => \$options{msgid_str_prefix},
899             'M|msgstr-suffix:s' => \$options{msgid_str_suffix},
900              
901             # Informative output.
902             'h|help' => \$options{help},
903             'V|version' => \$options{version},
904 0         0 );
905 0         0 $SIG{__WARN__} = 'DEFAULT';
906            
907 0         0 foreach my $key (keys %options) {
908 0 0       0 delete $options{$key} if !defined $options{$key};
909             }
910            
911 0         0 return %options;
912             }
913              
914             sub __setKeywords {
915 52     52   149 my ($self, $options) = @_;
916              
917 52   50     150 my $defaults = $self->defaultKeywords || [];
918            
919 52         93 my $keywords = {};
920 52         134 foreach my $option (@$defaults, @$options) {
921 7 50       18 if ('' eq $option) {
922 0         0 $keywords = {};
923 0         0 next;
924             }
925              
926 7         10 my $keyword;
927 7 50       19 if (ref $option) {
928 0         0 $keyword = $option;
929             } else {
930 7         33 $keyword = Locale::XGettext::Util::Keyword->newFromString($option);
931             }
932 7         25 $keywords->{$keyword->function} = $keyword;
933             }
934              
935 52         136 return $keywords;
936             }
937              
938             sub __setFlags {
939 52     52   139 my ($self, $options) = @_;
940            
941 52         93 my @defaults = @{$self->defaultFlags};
  52         138  
942              
943 52         104 my %flags;
944             my @flags;
945              
946 52         112 foreach my $spec (@defaults, @$options) {
947 10 50       34 my $obj = Locale::XGettext::Util::Flag->newFromString($spec)
948             or die __x("A --flag argument doesn't have the"
949             . " ::[pass-] syntax: {flag}",
950             $spec);
951 10         28 my $function = $obj->function;
952 10         22 my $arg = $obj->arg;
953 10         20 my $flag = $obj->flag;
954              
955             # First one wins.
956 10 100       32 next if $flags{$function}->{$flag}->{$arg};
957              
958 9         20 $flags{$function}->{$flag}->{$arg} = $obj;
959 9         24 push @flags, $obj;
960             }
961            
962 52         145 return \@flags;
963             }
964              
965 0     0 1   sub programName { $0 }
966              
967             sub __displayUsage {
968 0     0     my ($self) = @_;
969            
970 0 0         if ($self->needInputFiles) {
971 0           print __x("Usage: {program} [OPTION] [INPUTFILE]...\n",
972             program => $self->programName);
973 0           print "\n";
974            
975 0           print __(<
976             Extract translatable strings from given input files.
977             EOF
978             } else {
979 0           print __x("Usage: {program} [OPTION]\n", program => $self->programName);
980 0           print "\n";
981            
982 0           print __(<
983             Extract translatable strings.
984             EOF
985             }
986            
987 0 0         if (defined $self->fileInformation) {
988 0           print "\n";
989 0           my $description = $self->fileInformation;
990 0           chomp $description;
991 0           print "$description\n";
992             }
993              
994 0           print "\n";
995            
996 0           print __(<
997             Mandatory arguments to long options are mandatory for short options too.
998             Similarly for optional arguments.
999             EOF
1000              
1001 0           print "\n";
1002              
1003 0           print __(<
1004             Input file location:
1005             EOF
1006              
1007 0           print __(<
1008             INPUTFILE ... input files
1009             EOF
1010              
1011 0           print __(<
1012             -f, --files-from=FILE get list of input files from FILE\
1013             EOF
1014              
1015 0           print __(<
1016             -D, --directory=DIRECTORY add DIRECTORY to list for input files search
1017             EOF
1018              
1019 0           printf __(<
1020             If input file is -, standard input is read.
1021             EOF
1022              
1023 0           print "\n";
1024            
1025 0           printf __(<
1026             Output file location:
1027             EOF
1028              
1029 0           printf __(<
1030             -d, --default-domain=NAME use NAME.po for output (instead of messages.po)
1031             EOF
1032              
1033 0           print __(<
1034             -o, --output=FILE write output to specified file
1035             EOF
1036              
1037 0           print __(<
1038             -p, --output-dir=DIR output files will be placed in directory DIR
1039             EOF
1040              
1041 0           print __(<
1042             If output file is -, output is written to standard output.
1043             EOF
1044              
1045 0           print "\n";
1046              
1047 0           print __(<
1048             Input file interpretation:
1049             EOF
1050              
1051 0           print __(<
1052             --from-code=NAME encoding of input files
1053             EOF
1054 0           print __(<
1055             By default the input files are assumed to be in ASCII.
1056             EOF
1057              
1058 0           printf "\n";
1059              
1060 0           print __(<
1061             Operation mode:
1062             EOF
1063              
1064 0           print __(<
1065             -j, --join-existing join messages with existing file
1066             EOF
1067              
1068 0           print __(<
1069             -x, --exclude-file=FILE.po entries from FILE.po are not extracted
1070             EOF
1071              
1072 0           print __(<
1073             -cTAG, --add-comments=TAG place comment blocks starting with TAG and
1074             preceding keyword lines in output file
1075             -c, --add-comments place all comment blocks preceding keyword lines
1076             in output file
1077             EOF
1078              
1079 0           print "\n";
1080              
1081 0           print __(<
1082             Language specific options:
1083             EOF
1084              
1085 0 0         if ($self->canExtractAll) {
1086 0           print __(<
1087             -a, --extract-all extract all strings
1088             EOF
1089             }
1090              
1091 0 0         if ($self->canKeywords) {
1092 0           print __(<
1093             -kWORD, --keyword=WORD look for WORD as an additional keyword
1094             -k, --keyword do not to use default keywords"));
1095             --flag=WORD:ARG:FLAG additional flag for strings inside the argument
1096             number ARG of keyword WORD
1097             EOF
1098             }
1099              
1100 0           $self->printLanguageSpecificUsage;
1101              
1102 0           print "\n";
1103              
1104 0           print __(<
1105             Output details:
1106             EOF
1107              
1108 0           print __(<
1109             --force-po write PO file even if empty
1110             EOF
1111              
1112 0           print __(<
1113             --no-location do not write '#: filename:line' lines
1114             EOF
1115              
1116 0           print __(<
1117             -n, --add-location generate '#: filename:line' lines (default)
1118             EOF
1119              
1120 0           print __(<
1121             -s, --sort-output generate sorted output
1122             EOF
1123              
1124 0           print __(<
1125             -F, --sort-by-file sort output by file location
1126             EOF
1127              
1128 0           print __(<
1129             --omit-header don't write header with 'msgid ""' entry
1130             EOF
1131              
1132 0           print __(<
1133             --copyright-holder=STRING set copyright holder in output
1134             EOF
1135              
1136 0           print __(<
1137             --foreign-user omit FSF copyright in output for foreign user
1138             EOF
1139              
1140 0           print __(<
1141             --package-name=PACKAGE set package name in output
1142             EOF
1143              
1144 0           print __(<
1145             --package-version=VERSION set package version in output
1146             EOF
1147              
1148 0           print __(<
1149             --msgid-bugs-address=EMAIL\@ADDRESS set report address for msgid bugs
1150             EOF
1151              
1152 0           print __(<
1153             -m[STRING], --msgstr-prefix[=STRING] use STRING or "" as prefix for msgstr
1154             values
1155             EOF
1156              
1157 0           print __(<
1158             -M[STRING], --msgstr-suffix[=STRING] use STRING or "" as suffix for msgstr
1159             values
1160             EOF
1161              
1162 0           printf "\n";
1163              
1164 0           print __(<
1165             Informative output:
1166             EOF
1167              
1168 0           print __(<
1169             -h, --help display this help and exit
1170             EOF
1171              
1172 0           print __(<
1173             -V, --version output version information and exit
1174             EOF
1175              
1176 0           my $url = $self->bugTrackingAddress;
1177              
1178 0           printf "\n";
1179              
1180 0 0         if (defined $url) {
1181             # TRANSLATORS: The placeholder indicates the bug-reporting address
1182             # for this package. Please add _another line_ saying
1183             # "Report translation bugs to <...>\n" with the address for translation
1184             # bugs (typically your translation team's web or email address).
1185 0           print __x("Report bugs at <{URL}>!\n", URL => $url);
1186             }
1187              
1188 0           exit 0;
1189             }
1190              
1191             sub __usageError {
1192 0     0     my ($self, $message) = @_;
1193              
1194 0 0         if ($message) {
1195 0           $message =~ s/\s+$//;
1196 0           $message = __x("{program_name}: {error}\n",
1197             program_name => $self->programName, error => $message);
1198             } else {
1199 0           $message = '';
1200             }
1201            
1202 0           die $message . __x("Try '{program_name} --help' for more information!\n",
1203             program_name => $self->programName);
1204             }
1205              
1206             1;