File Coverage

blib/lib/Locale/Maketext/Gettext/Functions.pm
Criterion Covered Total %
statement 236 237 99.5
branch 62 80 77.5
condition 15 21 71.4
subroutine 51 51 100.0
pod 13 13 100.0
total 377 402 93.7


line stmt bran cond sub pod time code
1             # Locale::Maketext::Gettext::Functions - Functional interface to Locale::Maketext::Gettext
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/28
7              
8             package Locale::Maketext::Gettext::Functions;
9 8     8   95780 use 5.008;
  8         66  
10 8     8   283 use strict;
  8         30  
  8         147  
11 8     8   278 use warnings;
  8         34  
  8         191  
12 8     8   294 use base qw(Exporter);
  8         32  
  8         1785  
13             our ($VERSION, @EXPORT, @EXPORT_OK);
14             $VERSION = 0.14;
15             @EXPORT = qw(
16             bindtextdomain textdomain get_handle maketext __ N_
17             dmaketext pmaketext dpmaketext
18             reload_text read_mo encoding key_encoding encode_failure
19             die_for_lookup_failures);
20             @EXPORT_OK = @EXPORT;
21             # Prototype declaration
22             sub bindtextdomain($;$);
23             sub textdomain(;$);
24             sub get_handle(@);
25             sub maketext(@);
26             sub __(@);
27             sub N_(@);
28             sub dmaketext($$@);
29             sub pmaketext($$@);
30             sub dpmaketext($$$@);
31             sub reload_text();
32             sub encoding(;$);
33             sub key_encoding(;$);
34             sub encode_failure(;$);
35             sub die_for_lookup_failures(;$);
36             sub _declare_class($);
37             sub _cat_class(@);
38             sub _init_textdomain($);
39             sub _get_langs($$);
40             sub _get_handle();
41             sub _get_empty_handle();
42             sub _reset();
43             sub _new_rid();
44             sub _k($);
45             sub _lang($);
46              
47 8     8   1450 use Encode qw(encode decode from_to FB_DEFAULT);
  8         23656  
  8         564  
48 8     8   435 use File::Spec::Functions qw(catdir catfile);
  8         33  
  8         248  
49 8     8   2994 use Locale::Maketext::Gettext qw(read_mo);
  8         40  
  8         15048  
50             our (%LOCALEDIRS, %RIDS, %CLASSES, %LANGS);
51             our (%LHS, $_EMPTY, $LH, $DOMAIN, $CATEGORY, $BASE_CLASS, @LANGS, %PARAMS);
52             our (@SYSTEM_LOCALEDIRS);
53             %LHS = qw();
54             # The category is always LC_MESSAGES
55             $CATEGORY = "LC_MESSAGES";
56             $BASE_CLASS = "Locale::Maketext::Gettext::_runtime";
57             # Current language parameters
58             @LANGS = qw();
59             @SYSTEM_LOCALEDIRS = @Locale::Maketext::Gettext::SYSTEM_LOCALEDIRS;
60             %PARAMS = qw();
61             $PARAMS{"KEY_ENCODING"} = "US-ASCII";
62             $PARAMS{"ENCODE_FAILURE"} = FB_DEFAULT;
63             $PARAMS{"DIE_FOR_LOOKUP_FAILURES"} = 0;
64             # Parameters for random class IDs
65             our ($RID_LEN, @RID_CHARS);
66             $RID_LEN = 8;
67             @RID_CHARS = split //,
68             "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
69              
70             # Bind a text domain to a locale directory
71             sub bindtextdomain($;$) {
72 59     58 1 409 local ($_, %_);
73 59         109 my ($domain, $LOCALEDIR);
74 59         282 ($domain, $LOCALEDIR) = @_;
75             # Return the current registry
76 58 50       166 return (exists $LOCALEDIRS{$domain}? $LOCALEDIRS{$domain}: undef)
    100          
77             if !defined $LOCALEDIR;
78             # Register the locale directory
79 57         125 $LOCALEDIRS{$domain} = $LOCALEDIR;
80             # Reinitialize the text domain
81 57         223 _init_textdomain($domain);
82             # Reset the current language handle
83 57 100 100     200 _get_handle() if defined $DOMAIN && $domain eq $DOMAIN;
84             # Return the locale directory
85 57         168 return $LOCALEDIR;
86             }
87              
88             # Set the current text domain
89             sub textdomain(;$) {
90 69     69 1 35978 local ($_, %_);
91 69         115 my ($new_domain);
92 69         134 $new_domain = $_[0];
93             # Return the current text domain
94 69 100       230 return $DOMAIN if !defined $new_domain;
95             # Set the current text domain
96 68         122 $DOMAIN = $new_domain;
97             # Reinitialize the text domain
98 68         172 _init_textdomain($DOMAIN);
99             # Reset the current language handle
100 68         286 _get_handle();
101 68         179 return $DOMAIN;
102             }
103              
104             # Get a language handle
105             sub get_handle(@) {
106 72     72 1 380 local ($_, %_);
107             # Register the current get_handle arguments
108 72         226 @LANGS = @_;
109             # Reset and return the current language handle
110 72         147 return _get_handle();
111             }
112              
113             # Maketext, in its long name
114             # Use @ instead of $@ in prototype, so that we can pass @_ to it.
115             sub maketext(@) {
116 40     40 1 279 return __($_[0], @_[1..$#_]);
117             }
118              
119             # Maketext, in its shortcut name
120             # Use @ instead of $@ in prototype, so that we can pass @_ to it.
121             sub __(@) {
122 103     103   757 local ($_, %_);
123 103         196 my ($key, @param, $keyd);
124 103         220 ($key, @param) = @_;
125             # Reset the current language handle if it is not set yet
126 103 100       285 _get_handle() if !defined $LH;
127            
128             # Decode the source text
129 103         178 $keyd = $key;
130             $keyd = decode($PARAMS{"KEY_ENCODING"}, $keyd, $PARAMS{"ENCODE_FAILURE"})
131 103 50 33     694 if exists $PARAMS{"KEY_ENCODING"} && !Encode::is_utf8($key);
132             # Maketext
133 103         5339 $_ = $LH->maketext($keyd, @param);
134             # Output to the requested encoding
135 103 100 66     254 if (exists $PARAMS{"ENCODING"}) {
    100 33        
136 92         250 $_ = encode($PARAMS{"ENCODING"}, $_, $PARAMS{"ENCODE_FAILURE"});
137             # Pass through the empty/invalid lexicon
138 12         135 } elsif ( scalar(keys %{$LH->{"Lexicon"}}) == 0
139             && exists $PARAMS{"KEY_ENCODING"}
140             && !Encode::is_utf8($key)) {
141 10         38 $_ = encode($PARAMS{"KEY_ENCODING"}, $_, $PARAMS{"ENCODE_FAILURE"});
142             }
143            
144 102         14570 return $_;
145             }
146              
147             # Return the original text untouched, so that it can be cached
148             # with xgettext
149             # Use @ instead of $@ in prototype, so that we can pass @_ to it.
150             sub N_(@) {
151             # Watch out for this Perl magic! :p
152 6 100   6 1 126 return $_[0] unless wantarray;
153 3         17 return @_;
154             }
155              
156             # Maketext in another text domain temporarily,
157             # an equivalent to dgettext().
158             sub dmaketext($$@) {
159 5     5 1 15 local ($_, %_);
160 5         144 my ($domain, $key, @param, $lh0, $domain0, $text);
161 5         18 ($domain, $key, @param) = @_;
162             # Preserve the current status
163 5         12 ($lh0, $domain0) = ($LH, $DOMAIN);
164             # Reinitialize the text domain
165 5         68 textdomain($domain);
166             # Maketext
167 5         28 $text = maketext($key, @param);
168             # Return the current status
169 5         14 ($LH, $DOMAIN) = ($lh0, $domain0);
170             # Return the "made text"
171 5         99 return $text;
172             }
173              
174             # Maketext with context,
175             # an equivalent to pgettext().
176             sub pmaketext($$@) {
177 20     20 1 92 local ($_, %_);
178 20         35 my ($context, $key, @param);
179 20         104 ($context, $key, @param) = @_;
180             # This is actually a wrapper to the maketext() function
181 20         68 return maketext("$context\x04$key", @param);
182             }
183              
184             # Maketext with context in another text domain temporarily,
185             # an equivalent to dpgettext().
186             sub dpmaketext($$$@) {
187 3     3 1 13 local ($_, %_);
188 3         85 my ($domain, $context, $key, @param);
189 3         12 ($domain, $context, $key, @param) = @_;
190             # This is actually a wrapper to the dmaketext() function
191 3         10 return dmaketext($domain, "$context\x04$key", @param);
192             }
193              
194             # Purge the lexicon cache
195             sub reload_text() {
196             # reload_text is static.
197 2     2 1 69 Locale::Maketext::Gettext->reload_text;
198             }
199              
200             # Set the output encoding
201             sub encoding(;$) {
202 17     17 1 71 local ($_, %_);
203 17         33 $_ = $_[0];
204            
205             # Set the output encoding
206 17 100       121 if (@_ > 0) {
207 13 100       45 if (defined $_) {
208 11         58 $PARAMS{"ENCODING"} = $_;
209             } else {
210 3         68 delete $PARAMS{"ENCODING"};
211             }
212 13         32 $PARAMS{"USERSET_ENCODING"} = $_;
213             }
214            
215             # Return the encoding
216 17 100       55 return exists $PARAMS{"ENCODING"}? $PARAMS{"ENCODING"}: undef;
217             }
218              
219             # Set the encoding of the original text
220             sub key_encoding(;$) {
221 3     3 1 91 local ($_, %_);
222 3         12 $_ = $_[0];
223            
224             # Set the encoding used in the keys
225 3 50       9 if (@_ > 0) {
226 3 50       65 if (defined $_) {
227 3         12 $PARAMS{"KEY_ENCODING"} = $_;
228             } else {
229 1         2 delete $PARAMS{"KEY_ENCODING"};
230             }
231             }
232            
233             # Return the encoding
234 3 50       64 return exists $PARAMS{"KEY_ENCODING"}? $PARAMS{"KEY_ENCODING"}: undef;
235             }
236              
237             # What to do if the text is out of your output encoding
238             # Refer to Encode on possible values of this check
239             sub encode_failure(;$) {
240 3     3 1 16 local ($_, %_);
241 3         6 $_ = $_[0];
242             # Set and return the current setting
243 3 50       64 $PARAMS{"ENCODE_FAILURE"} = $_ if @_ > 0;
244             # Return the current setting
245 3         11 return $PARAMS{"ENCODE_FAILURE"};
246             }
247              
248             # Whether we should die for lookup failure
249             # The default is no. GNU gettext never fails.
250             sub die_for_lookup_failures(;$) {
251 1     1 1 2 local ($_, %_);
252 1         76 $_ = $_[0];
253             # Set the current setting
254 1 0       7 if (@_ > 0) {
255 1 0       2 $PARAMS{"DIE_FOR_LOOKUP_FAILURES"} = $_? 1: 0;
256 1         57 $LH->die_for_lookup_failures($PARAMS{"DIE_FOR_LOOKUP_FAILURES"});
257             }
258             # Return the current setting
259             # Resetting the current language handle is not required
260             # Lookup failures are handled by the fail handler directly
261 1         6 return $PARAMS{"DIE_FOR_LOOKUP_FAILURES"};
262             }
263              
264             # Declare a class
265             sub _declare_class($) {
266 94     94   208 local ($_, %_);
267 94         232 $_ = $_[0];
268 94     5   7865 eval << "EOT";
  4     5   33  
  4     5   9  
  4     5   416  
  4     5   29  
  4     1   121  
  4     1   311  
  4     1   27  
  4     1   9  
  4     1   325  
  4     1   29  
  4     1   7  
  4         333  
  4         29  
  4         8  
  4         321  
269             package $_[0];
270             use base qw(Locale::Maketext::Gettext);
271             our (\@ISA, %Lexicon);
272             EOT
273             }
274              
275             # Concatenate the class name
276             sub _cat_class(@) {
277 94     94   374 return join("::", @_);;
278             }
279              
280             # Initialize a text domain
281             sub _init_textdomain($) {
282 124     124   271 local ($_, %_);
283 124         227 my ($domain, $k, @langs, $langs);
284 124         232 $domain = $_[0];
285            
286             # Return if text domain not specified yet
287 124 50       377 return if !defined $domain;
288            
289             # Obtain the available locales
290             # A bound domain
291 124 100       315 if (exists $LOCALEDIRS{$domain}) {
292 118         268 @langs = _get_langs($LOCALEDIRS{$domain}, $domain);
293             # Not bound
294             } else {
295 7         113 @langs = qw();
296             # Search the system locale directories
297 7         28 foreach (@SYSTEM_LOCALEDIRS) {
298 22         55 @langs = _get_langs($_, $domain);
299             # Domain not found in this directory
300 22 100       145 next if @langs == 0;
301 2         12 $LOCALEDIRS{$domain} = $_;
302 2         5 last;
303             }
304             # Not found at last
305 7 100       87 return if !exists $LOCALEDIRS{$domain};
306             }
307 119         650 $langs = join ",", sort @langs;
308            
309             # Obtain the registry key
310 119         306 $k = _k($domain);
311            
312             # Available language list remains for this domain
313 119 100 100     809 return if exists $LANGS{$k} && $LANGS{$k} eq $langs;
314             # Register this new language list
315 20         63 $LANGS{$k} = $langs;
316            
317 20         45 my ($rid, $class);
318             # Garbage collection - drop abandoned language handles
319 20 100       110 if (exists $CLASSES{$k}) {
320 5         160 delete $LHS{$_} foreach grep /^$CLASSES{$k}/, keys %LHS;
321             }
322             # Get a new class ID
323 20         63 $rid = _new_rid();
324             # Obtain the class name
325 20         134 $class = _cat_class($BASE_CLASS, $rid);
326             # Register the domain with this class
327 20         58 $CLASSES{$k} = $class;
328             # Declare this class
329 20         66 _declare_class($class);
330             # Declare its language subclasses
331             _declare_class(_cat_class($class, $_))
332 20         163 foreach @langs;
333            
334 20         80 return;
335             }
336              
337             # Search a locale directory and return the available languages
338             sub _get_langs($$) {
339 139     139   244 local ($_, %_);
340 139         305 my ($dir, $domain, $DH, $entry, $MO_file);
341 139         269 ($dir, $domain) = @_;
342            
343 139         257 @_ = qw();
344             {
345 139 100       268 opendir $DH, $dir or last;
  138         4663  
346 122         2440 while (defined($entry = readdir $DH)) {
347             # Skip hidden entries
348 975 100       3260 next if $entry =~ /^\./;
349             # Skip non-directories
350 731 50       11475 next unless -d catdir($dir, $entry);
351             # Skip locales with dot "." (trailing encoding)
352 731 100       3368 next if $entry =~ /\./;
353             # Get the MO file name
354 726         3739 $MO_file = catfile($dir, $entry, $CATEGORY, "$domain.mo");
355             # Skip if MO file is not available for this locale
356 726 50 66     13946 next if ! -f $MO_file && ! -r $MO_file;
357             # Map C to i_default
358 403 100       1640 $entry = "i_default" if $entry eq "C";
359             # Add this language
360 403         2135 push @_, lc $entry;
361             }
362 122 50       465 close $DH or last;
363             }
364 138         2216 return @_;
365             }
366              
367             # Set the language handle with the current DOMAIN and @LANGS
368             sub _get_handle() {
369 142     143   264 local ($_, %_);
370 142         250 my ($k, $class, $subclass);
371            
372             # Lexicon empty if text domain not specified, or not bound yet
373 142 100 100     598 return _get_empty_handle if !defined $DOMAIN || !exists $LOCALEDIRS{$DOMAIN};
374             # Obtain the registry key
375 124         255 $k = _k($DOMAIN);
376             # Lexicon empty if text domain was not properly set yet
377 124 50       317 return _get_empty_handle if !exists $CLASSES{$k};
378            
379             # Get the localization class name
380 124         236 $class = $CLASSES{$k};
381             # Get the language handle
382 124         751 $LH = $class->get_handle(@LANGS);
383             # Lexicon empty if failed get_handle()
384 124 100       13669 return _get_empty_handle if !defined $LH;
385            
386             # Obtain the subclass name of the got language handle
387 108         315 $subclass = ref($LH);
388             # Use the existing language handle whenever possible, to reduce
389             # the initialization overhead
390 108 100       275 if (exists $LHS{$subclass}) {
391 83         444 $LH = $LHS{$subclass};
392 83 50       215 if (!exists $PARAMS{"USERSET_ENCODING"}) {
393 83 50       187 if (exists $LH->{"MO_ENCODING"}) {
394 83         172 $PARAMS{"ENCODING"} = $LH->{"MO_ENCODING"};
395             } else {
396 0         0 delete $PARAMS{"ENCODING"};
397             }
398             }
399 83         187 return _lang($LH)
400             }
401            
402             # Initialize it
403 25         152 $LH->bindtextdomain($DOMAIN, $LOCALEDIRS{$DOMAIN});
404 25         139 $LH->textdomain($DOMAIN);
405             # Respect the MO file encoding unless there is a user preference
406 25 50       76 if (!exists $PARAMS{"USERSET_ENCODING"}) {
407 25 100       65 if (exists $LH->{"MO_ENCODING"}) {
408 24         63 $PARAMS{"ENCODING"} = $LH->{"MO_ENCODING"};
409             } else {
410 1         3 delete $PARAMS{"ENCODING"};
411             }
412             }
413             # We handle the encoding() and key_encoding() ourselves.
414 25         155 $LH->key_encoding(undef);
415 25         193 $LH->encoding(undef);
416             # Register it
417 25         57 $LHS{$subclass} = $LH;
418            
419 25         66 return _lang($LH);
420             }
421              
422             # Obtain the empty language handle
423             sub _get_empty_handle() {
424 34     35   71 local ($_, %_);
425 34 100       86 if (!defined $_EMPTY) {
426 2         30 $_EMPTY = Locale::Maketext::Gettext::Functions::_EMPTY->get_handle;
427 2         16 $_EMPTY->key_encoding(undef);
428 2         12 $_EMPTY->encoding(undef);
429             }
430 34         59 $LH = $_EMPTY;
431 34         160 $LH->die_for_lookup_failures($PARAMS{"DIE_FOR_LOOKUP_FAILURES"});
432 34         86 return _lang($LH);
433             }
434              
435             # Initialize everything
436             sub _reset() {
437 55     56   23505 local ($_, %_);
438            
439 55         165 %LOCALEDIRS = qw();
440 55         122 undef $LH;
441 55         104 undef $DOMAIN;
442 55         113 @LANGS = qw();
443 55         148 %PARAMS = qw();
444 55         144 $PARAMS{"KEY_ENCODING"} = "US-ASCII";
445 55         101 $PARAMS{"ENCODE_FAILURE"} = FB_DEFAULT;
446 55         97 $PARAMS{"DIE_FOR_LOOKUP_FAILURES"} = 0;
447            
448 55         169 return;
449             }
450              
451             # Generate a new random ID
452             sub _new_rid() {
453 19     20   41 local ($_, %_);
454 19         30 my ($id);
455            
456             do {
457 19         73 for ($id = "", $_ = 0; $_ < $RID_LEN; $_++) {
458 152         565 $id .= $RID_CHARS[int rand scalar @RID_CHARS];
459             }
460 19         35 } while exists $RIDS{$id};
461 19         58 $RIDS{$id} = 1;
462            
463 19         57 return $id;
464             }
465              
466             # Build the key for the domain registry
467             sub _k($) {
468 242     243   983 return join "\n", $LOCALEDIRS{$_[0]}, $CATEGORY, $_[0];
469             }
470              
471             # The language from a language handle. language_tag is not quite sane.
472             sub _lang($) {
473 142     143   259 local ($_, %_);
474 142         227 $_ = $_[0];
475 142         314 $_ = ref($_);
476 142         701 s/^.+:://;
477 142         380 s/_/-/g;
478 142         434 return $_;
479             }
480              
481             # Public empty lexicon
482             package Locale::Maketext::Gettext::Functions::_EMPTY;
483 8     7   370 use 5.008;
  7         41  
484 7     7   43 use strict;
  7         235  
  7         205  
485 7     7   37 use warnings;
  7         152  
  7         218  
486 7     7   49 use base qw(Locale::Maketext::Gettext);
  7         148  
  7         613  
487             our $VERSION = 0.01;
488              
489             package Locale::Maketext::Gettext::Functions::_EMPTY::i_default;
490 7     7   196 use 5.008;
  7         163  
491 7     7   46 use strict;
  7         15  
  7         233  
492 7     7   40 use warnings;
  7         24  
  7         330  
493 7     7   45 use base qw(Locale::Maketext::Gettext);
  7         15  
  7         738  
494             our $VERSION = 0.01;
495              
496             return 1;
497              
498             __END__