File Coverage

blib/lib/Locale/Maketext/Gettext.pm
Criterion Covered Total %
statement 250 258 96.9
branch 93 124 75.0
condition 18 27 66.6
subroutine 22 22 100.0
pod 12 14 85.7
total 395 445 88.7


line stmt bran cond sub pod time code
1             # Locale::Maketext::Gettext - Joins the gettext and Maketext frameworks
2              
3             # Copyright (c) 2003-2021 imacat. All rights reserved. This program is free
4             # software; you can redistribute it and/or modify it under the same terms
5             # as Perl itself.
6             # First written: 2003/4/23
7              
8             package Locale::Maketext::Gettext;
9 11     11   27318 use 5.008;
  11         50  
10 11     11   62 use strict;
  11         22  
  11         4038  
11 11     11   61 use warnings;
  11         27  
  11         2407  
12 11     11   66 use base qw(Locale::Maketext Exporter);
  11         19  
  11         6578  
13             our ($VERSION, @EXPORT, @EXPORT_OK);
14             $VERSION = 1.32;
15             @EXPORT = qw(read_mo);
16             @EXPORT_OK = @EXPORT;
17             # Prototype declaration
18             sub read_mo($);
19              
20 11     11   143587 use Encode qw(encode decode FB_DEFAULT);
  11         41338  
  11         791  
21 11     11   77 use File::Spec::Functions qw(catfile);
  11         25  
  11         510  
22 11     11   69 no strict qw(refs);
  11         23  
  11         32618  
23              
24             our (%CACHE, $REREAD_MO, $MO_FILE);
25             %CACHE = qw();
26             $REREAD_MO = 0;
27             $MO_FILE = "";
28             our (@SYSTEM_LOCALEDIRS);
29             @SYSTEM_LOCALEDIRS = qw(/usr/share/locale /usr/lib/locale
30             /usr/local/share/locale /usr/local/lib/locale);
31              
32             # Set or retrieve the output encoding
33             sub encoding : method {
34 52     52 1 257 local ($_, %_);
35 52         80 my $self;
36 52         107 ($self, $_) = @_;
37            
38             # This is not a static method
39 52 50       154 return if ref($self) eq "";
40            
41             # Set the output encoding
42 52 100       132 if (@_ > 1) {
43 48 100       113 if (defined $_) {
44 19         37 $self->{"ENCODING"} = $_;
45             } else {
46 29         67 delete $self->{"ENCODING"};
47             }
48 48         93 $self->{"USERSET_ENCODING"} = $_;
49             }
50            
51             # Return the encoding
52 52 100       194 return exists $self->{"ENCODING"}? $self->{"ENCODING"}: undef;
53             }
54              
55             # Specify the encoding used in the keys
56             sub key_encoding : method {
57 29     29 1 78 local ($_, %_);
58 29         45 my $self;
59 29         74 ($self, $_) = @_;
60            
61             # This is not a static method
62 29 50       91 return if ref($self) eq "";
63            
64             # Set the encoding used in the keys
65 29 50       106 if (@_ > 1) {
66 29 100       82 if (defined $_) {
67 2         5 $self->{"KEY_ENCODING"} = $_;
68             } else {
69 27         62 delete $self->{"KEY_ENCODING"};
70             }
71             }
72            
73             # Return the encoding
74 29 100       92 return exists $self->{"KEY_ENCODING"}? $self->{"KEY_ENCODING"}: undef;
75             }
76              
77             # Initialize the language handler
78             sub new : method {
79 155     155 0 50788 local ($_, %_);
80 155         303 my ($self, $class);
81 155   33     629 $class = ref($_[0]) || $_[0];
82 155         366 $self = bless {}, $class;
83 155         604 $self->subclass_init;
84 155         670 $self->init;
85 155         978 return $self;
86             }
87              
88             # Initialize at the subclass level, so that it can be
89             # inherited by calling $self->SUPER:subclass_init
90             sub subclass_init : method {
91 155     155 0 320 local ($_, %_);
92 155         304 my ($self, $class);
93 155         244 $self = $_[0];
94 155         283 $class = ref($self);
95             # Initialize the instance lexicon
96 155         565 $self->{"Lexicon"} = {};
97             # Initialize the LOCALEDIRS registry
98 155         312 $self->{"LOCALEDIRS"} = {};
99             # Initialize the MO timestamp
100 155         302 $self->{"REREAD_MO"} = $REREAD_MO;
101             # Initialize the DIE_FOR_LOOKUP_FAILURES setting
102 155         274 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 0;
103 155         1016 $self->SUPER::fail_with($self->can("failure_handler_auto"));
104             # Initialize the ENCODE_FAILURE setting
105 155         1350 $self->{"ENCODE_FAILURE"} = FB_DEFAULT;
106             # Initialize the MO_FILE value of this instance
107 155         341 $self->{"MO_FILE"} = "";
108 155 100       236 ${"$class\::MO_FILE"} = "" if !defined ${"$class\::MO_FILE"};
  43         175  
  155         743  
109             # Find the locale name, for this subclass
110 155         466 $self->{"LOCALE"} = $class;
111 155         865 $self->{"LOCALE"} =~ s/^.*:://;
112 155         580 $self->{"LOCALE"} =~ s/(_)(.*)$/$1 . uc $2/e;
  63         294  
113             # Map i_default to C
114 155 50       432 $self->{"LOCALE"} = "C" if $self->{"LOCALE"} eq "i_default";
115             # Set the category. Currently this is always LC_MESSAGES
116 155         307 $self->{"CATEGORY"} = "LC_MESSAGES";
117             # Default key encoding is US-ASCII
118 155         267 $self->{"KEY_ENCODING"} = "US-ASCII";
119 155         361 return;
120             }
121              
122             # Bind a text domain to a locale directory
123             sub bindtextdomain : method {
124 71     71 1 424 local ($_, %_);
125 71         138 my ($self, $DOMAIN, $LOCALEDIR);
126 71         169 ($self, $DOMAIN, $LOCALEDIR) = @_;
127            
128             # This is not a static method
129 71 50       198 return if ref($self) eq "";
130            
131             # Return null for this rare case
132             return if !defined $LOCALEDIR
133 71 100 100     215 && !exists ${$self->{"LOCALEDIRS"}}{$DOMAIN};
  2         18  
134            
135             # Register the DOMAIN and its LOCALEDIR
136 70 100       181 ${$self->{"LOCALEDIRS"}}{$DOMAIN} = $LOCALEDIR if defined $LOCALEDIR;
  69         233  
137            
138             # Return the registry
139 70         112 return ${$self->{"LOCALEDIRS"}}{$DOMAIN};
  70         209  
140             }
141              
142             # Set the current text domain
143             sub textdomain : method {
144 82     82 1 658 local ($_, %_);
145 82         146 my ($self, $class, $DOMAIN, $LOCALEDIR, $mo_file);
146 82         163 ($self, $DOMAIN) = @_;
147            
148             # This is not a static method
149 82 50       201 return if ref($self) eq "";
150             # Find the class name
151 82         469 $class = ref($self);
152            
153             # Return the current domain
154 82 100       188 return $self->{"DOMAIN"} if !defined $DOMAIN;
155            
156             # Set the timestamp of this read in this instance
157 78         150 $self->{"REREAD_MO"} = $REREAD_MO;
158             # Set the current domain
159 78         170 $self->{"DOMAIN"} = $DOMAIN;
160            
161             # Clear it
162 78         174 $self->{"Lexicon"} = {};
163 78         166 %{"$class\::Lexicon"} = qw();
  78         297  
164 78         156 $self->{"MO_FILE"} = "";
165 78         123 ${"$class\::MO_FILE"} = "";
  78         202  
166            
167             # The format is "{LOCALEDIR}/{LOCALE}/{CATEGORY}/{DOMAIN}.mo"
168             # Search the system locale directories if the domain was not
169             # registered yet
170 78 100       120 if (!exists ${$self->{"LOCALEDIRS"}}{$DOMAIN}) {
  78         247  
171 4         8 undef $mo_file;
172 4         22 foreach $LOCALEDIR (@SYSTEM_LOCALEDIRS) {
173             $_ = catfile($LOCALEDIR, $self->{"LOCALE"},
174 13         92 $self->{"CATEGORY"}, "$DOMAIN.mo");
175 13 100 66     317 if (-f $_ && -r $_) {
176 1         5 $mo_file = $_;
177 1         3 last;
178             }
179             }
180             # Not found at last
181 4 100       32 return $DOMAIN if !defined $mo_file;
182            
183             # This domain was registered
184             } else {
185 74         529 $mo_file = catfile(${$self->{"LOCALEDIRS"}}{$DOMAIN},
186 74         113 $self->{"LOCALE"}, $self->{"CATEGORY"}, "$DOMAIN.mo");
187             }
188            
189             # Record it
190 75         188 ${"$class\::MO_FILE"} = $mo_file;
  75         246  
191 75         144 $self->{"MO_FILE"} = $mo_file;
192            
193             # Read the MO file
194             # Cached
195 75 100       258 if (!$self->_is_using_cache($mo_file)) {
196 51         104 my ($enc, @stats, $mtime, $size);
197             # Read it
198 51         256 %_ = read_mo($mo_file);
199            
200             # Successfully read
201 51 100       221 if (scalar(keys %_) > 0) {
202             # Decode it
203             # Find the encoding of that MO file
204 46 50       397 if ($_{""} =~ /^Content-Type: text\/plain; charset=(.*)$/im) {
205 46         224 $enc = $1;
206             # Default to US-ASCII
207             } else {
208 0         0 $enc = "US-ASCII";
209             }
210             # Set the current encoding to the encoding of the MO file
211 46         324 $_{$_} = decode($enc, $_{$_}) foreach keys %_;
212             }
213            
214             # Cache them
215 51         91616 @stats = stat $mo_file;
216 51 100       231 if (@stats > 0) {
217 48         163 ($mtime, $size) = @stats[9,7];
218             } else {
219 3         8 ($mtime, $size) = (undef, undef);
220             }
221 51         718 $CACHE{$mo_file} = {
222             "Lexicon" => {%_},
223             "encoding" => $enc,
224             "mtime" => $mtime,
225             "size" => $size,
226             };
227             }
228            
229             # Respect the existing output encoding
230 75 100       270 if (defined $CACHE{$mo_file}->{"encoding"}) {
231 70         184 $self->{"MO_ENCODING"} = $CACHE{$mo_file}->{"encoding"};
232             } else {
233 5         10 delete $self->{"MO_ENCODING"};
234             }
235             # Respect the MO file encoding unless there is a user preference
236 75 100       218 if (!exists $self->{"USERSET_ENCODING"}) {
237 70 100       161 if (exists $self->{"MO_ENCODING"}) {
238 65         147 $self->{"ENCODING"} = $self->{"MO_ENCODING"};
239             } else {
240 5         11 delete $self->{"ENCODING"};
241             }
242             }
243 75         187 $self->{"Lexicon"} = $CACHE{$mo_file}->{"Lexicon"};
244 75         122 %{"$class\::Lexicon"} = %{$CACHE{$mo_file}->{"Lexicon"}};
  75         617  
  75         293  
245 75         575 $self->clear_isa_scan;
246            
247 75         594 return $DOMAIN;
248             }
249              
250             # Return whether we are using our cache.
251             sub _is_using_cache : method {
252 75     75   138 local ($_, %_);
253 75         142 my ($self, $mo_file, @stats, $mtime, $size);
254 75         160 ($self, $mo_file) = @_;
255            
256             # NO if we do not have such a cache.
257 75 100       287 return undef unless exists $CACHE{$mo_file};
258            
259 25         563 @stats = stat $mo_file;
260             # The MO file does not exist previously.
261 25 50 33     207 if (!defined $CACHE{$mo_file}->{"mtime"}
262             || !defined $CACHE{$mo_file}->{"size"}) {
263             # Use the cache if the MO file still does not exist.
264 0         0 return (@stats == 0);
265            
266             # The MO file exists previously.
267             } else {
268             # Use the cache if the MO file did not change.
269 25         69 ($mtime, $size) = @stats[9,7];
270             return $mtime == $CACHE{$mo_file}->{"mtime"}
271 25   66     181 && $size == $CACHE{$mo_file}->{"size"};
272             }
273             }
274              
275             # Encode after maketext
276             sub maketext : method {
277 194     194 1 4002 local ($_, %_);
278 194         347 my ($self, $key, @param, $class, $keyd);
279 194         452 ($self, $key, @param) = @_;
280            
281             # This is not a static method - NOW
282 194 50       475 return if ref($self) eq "";
283             # Find the class name
284 194         324 $class = ref($self);
285            
286             # MO file should be re-read
287 194 100       463 if ($self->{"REREAD_MO"} < $REREAD_MO) {
288 2         5 $self->{"REREAD_MO"} = $REREAD_MO;
289 2 50       8 defined($_ = $self->textdomain) and $self->textdomain($_);
290             }
291            
292             # If the instance lexicon is changed.
293             # Maketext uses a class lexicon. We have to copy the instance
294             # lexicon into the class lexicon. This is slow. Mass memory
295             # copy sucks. Avoid create several language handles for a
296             # single localization subclass whenever possible.
297             # Maketext uses class lexicon in order to track the inheritance.
298             # It is hard to change it.
299 194 100       295 if (${"$class\::MO_FILE"} ne $self->{"MO_FILE"}) {
  194         829  
300 16         33 ${"$class\::MO_FILE"} = $self->{"MO_FILE"};
  16         36  
301 16         26 %{"$class\::Lexicon"} = %{$self->{"Lexicon"}};
  16         69  
  16         41  
302             }
303            
304             # Decode the source text
305 194         389 $keyd = $key;
306             $keyd = decode($self->{"KEY_ENCODING"}, $keyd, $self->{"ENCODE_FAILURE"})
307 194 100 66     872 if exists $self->{"KEY_ENCODING"} && !Encode::is_utf8($key);
308             # Maketext
309 194         5445 $_ = $self->SUPER::maketext($keyd, @param);
310             # Output to the requested encoding
311 185 100 100     8957 if (exists $self->{"ENCODING"}) {
    100 66        
312 74         218 $_ = encode($self->{"ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
313             # Pass through the empty/invalid lexicon
314 111         468 } elsif ( scalar(keys %{$self->{"Lexicon"}}) == 0
315             && exists $self->{"KEY_ENCODING"}
316             && !Encode::is_utf8($key)) {
317 7         24 $_ = encode($self->{"KEY_ENCODING"}, $_, $self->{"ENCODE_FAILURE"});
318             }
319            
320 184         13265 return $_;
321             }
322              
323             # Maketext with context
324             sub pmaketext : method {
325 9     9 1 49 local ($_, %_);
326 9         55 my ($self, $context, $key, @param);
327 9         24 ($self, $context, $key, @param) = @_;
328             # This is not a static method - NOW
329 9 50       27 return if ref($self) eq "";
330             # This is actually a wrapper to the maketext() method
331 9         30 return $self->maketext("$context\x04$key", @param);
332             }
333              
334             # Subroutine to read and parse the MO file
335             # Refer to gettext documentation section 8.3
336             sub read_mo($) {
337 52     52 1 256 local ($_, %_);
338 52         108 my ($mo_file, $len, $FH, $content, $tmpl);
339 52         89 $mo_file = $_[0];
340            
341             # Avoid being stupid
342 52 100 66     2157 return unless -f $mo_file && -r $mo_file;
343             # Read the MO file
344 49         766 $len = (stat $mo_file)[7];
345 49 50       2027 open $FH, $mo_file or return; # GNU gettext never fails!
346 49         244 binmode $FH;
347 49 50       1247 defined($_ = read $FH, $content, $len)
348             or return;
349 49 50       554 close $FH or return;
350            
351             # Find the byte order of the MO file creator
352 49         239 $_ = substr($content, 0, 4);
353             # Little endian
354 49 100       157 if ($_ eq "\xde\x12\x04\x95") {
    100          
355 44         86 $tmpl = "V";
356             # Big endian
357             } elsif ($_ eq "\x95\x04\x12\xde") {
358 3         6 $tmpl = "N";
359             # Wrong magic number. Not a valid MO file.
360             } else {
361 2         12 return;
362             }
363            
364             # Check the MO format revision number
365 47         211 $_ = unpack $tmpl, substr($content, 4, 4);
366             # There is only one revision now: revision 0.
367 47 50       147 return if $_ > 0;
368            
369 47         90 my ($num, $offo, $offt);
370             # Number of messages
371 47         107 $num = unpack $tmpl, substr($content, 8, 4);
372             # Offset to the beginning of the original messages
373 47         107 $offo = unpack $tmpl, substr($content, 12, 4);
374             # Offset to the beginning of the translated messages
375 47         100 $offt = unpack $tmpl, substr($content, 16, 4);
376 47         381 %_ = qw();
377 47         162 for ($_ = 0; $_ < $num; $_++) {
378 688         999 my ($len, $off, $stro, $strt);
379             # The first word is the length of the message
380 688         1284 $len = unpack $tmpl, substr($content, $offo+$_*8, 4);
381             # The second word is the offset of the message
382 688         1217 $off = unpack $tmpl, substr($content, $offo+$_*8+4, 4);
383             # Original message
384 688         1199 $stro = substr($content, $off, $len);
385            
386             # The first word is the length of the message
387 688         1141 $len = unpack $tmpl, substr($content, $offt+$_*8, 4);
388             # The second word is the offset of the message
389 688         1219 $off = unpack $tmpl, substr($content, $offt+$_*8+4, 4);
390             # Translated message
391 688         1242 $strt = substr($content, $off, $len);
392            
393             # Hash it
394 688         2096 $_{$stro} = $strt;
395             }
396            
397 47         663 return %_;
398             }
399              
400             # Method to purge the lexicon cache
401             sub reload_text : method {
402 2     2 1 11 local ($_, %_);
403            
404             # Purge the text cache
405 2         24 %CACHE = qw();
406 2         7 $REREAD_MO = time;
407            
408 2         5 return;
409             }
410              
411             # A wrapper to the fail_with() of Locale::Maketext, in order
412             # to record the preferred failure handler of the user, so that
413             # die_for_lookup_failures() knows where to return to.
414             sub fail_with : method {
415 2     2 1 32 local ($_, %_);
416 2         3 my $self;
417 2         4 ($self, $_) = @_;
418            
419             # This is not a static method
420 2 50       7 return if ref($self) eq "";
421            
422             # Set the current setting
423 2 50       5 if (@_ > 1) {
424 2 50       6 if (defined $_) {
425 2         3 $self->{"USERSET_FAIL"} = $_;
426 2 50       6 $self->SUPER::fail_with($_) if $self->{"DIE_FOR_LOOKUP_FAILURES"};
427             } else {
428 0         0 delete $self->{"USERSET_FAIL"};
429 0 0       0 delete $self->{"fail"} if $self->{"DIE_FOR_LOOKUP_FAILURES"};
430             }
431             }
432            
433             # Return the current setting
434 2 50       7 return exists $self->{"USERSET_FAIL"}? $self->{"USERSET_FAIL"}: undef;
435             }
436              
437             # Whether we should die for lookup failure
438             # The default is no. GNU gettext never fails.
439             sub die_for_lookup_failures : method {
440 41     41 1 175 local ($_, %_);
441 41         91 my $self;
442 41         82 ($self, $_) = @_;
443            
444             # This is not a static method
445 41 50       116 return if ref($self) eq "";
446            
447             # Set the current setting
448 41 50       104 if (@_ > 1) {
449 41 100       89 if ($_) {
450 4         7 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 1;
451 4 100       12 if (exists $self->{"USERSET_FAIL"}) {
452 3         6 $self->{"fail"} = $self->{"USERSET_FAIL"};
453             } else {
454 1         3 delete $self->{"fail"};
455             }
456             } else {
457 37         191 $self->SUPER::fail_with($self->can("failure_handler_auto"));
458 37         319 $self->{"DIE_FOR_LOOKUP_FAILURES"} = 0;
459             }
460             }
461            
462             # Return the current setting
463             return exists $self->{"DIE_FOR_LOOKUP_FAILURES"}?
464 41 50       132 $self->{"DIE_FOR_LOOKUP_FAILURES"}: undef;
465             }
466              
467             # What to do if the text is out of your output encoding
468             # Refer to Encode on possible values of this check
469             sub encode_failure : method {
470 2     2 1 16 local ($_, %_);
471 2         3 my $self;
472 2         4 ($self, $_) = @_;
473            
474             # This is not a static method
475 2 50       7 return if ref($self) eq "";
476            
477             # Specify the action used in the keys
478 2 50       6 $self->{"ENCODE_FAILURE"} = $_ if @_ > 1;
479            
480             # Return the encoding
481 2 50       6 return $self->{"ENCODE_FAILURE"} if exists $self->{"ENCODE_FAILURE"};
482 0         0 return undef;
483             }
484              
485             # Our local version of failure_handler_auto(),
486             # Copied and rewritten from Locale::Maketext, with bug#33938 patch applied.
487             # See https://github.com/Perl/perl5/issues/7767
488             sub failure_handler_auto : method {
489 69     69 1 4198 local ($_, %_);
490 69         160 my ($self, $key, @param, $r);
491 69         162 ($self, $key, @param) = @_;
492            
493             # This is not a static method
494 69 50       169 return if ref($self) eq "";
495            
496             # Remove the context
497             # We assume there is no one using EOF either in the context or message.
498             # That does not work in GNU gettext, anyway.
499 69         227 $key =~ s/^[^\x04]*\x04//;
500            
501 69 100       192 $self->{"failure_lex"} = {} if !exists $self->{"failure_lex"};
502 36         1008 ${$self->{"failure_lex"}}{$key} = $self->_compile($key)
503 69 100       106 if !exists ${$self->{"failure_lex"}}{$key};
  69         283  
504 69         118 $_ = ${$self->{"failure_lex"}}{$key};
  69         141  
505            
506             # A scalar result
507 69 100       367 return $$_ if ref($_) eq "SCALAR";
508 8 50       24 return $_ unless ref($_) eq "CODE";
509             # A compiled subroutine
510             {
511 8         24 local $SIG{"__DIE__"};
  8         28  
512 8         17 $r = eval {
513 8         164 $_ = &$_($self, @param);
514 8         354 return 1;
515             };
516             }
517            
518             # If we make it here, there was an exception thrown in the
519             # call to $value, and so scream:
520 8 50       35 if (!defined $r) {
521 0         0 $_ = $@;
522             # pretty up the error message
523 0         0 s<\s+at\s+\(eval\s+\d+\)\s+line\s+(\d+)\.?\n?>
524 0         0 <\n in bracket code [compiled line $1],>s;
525             Carp::croak "Error in maketexting \"$key\":\n$_ as used";
526             }
527            
528 8         28 # OK
529             return $_;
530             }
531              
532             return 1;
533              
534             __END__