File Coverage

blib/lib/Locale/Utils/PluralForms.pm
Criterion Covered Total %
statement 40 47 85.1
branch 4 6 66.6
condition 2 6 33.3
subroutine 8 9 88.8
pod n/a
total 54 68 79.4


line stmt bran cond sub pod time code
1             package Locale::Utils::PluralForms; ## no critic (TidyCode)
2              
3 6     6   600163 use Moose;
  6         3606498  
  6         70  
4 6     6   60707 use MooseX::StrictConstructor;
  6         227366  
  6         39  
5              
6 6     6   87550 use namespace::autoclean;
  6         51  
  6         57  
7              
8 6     6   4020 use English qw(-no_match_vars $EVAL_ERROR);
  6         25917  
  6         56  
9 6     6   4230 use HTML::Entities qw(decode_entities);
  6         48541  
  6         6827  
10             require LWP::UserAgent;
11             require Safe;
12              
13             our $VERSION = '0.002';
14              
15             has language => (
16             is => 'rw',
17             isa => 'Str',
18             trigger => \&_language,
19             );
20              
21             has _all_plural_forms_url => (
22             is => 'rw',
23             isa => 'Str',
24             default => 'http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html',
25             );
26              
27             has _all_plural_forms_html => (
28             is => 'rw',
29             isa => 'Str',
30             default => \&_get_all_plural_forms_html,
31             lazy => 1,
32             clearer => 'clear_all_plural_forms_html',
33             );
34              
35             has all_plural_forms => (
36             is => 'rw',
37             isa => 'HashRef',
38             default => \&_get_all_plural_forms,
39             lazy => 1,
40             );
41              
42             has plural_forms => (
43             is => 'rw',
44             isa => 'Str',
45             default => 'nplurals=1; plural=0',
46             lazy => 1,
47             trigger => \&_calculate_plural_forms,
48             );
49              
50             has nplurals => (
51             is => 'rw',
52             isa => 'Int',
53             default => 1,
54             lazy => 1,
55             init_arg => undef,
56             writer => '_nplurals',
57             );
58              
59             has plural_code => (
60             is => 'rw',
61             isa => 'CodeRef',
62             default => sub { return sub { return 0 } },
63             lazy => 1,
64             init_arg => undef,
65             writer => '_plural_code',
66             );
67              
68             sub _get_all_plural_forms_html {
69 0     0   0 my $self = shift;
70              
71 0         0 my $url = $self->_all_plural_forms_url;
72 0         0 my $ua = LWP::UserAgent->new;
73 0         0 $ua->env_proxy;
74 0         0 my $response = $ua->get($url);
75 0 0       0 $response->is_success
76             or confess "$url $response->status_line";
77              
78 0         0 return $response->decoded_content;
79             }
80              
81             sub _get_all_plural_forms {
82 1     1   4 my $self = shift;
83              
84             ## no critic (ComplexRegexes)
85 1         46 my @match = $self->_all_plural_forms_html =~ m{
86             <tr \s+ class="row- (?: even | odd ) ">
87             \s*
88             <td> ( [^<>]+ ) </td>
89             \s*
90             <td> ( [^<>]+ ) .*? </td>
91             \s*
92             <td> ( nplurals [^<>]+? ) [;]? </td>
93             \s*
94             </tr>
95             }xmsg;
96             ## use critic(ComplexRegexes)
97 1         56 $self->clear_all_plural_forms_html;
98 1         2 my %all_plural_forms;
99 1         10 while ( my ($iso, $english_name, $plural_forms) = splice @match, 0, 3 ) { ## no critic (MagicNumbers)
100 3         18 $english_name =~ s{ \s+ \z }{}xms;
101 3         96 $all_plural_forms{ decode_entities($iso) } = {
102             english_name => decode_entities($english_name),
103             plural_forms => decode_entities($plural_forms),
104             };
105             }
106              
107 1         48 return \%all_plural_forms;
108             }
109              
110             sub _language {
111 4     4   17 my ($self, $language) = @_;
112              
113 4         194 my $all_plural_forms = $self->all_plural_forms;
114 4 100       18 if ( exists $all_plural_forms->{$language} ) {
115             return $self->plural_forms(
116             $all_plural_forms->{$language}->{plural_forms}
117 2         105 );
118             }
119 2         12 $language =~ s{_ .* \z}{}xms;
120 2 100       11 if ( exists $all_plural_forms->{$language} ) {
121             return $self->plural_forms(
122             $all_plural_forms->{$language}->{plural_forms}
123 1         51 );
124             }
125              
126 1         25 return confess
127             "Missing plural forms for language $language in all_plural_forms";
128             }
129              
130             sub _calculate_plural_forms {
131 6     6   18 my $self = shift;
132              
133 6         290 my $plural_forms = $self->plural_forms;
134 6         121 $plural_forms =~ s{\b ( nplurals | plural | n ) \b}{\$$1}xmsg;
135 6         56 my $safe = Safe->new;
136 6         9237 my $nplurals_code = <<"EOC";
137             my \$n = 0;
138             my (\$nplurals, \$plural);
139             $plural_forms;
140             \$nplurals;
141             EOC
142 6   33     34 $self->_nplurals(
143             $safe->reval($nplurals_code)
144             or confess
145             "Code of Plural-Forms $plural_forms is not safe, $EVAL_ERROR"
146             );
147 6         32 my $plural_code = <<"EOC";
148             sub {
149             my \$n = shift;
150             my (\$nplurals, \$plural);
151             $plural_forms;
152             return \$plural || 0;
153             }
154             EOC
155 6   33     31 $self->_plural_code(
156             $safe->reval($plural_code)
157             or confess "Code $plural_forms is not safe, $EVAL_ERROR"
158             );
159              
160 6         1032 return $self;
161             }
162              
163             __PACKAGE__->meta->make_immutable;
164              
165             1;
166              
167             __END__
168              
169             =head1 NAME
170              
171             Locale::Utils::PluralForms - Utils to use plural forms
172              
173             $Id: PluralForms.pm 382 2011-11-13 13:20:22Z steffenw $
174              
175             $HeadURL: https://perl-gettext-oo.svn.sourceforge.net/svnroot/perl-gettext-oo/Locale-Utils-PluralForms/trunk/lib/Locale/Utils/PluralForms.pm $
176              
177             =head1 VERSION
178              
179             0.002
180              
181             =head1 SYNOPSIS
182              
183             use Locale::Utils::PluralForms;
184              
185             my $obj = Locale::Utils::PluralForms->new;
186              
187             Data downloaded from web
188              
189             $obj = Locale::Utils::PluralForms->new(
190             language => 'en_GB', # fallbacks from given en_GB to en
191             );
192              
193             Data of given data structure
194              
195             $obj = Locale::Utils::PluralForms->new(
196             all_plural_forms => {
197             'en' => {
198             english_name => 'English',
199             plural_forms => 'nplurals=2; plural=(n != 1)',
200             },
201             # next languages
202             },
203             );
204              
205             Getter
206              
207             my $language = $obj->language;
208             my $all_plural_forms = $obj->all_plural_forms;
209             my $plural_forms = $obj->plural_forms;
210             my $nplurals = $obj->nplurals;
211             my $plural_code = $obj->plural_code;
212             my $plural = $obj->plural_code->($number);
213              
214             =head1 DESCRIPTION
215              
216             =head2 Find plural forms for the language
217              
218             This module helps to find the plural forms for all languages.
219             It downloads the plural forms for all languages from web.
220             Then it stores the extracted data
221             into a data structure named "all_plural_forms".
222              
223             It is possible to fill that data structure
224             before method "language" is called first time
225             or to cache after first method call "language".
226              
227             =head2 "plural" as subroutine
228              
229             In the header of a PO- or MO-File
230             is an entry is called "Plural-Forms".
231             How many plural forms the language has, is described there in "nplurals".
232             The second Information in "Plural-Forms" describes as a formula,
233             how to choose the "plural".
234              
235             This module compiles plural forms
236             to a code references in a secure way.
237              
238             =head1 SUBROUTINES/METHODS
239              
240             =head2 Find plural forms for the language
241              
242             =head3 method language
243              
244             Set the language to switch to the plural forms of that language.
245             "plural_forms" is set then and "nplurals" and "plural_code" will be calculated.
246              
247             $obj->language('de_AT'); # A fallback finds plural forms of language de
248             # because de_AT is not different.
249              
250             Read the language back.
251              
252             $obj->language eq 'de_AT';
253              
254             =head3 method all_plural_forms
255              
256             Set the data structure.
257              
258             $obj->all_plural_forms({
259             'de' => {
260             english_name => 'German',
261             plural_forms => 'nplurals=2; plural=(n != 1)',
262             },
263             # next languages
264             });
265              
266             Read the data structure back.
267              
268             my $hash_ref = $obj->all_plural_forms;
269              
270             =head2 executable plural forms
271              
272             =head3 method plural_forms
273              
274             Set "plural_forms" if no "language" is set.
275             After that "nplurals" and "plural_code" will be calculated in a safe way.
276              
277             $obj->plural_forms('nplurals=1; plural=0');
278              
279             Or read it back.
280              
281             my $plural_forms = $obj->plural_forms;
282              
283             =head3 method nplurals
284              
285             This method get back the calculated count of plurals.
286              
287             If no "language" and no "plural_forms" is set,
288             the defaults for "nplurals" is:
289              
290             my $count = $obj->nplurals # returns: 1
291              
292             There is no public setter for "nplurals"
293             and it is not possible to set them in the constructor.
294             Call method "language" or "plural_forms"
295             or set attribute "language" or "plural_forms" in the constructor.
296             After that "nplurals" will be calculated automaticly and safe.
297              
298             =head2 method plural_code
299              
300             This method get back the calculated code for the "plural"
301             to choose the correct "plural".
302              
303             If no "language" and no "plural_forms" is set,
304             the defaults for plural_code is:
305              
306             my $code_ref = $obj->plural_code # returns: sub { return 0 }
307             my $plural = $obj->plural_code->($number); # returns 0
308              
309             There is no public setter for "plural_code"
310             and it is not possible to set them in the constructor.
311             Call method "language" or "plural_forms"
312             or set attribute "language" or "plural_forms" in the constructor.
313             After that "plural_code" will be calculated automaticly and safe.
314              
315             For the example plural forms C<'nplurals=2; plural=(n != 1)'>:
316              
317             $plural = $obj->plural_code->(0), # $plural is 1
318             $plural = $obj->plural_code->(1), # $plural is 0
319             $plural = $obj->plural_code->(2), # $plural is 1
320             $plural = $obj->plural_code->(3), # $plural is 1
321             ...
322              
323             =head1 EXAMPLE
324              
325             Inside of this distribution is a directory named example.
326             Run the *.pl files.
327              
328             =head1 DIAGNOSTICS
329              
330             none
331              
332             =head1 CONFIGURATION AND ENVIRONMENT
333              
334             none
335              
336             =head1 DEPENDENCIES
337              
338             L<Moose|Moose>
339              
340             L<MooseX::StrictConstructor|MooseX::StrictConstructor>
341              
342             L<namespace::autoclean|namespace::autoclean>
343              
344             L<English|English>
345              
346             L<HTML::Entities|HTML::Entities>
347              
348             L<LWP::UserAgent|LWP::UserAgent>
349              
350             L<Safe|Safe>
351              
352             =head1 INCOMPATIBILITIES
353              
354             not known
355              
356             =head1 BUGS AND LIMITATIONS
357              
358             not known
359              
360             =head1 SEE ALSO
361              
362             L<http://en.wikipedia.org/wiki/Gettext>
363              
364             L<http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html>
365              
366             L<Locele::TextDomain|Locele::TextDomain>
367              
368             =head1 AUTHOR
369              
370             Steffen Winkler
371              
372             =head1 LICENSE AND COPYRIGHT
373              
374             Copyright (c) 2011 - 2017,
375             Steffen Winkler
376             C<< <steffenw at cpan.org> >>.
377             All rights reserved.
378              
379             This module is free software;
380             you can redistribute it and/or modify it
381             under the same terms as Perl itself.