Codebase list liblog-report-perl / v0.998
distribution Log-Report-0.998.tar.gz Mark Overmeer authored 10 years ago Mark Overmeer committed 6 years ago
7 changed file(s) with 172 addition(s) and 53 deletion(s). Raw diff Collapse all Expand all
44 . connect to Message::Passing framework
55 . extract a Log::Report::Optional distribution
66
7 version 0.998: Tue Oct 22 09:55:06 CEST 2013
8 Fixes:
9 - xgettext-perl: actually use the provided template pattern
10 - xgettext-perl: only take template from .tt and .tt2 files
11 - xgettext-perl: accept '-' (STDIN) for --from
12
13 Improvements:
14 - more documentation about the PPI extraction process, and how
15 to use ::Message::new(_domain)
16 - Log::Report import option 'import'
17
718 version 0.997: Fri Sep 27 17:37:11 CEST 2013
819 Fixes:
920 - error about double definedness of settings, dependent on the
1223
1324 Improvements:
1425 - xgettext-perl: do not PPI files unless they are Perl
15 - xgettext-perl: do warn when ' are used, needs " with __x
26 - xgettext-perl: do warn when ' (single quotes) are used, needs
27 " (double quote) with __x
1628 - __x() now can have a _domain parameter
1729
1830 version 0.996: Wed Sep 4 17:23:11 CEST 2013
11
22 use 5.008;
33
4 my $version = '0.997';
4 my $version = '0.998';
55
66 my %prereq =
77 ( Test::More => 0.86
1111 my $lang = 'perl';
1212 my $version = 0;
1313 my $help = 0;
14 my ($from, $output);
14 my ($from, $output, $fn_match);
1515 my ($char_in, $char_out, $domain, $mode, $template);
1616
1717 GetOptions
18 'files-from|f=s' => \$from
18 'domain|d=s' => \$domain
19 , 'files-from|f=s' => \$from # file with filenames (MANIFEST?) or '-'
20 , 'files-match|m=s' => \$fn_match # select filename is dir
21 , 'from-code=s' => \$char_in
22 , 'help|h' => \$help
23 , 'language|L=s' => \$lang
24 , 'mode=s' => \$mode
1925 , 'output-dir|p=s' => \$output
20 , 'domain|d=s' => \$domain
21 , 'language|L=s' => \$lang
22 , 'from-code=s' => \$char_in
26 , 'template|t=s' => \$template # pattern in ::Template
2327 , 'to-code=s' => \$char_out # missing in xgettext?
28 , 'verbose=i' => \$mode
2429 , 'version|V' => \$version
25 , 'help|h' => \$help
26 , 'template|t=s' => \$template # pattern in ::Template
27 , 'verbose=i' => \$mode
2830 , 'v+' => \$mode
29 , 'mode=s' => \$mode
30 or exit(1);
31 or exit(1);
3132
3233 if($version)
3334 { print "Log::Report $Log::Report::VERSION\n";
6061 { !@ARGV
6162 or error __x"do not combine command-line filenames with --files-from";
6263
63 open FILENAMES, '<:raw', $from
64 or fault __x"cannot read filename list from {fn}", fn => $from;
64 if($from eq '-')
65 { @filenames = <STDIN>;
66 }
67 else
68 { open FILENAMES, '<:raw', $from
69 or fault __x"cannot read filename list from {fn}", fn => $from;
6570
66 @filenames = <FILENAMES>;
67 close FILENAMES;
71 @filenames = <FILENAMES>;
72 close FILENAMES;
73 }
6874 chomp(@filenames);
6975 }
7076 elsif(@ARGV)
8591 ( lexicon => $output
8692 , charset => $char_out
8793 , domain => $domain
88 , pattern => 'TT2-loc'
94 , pattern => $template
8995 );
9096
91 $extr->process($_, charset => $char_in)
92 for @filenames;
97 $fn_match ||= qr/\.tt2?$/i;
98
99 foreach my $filename (@filenames)
100 { unless($filename =~ $fn_match)
101 { info __x"skipping (not a template) {fn}", fn => $filename;
102 next;
103 }
104 $extr->process($_, charset => $char_in)
105 }
93106 }
94107 else
95108 { # process the pm files
101114 , charset => $char_out
102115 );
103116
117 $fn_match ||= qr/\.p[lm]$/i;
104118 foreach my $filename (@filenames)
105 { unless($filename =~ m/\.p[lm]$/i)
119 { unless($filename =~ $fn_match)
106120 { info __x"skipping (not perl) {fn}", fn => $filename;
107121 next;
108122 }
3232 my $ppi = Log::Report::Extract::PerlPPI->new
3333 ( lexicon => '/usr/share/locale'
3434 );
35 $ppi->process('lib/My/Pkg.pm'); # many times
36 $ppi->showStats;
35 $ppi->process('lib/My/Pkg.pm'); # call for each .pm file
36 $ppi->showStats; # optional
3737 $ppi->write;
3838
3939 # See script xgettext-perl
4040
4141 =chapter DESCRIPTION
42
4243 This module helps maintaining the POT files, updating the list of
4344 message-ids which are kept in them. After initiation, the M<process()>
4445 method needs to be called with all files which changed since last processing
45 and the existing PO files will get updated accordingly. If no translations
46 exist yet, one C<textdomain/xx.po> file will be created.
46 and the existing PO files will get updated accordingly.
47
48 If no translations exist yet, one C<$lexicon/$domain.po> file will be
49 created. If you want to start a translation, copy C<$lexicon/$domain.po>
50 to C<$lexicon/$domain/$lang.po> and edit that file. You may use
51 C<poedit> to edit po-files. Do not forget to add the new po-file to
52 your distribution (MANIFEST)
53
54 =section The extraction process
55
56 All pm-files need to be processed in one go: no incremental processing!
57
58 The Perl source is parsed using M<PPI>, which does understand Perl syntax
59 quite well, but does not support all features.
60
61 Automatically, the textdomain of the translations is discovered, as
62 first parameter of C<use Log::Report>. You may switch textdomain inside
63 one pm-file.
64
65 When all files have been processed, during the M<write()>, all existing
66 po-files for all discovered textdomains will get updated. Not only the
67 C<$lexicon/$domain.po> template, but also all C<$lexicon/$domain/$lang.po>
68 will be replaced. When a msgid has disappeared, existing translations
69 will get disabled, not removed. New msgids will be added and flagged
70 "fuzzy".
71
72 =subsection What is extracted?
73
74 This script will extract the msgids used in C<__()>, C<__x()>, C<__xn()>,
75 and C<__n()> (implemented by M<Log::Report>) For instance
76
77 __x"msgid", @more
78 __x'msgid', @more <--- no! syntax error!
79 __x("msgid", @more)
80 __x('msgid', @more)
81 __x(msgid => @more)
82
83 Besides, there are some helpers which are no-ops in the code, only to fill
84 the po-tables: C<N__()>, C<N__n()>, C<N__()>
85
86 =subsection What is not extracted?
87
88 B<Not> extracted are the usage of anything above, where the first
89 parameter is not a simple string. Not extracted are
90
91 __x($format, @more)
92 __x$format, @more
93 __x(+$format, _domain => 'other domain', @more)
94 __x($first.$second, @more)
95
96 In these cases, you have to use C<N__()> functions to declare the possible
97 values of C<$format>.
4798
4899 =chapter METHODS
49100
125176 my $def = $msgids{$word} # get __() description
126177 or return 0;
127178
128 my @msgids = $self->_get($node, @$def)
179 my @msgids = $self->_get($node, $domain, $word, $def)
129180 or return 0;
181
182 my ($nr_msgids, $has_count, $has_opts, $has_vars,$do_split) = @$def;
130183
131184 my $line = $node->location->[0];
132185 unless($domain)
133 { mistake __x
134 "no text-domain for translatable at {fn} line {line}"
186 { mistake
187 __x"no text-domain for translatable at {fn} line {line}"
135188 , fn => $fn, line => $line;
136189 return 0;
137190 }
138191
139 if($def->[4]) # must split? Bulk conversion strings
192 if($do_split) # Bulk conversion strings
140193 { my @words = map {split} @msgids;
141194 $self->store($domain, $fn, $line, $_) for @words;
142195 $msgs_found += @words;
153206 $msgs_found;
154207 }
155208
156 sub _get($@)
157 { my ($self, $node, $msgids, $count, $opts, $vars, $split) = @_;
158 my $list_only = ($msgids > 1) || $count || $opts || $vars;
209 sub _get($$$$)
210 { my ($self, $node, $domain, $function, $def) = @_;
211 my ($nr_msgids, $has_count, $opts, $vars, $split) = @$def;
212 my $list_only = ($nr_msgids > 1) || $has_count || $opts || $vars;
159213 my $expand = $opts || $vars;
160214
161215 my @msgids;
166220 $first = $first->schild(0)
167221 if $first->isa('PPI::Statement::Expression');
168222
169 while(defined $first && $msgids > @msgids)
223 my $line;
224 while(defined $first && $nr_msgids > @msgids)
170225 { my $msgid;
171226 my $next = $first->snext_sibling;
172227 my $sep = $next && $next->isa('PPI::Token::Operator') ? $next : '';
173 my $line = $first->location->[0];
228 $line = $first->location->[0];
174229
175230 if($first->isa('PPI::Token::Quote'))
176231 { last if $sep !~ m/^ (?: | \=\> | [,;:] ) $/x;
200255 , line => $line if $msgid =~ s/(?<!\\)\n$//;
201256
202257 push @msgids, $msgid;
203 last if $msgids==@msgids || !$sep;
258 last if $nr_msgids==@msgids || !$sep;
204259
205260 $first = $sep->snext_sibling;
261 }
262 @msgids or return ();
263 my $next = $first->snext_sibling;
264 if($has_count && !$next)
265 { error __x"count missing in {function} in line {line}"
266 , function => $function, line => $line;
206267 }
207268
208269 @msgids;
8080 Indicates whether variables are to be filled-in.
8181
8282 =option _domain STRING
83 =default _domain from C<use>
83 =default _domain <from "use Log::Report">
8484 The text-domain (translation table) to which this C<_msgid> belongs.
85
86 With this parameter, your can "borrow" translations from other textdomains.
87 Be very careful with this (although there are good use-cases) The xgettext
88 msgid extractor may add the used msgid to this namespace as well. To
89 avoid that, add a harmless '+':
90
91 print __x(+"errors", _domain => 'global');
92
93 The extractor will not take the msgid when it is an expression. The '+'
94 has no effect on the string at runtime.
8595
8696 =option _count INTEGER|ARRAY|HASH
8797 =default _count C<undef>
2222 require Log::Report::Dispatcher::Try;
2323
2424 # See section Run modes
25 my %is_reason = map {($_=>1)} @Log::Report::Util::reasons;
26 my %is_fatal = map {($_=>1)} qw/ERROR FAULT FAILURE PANIC/;
27 my %use_errno = map {($_=>1)} qw/FAULT ALERT FAILURE/;
25 my %is_reason = map +($_=>1), @Log::Report::Util::reasons;
26 my %is_fatal = map +($_=>1), qw/ERROR FAULT FAILURE PANIC/;
27 my %use_errno = map +($_=>1), qw/FAULT ALERT FAILURE/;
2828
2929 sub _whats_needed(); sub dispatcher($@);
3030 sub trace(@); sub assert(@); sub info(@); sub notice(@); sub warning(@);
101101 fault "cannot allocate $size bytes"; # no translation
102102 fault __x "cannot allocate $size bytes"; # wrong, not static
103103
104 # translation depends on count.
104 # translation depends on count
105105 print __xn("found one file", "found {_count} files", @files), "\n";
106
107 # borrow from an other textdomain (see M<Log::Report::Message>)
108 print __x(+"errors in {line}", _domain => 'global', line => $line);
106109
107110 # catch errors (implements hidden eval/die)
108111 try { error };
829832 also selectively change the output mode, like
830833 dispatcher PERL => 'default', mode => 3
831834
835 =option import FUNCTION|ARRAY
836 =default import C<undef>
837 [0.998] When not specified, the C<syntax> option determines the list
838 of functions which are being exported. With this option, the C<syntax>
839 option is ignored and only the specified FUNCTION(s) are imported.
840
832841 =examples of import
833842 use Log::Report mode => 3; # or 'DEBUG'
834843
837846 use Log::Report 'my-domain' # in one package, top of distr
838847 , mode => 'VERBOSE'
839848 , translator => Log::Report::Translator::POT->new
840 ( lexicon => '/home/me/locale' # bindtextdomain
841 , charset => 'UTF-8' # codeset
849 ( lexicon => '/home/mine/locale' # bindtextdomain
850 , charset => 'UTF-8' # codeset
842851 )
843852 , native_language => 'nl_NL' # untranslated msgs are Dutch
844853 , syntax => 'REPORT';# report ERROR, not error()
845854
855 use Log::Report import => 'try'; # or ARRAY of functions
856
846857 =cut
847858
848859 sub import(@)
850861
851862 my $textdomain = @_%2 ? shift : undef;
852863 my %opts = @_;
853 my $syntax = delete $opts{syntax} || 'SHORT';
854864 my ($pkg, $fn, $linenr) = caller;
855865
856866 if(my $trans = delete $opts{translator})
875885
876886 push @{$domain_start{$fn}}, [$linenr => $textdomain];
877887
878 my @export = (@functions, @make_msg);
879
880 if($syntax eq 'SHORT')
881 { push @export, @reason_functions
882 }
883 elsif($syntax ne 'REPORT' && $syntax ne 'LONG')
884 { error __x"syntax flag must be either SHORT or REPORT, not `{syntax}'"
885 , syntax => $syntax;
888 my @export;
889 if(my $in = $opts{import})
890 { push @export, ref $in eq 'ARRAY' ? @$in : $in;
891 }
892 else
893 { push @export, @functions, @make_msg;
894
895 my $syntax = delete $opts{syntax} || 'SHORT';
896 if($syntax eq 'SHORT')
897 { push @export, @reason_functions
898 }
899 elsif($syntax ne 'REPORT' && $syntax ne 'LONG')
900 { error __x"syntax flag must be either SHORT or REPORT, not `{flag}'"
901 , flag => $syntax;
902 }
886903 }
887904
888905 $class->export_to_level(1, undef, @export);
66 use File::Temp qw/tempdir/;
77 use Test::More;
88
9 use constant MSGIDS => 24;
9 use constant MSGIDS => 25;
1010 use constant PLURAL_MSGIDS => 4;
1111 BEGIN
1212 { eval "require PPI";
1313 plan skip_all => 'PPI not installed'
1414 if $@;
1515
16 plan tests => 34 + MSGIDS*3 + PLURAL_MSGIDS*1;
16 plan tests => 10 + MSGIDS*4 + PLURAL_MSGIDS*1;
1717 use_ok('Log::Report::Extract::PerlPPI');
1818 }
1919
6666 take(__x(qq{b7a}, b7b => "b7c"), 'b7a');
6767 take(__x(q{b8a}, b8b => "b8c"), 'b8a');
6868 take(__x(b9a => b9b => "b9c"), 'b9a');
69 take(__x(b10 => 1, 2), 'b10');
6970
7071 take((__n "c1", "c2", 1), "c1", "c2");
7172 take((__n "c3", "c4", 0), "c3", "c4");
7879 take(join(',', N__w(" d4 d5
7980 d6
8081 d7")), "d4", "d5", "d6", "d7"); # line contains tab
82
83 ### do not index these:
84
85 __x(+"e1");
8186
8287 ### check that all tags were found in POT
8388
9499 ok(defined $msgid, "processing $msgid");
95100 ok(!defined $msgids{$msgid}, 'check not double');
96101 $msgids{$msgid}++;
97 ok(delete $expect_pos{$msgid}, 'was expected');
102 ok(delete $expect_pos{$msgid}, "was expected $msgid");
98103
99104 my $plural = $po->plural
100105 or next;