File Coverage

blib/lib/WWW/Google/Translate.pm
Criterion Covered Total %
statement 124 176 70.4
branch 32 82 39.0
condition 10 26 38.4
subroutine 18 18 100.0
pod 3 4 75.0
total 187 306 61.1


line stmt bran cond sub pod time code
1             package WWW::Google::Translate;
2              
3             our $VERSION = '0.09';
4              
5 1     1   12947 use strict;
  1         1  
  1         21  
6 1     1   3 use warnings;
  1         1  
  1         19  
7             {
8 1     1   3 use Carp;
  1         3  
  1         51  
9 1     1   453 use URI;
  1         4554  
  1         20  
10 1     1   4 use File::Spec;
  1         2  
  1         16  
11 1     1   609 use JSON qw( from_json );
  1         8428  
  1         2  
12 1     1   613 use LWP::UserAgent;
  1         24848  
  1         44  
13 1     1   5 use HTTP::Status qw( HTTP_BAD_REQUEST );
  1         1  
  1         94  
14 1     1   449 use Readonly;
  1         2538  
  1         43  
15 1     1   507 use English qw( -no_match_vars $EVAL_ERROR $OS_ERROR );
  1         2589  
  1         6  
16 1     1   653 use Data::Dumper;
  1         4555  
  1         1303  
17             }
18              
19             my ( $REST_HOST, $REST_URL, $CONSOLE_URL, %SIZE_LIMIT_FOR, $TEMP_FILE );
20             {
21             Readonly $REST_HOST => 'www.googleapis.com';
22             Readonly $REST_URL => "https://$REST_HOST/language/translate/v2";
23             Readonly $CONSOLE_URL => "https://code.google.com/apis/console";
24             Readonly %SIZE_LIMIT_FOR => (
25             translate => 2000, # google states 2K but observed results vary
26             detect => 2000,
27             languages => 9999, # N/A
28             );
29             Readonly $TEMP_FILE => 'www-google-translate.dat';
30             }
31              
32             sub new {
33 3     3 1 49 my ( $class, $param_rh ) = @_;
34              
35 3         28 my %self = (
36             key => 0,
37             format => 0,
38             prettyprint => 0,
39             default_source => 0,
40             default_target => 0,
41             data_format => 'perl',
42             timeout => 60,
43             force_post => 0,
44             rest_url => $REST_URL,
45             agent => ( sprintf '%s/%s', __PACKAGE__, $VERSION ),
46             cache_results => 0,
47             headers => {},
48             );
49              
50 3         23 for my $property ( keys %self ) {
51              
52 36 100       44 if ( exists $param_rh->{$property} ) {
53              
54 3   50     12 my $type = ref $param_rh->{$property} || 'String';
55 3   50     9 my $expected_type = ref $self{$property} || 'String';
56              
57 3 50       6 croak "$property should be a $expected_type"
58             if $expected_type ne $type;
59              
60 3         6 $self{$property} = delete $param_rh->{$property};
61             }
62             }
63              
64 3         5 for my $property ( keys %{$param_rh} ) {
  3         5  
65              
66 0         0 carp "$property is not a supported parameter";
67             }
68              
69 3         4 for my $default (qw( cache_results default_source default_target )) {
70              
71 9 50       13 if ( !$self{$default} ) {
72              
73 9         10 delete $self{$default};
74             }
75             }
76              
77 3 50       5 if ( exists $self{cache_results} ) {
78              
79 0         0 my $tmpdir = File::Spec->tmpdir();
80              
81 0 0       0 if ($tmpdir) {
82              
83 0         0 $self{cache_rh} = {};
84 0         0 $self{cache_file} = File::Spec->catfile( $tmpdir, $TEMP_FILE );
85              
86 0 0       0 if ( stat $self{cache_file} ) {
87              
88             croak $self{cache_file}, ' is not writable'
89 0 0       0 if !-w $self{cache_file};
90              
91             croak $self{cache_file}, ' is not readable'
92 0 0       0 if !-r $self{cache_file};
93              
94 0         0 $self{cache_rh} = do $self{cache_rh};
95             }
96             }
97             else {
98              
99 0         0 carp 'unable to find a writable temp directory';
100             }
101             }
102              
103             croak "key is a required parameter"
104 3 50       7 if !$self{key};
105              
106             croak "data_format must either be Perl or JSON"
107 3 50       13 if $self{data_format} !~ m{\A (?: perl|json ) \z}xmsi;
108              
109 3         11 $self{ua} = LWP::UserAgent->new();
110 3         2138 $self{ua}->agent( delete $self{agent} );
111              
112 3 50       94 if ( keys %{ $self{headers} } )
  3         10  
113             {
114 0         0 $self{ua}->default_header( %{ $self{headers} } );
  0         0  
115             }
116              
117 3         8 return bless \%self, $class;
118             }
119              
120             sub translate {
121 1     1 1 5 my ( $self, $arg_rh ) = @_;
122              
123             croak 'q is a required parameter'
124 1 50       3 if !exists $arg_rh->{q};
125              
126 1         1 my $result;
127              
128 1 50       3 if ( $arg_rh->{q} ) {
129              
130 1   33     3 $arg_rh->{source} ||= $self->{default_source};
131 1   33     2 $arg_rh->{target} ||= $self->{default_target};
132              
133 1         3 $self->{default_source} = $arg_rh->{source};
134 1         12 $self->{default_target} = $arg_rh->{target};
135              
136 1         5 my %is_supported = (
137             format => 1,
138             prettyprint => 1,
139             q => 1,
140             source => 1,
141             target => 1,
142             );
143              
144 5         7 my @unsupported = grep { !exists $is_supported{$_} }
145 1         1 keys %{$arg_rh};
  1         3  
146              
147 1 50       3 croak "unsupported parameters: ", ( join ',', @unsupported )
148             if @unsupported;
149              
150 1 50       3 if ( !exists $arg_rh->{prettyprint} ) {
151              
152 0 0       0 if ( $self->{prettyprint} ) {
153              
154 0         0 $arg_rh->{prettyprint} = $self->{prettyprint};
155             }
156             }
157              
158 1 50       3 if ( !exists $arg_rh->{format} ) {
159              
160 0 0       0 if ( $self->{format} ) {
    0          
161              
162 0         0 $arg_rh->{format} = $self->{format};
163             }
164             elsif ( $arg_rh->{q} =~ m{ < [^>]+ > }xms ) {
165              
166 0         0 $arg_rh->{format} = 'html';
167             }
168             else {
169              
170 0         0 $arg_rh->{format} = 'text';
171             }
172             }
173              
174 1         1 my $cache_key;
175              
176 1 50       3 if ( exists $self->{cache_rh} ) {
177              
178             $cache_key
179             = join ',',
180 0         0 map { $arg_rh->{$_} }
181 0         0 sort grep { exists $arg_rh->{$_} }
  0         0  
182             keys %is_supported;
183              
184             return $self->{cache_rh}->{$cache_key}
185 0 0       0 if exists $self->{cache_rh}->{$cache_key};
186             }
187              
188 1         2 $result = $self->_rest( 'translate', $arg_rh );
189              
190 1 50       3 if ($cache_key) {
191              
192 0         0 $self->{cache_rh}->{$cache_key} = $result;
193              
194 0         0 my $count = keys %{ $self->{cache_rh} };
  0         0  
195              
196 0 0       0 if ( $count % 10 == 0 ) {
197              
198 0         0 $self->_store_cache();
199             }
200             }
201             }
202              
203 1         2 return $result;
204             }
205              
206             sub languages {
207 1     1 0 10 my ( $self, $arg_rh ) = @_;
208              
209             croak 'target is a required parameter'
210 1 50       2 if !exists $arg_rh->{target};
211              
212 1         1 my $result;
213              
214 1 50       3 if ( $arg_rh->{target} ) {
215              
216 1         1 my @unsupported = grep { $_ ne 'target' } keys %{$arg_rh};
  1         3  
  1         2  
217              
218 1 50       2 croak "unsupported parameters: ", ( join ',', @unsupported )
219             if @unsupported;
220              
221 1         2 $result = $self->_rest( 'languages', $arg_rh );
222             }
223              
224 1         2 return $result;
225             }
226              
227             sub detect {
228 1     1 1 5 my ( $self, $arg_rh ) = @_;
229              
230             croak 'q is a required parameter'
231 1 50       3 if !exists $arg_rh->{q};
232              
233 1         1 my $result;
234              
235 1 50       3 if ( $arg_rh->{q} ) {
236              
237 1         1 my @unsupported = grep { $_ ne 'q' } keys %{$arg_rh};
  1         2  
  1         2  
238              
239 1 50       2 croak "unsupported parameters: ", ( join ',', @unsupported )
240             if @unsupported;
241              
242 1         2 $result = $self->_rest( 'detect', $arg_rh );
243             }
244              
245 1         2 return $result;
246             }
247              
248             sub _rest {
249 3     3   5 my ( $self, $operation, $arg_rh ) = @_;
250              
251             my $url
252             = $operation eq 'translate'
253             ? $self->{rest_url}
254 3 100       8 : $self->{rest_url} . "/$operation";
255              
256 3         3 my $force_post = $self->{force_post};
257              
258             my %form = (
259             key => $self->{key},
260 3         4 %{$arg_rh},
  3         8  
261             );
262              
263 3 50 66     11 if ( exists $arg_rh->{source} && !$arg_rh->{source} ) {
264              
265 0         0 delete $form{source};
266 0         0 delete $arg_rh->{source};
267             }
268              
269 3 100       6 my $byte_size = exists $form{q} ? length $form{q} : 0;
270 3         11 my $get_size_limit = $SIZE_LIMIT_FOR{$operation};
271              
272 3         17 my ( $method, $response );
273              
274 3 50 33     12 if ( $force_post || $byte_size > $get_size_limit ) {
275              
276 0         0 $method = 'POST';
277              
278             $response = $self->{ua}->post(
279 0         0 $url,
280             'X-HTTP-Method-Override' => 'GET',
281             'Content' => \%form
282             );
283             }
284             else {
285              
286 3         1 $method = 'GET';
287              
288 3         8 my $uri = URI->new($url);
289              
290 3         6020 $uri->query_form( \%form );
291              
292 3         257 $response = $self->{ua}->get($uri);
293             }
294              
295 3   50     15 my $json = $response->content() || "";
296              
297 3         12 my ($message) = $json =~ m{ "message" \s* : \s* "( [^"]+ )" }xms;
298              
299 3   33     10 $message ||= $response->status_line();
300              
301 3 50       10 if ( $response->code() == HTTP_BAD_REQUEST ) {
    50          
302              
303 0         0 my $dump = join ",\n", map {"$_ => $arg_rh->{$_}"} keys %{$arg_rh};
  0         0  
  0         0  
304              
305 0         0 warn "request failed: $dump\n";
306              
307 0         0 require Sys::Hostname;
308              
309 0   0     0 my $host = Sys::Hostname::hostname() || 'this machine';
310 0         0 $host = uc $host;
311              
312 0         0 die "unsuccessful $operation $method for $byte_size bytes: ",
313             $message,
314             "\n",
315             "check that $host is has API Access for this API key",
316             "\n",
317             "at $CONSOLE_URL\n";
318             }
319             elsif ( !$response->is_success() ) {
320              
321 0         0 croak "unsuccessful $operation $method ",
322             "for $byte_size bytes, message: $message\n";
323             }
324              
325             return $json
326 3 50       22 if 'json' eq lc $self->{data_format};
327              
328 3         4 $json =~ s{ NaN }{-1}xmsg; # prevent from_json failure
329              
330 3         3 my $trans_rh;
331              
332 3         3 eval { $trans_rh = from_json( $json, { utf8 => 1 } ); };
  3         7  
333              
334 3 50       64 if ($EVAL_ERROR) {
335 0         0 warn "$json\n$EVAL_ERROR";
336 0         0 return $json;
337             }
338              
339 3         11 return $trans_rh;
340             }
341              
342             sub _store_cache {
343 3     3   3 my ($self) = @_;
344              
345             return
346 3 50 33     8 if !exists $self->{cache_rh} || !exists $self->{cache_file};
347              
348 0         0 my $fh;
349              
350             open $fh, '>', $self->{cache_file}
351 0 0       0 or die 'open ', $self->{cache_file}, ": $OS_ERROR";
352              
353 0         0 local $Data::Dumper::Terse = 1;
354 0         0 local $Data::Dumper::Indent = 1;
355 0         0 local $Data::Dumper::Quotekeys = 0;
356 0         0 local $Data::Dumper::Sortkeys = 1;
357              
358 0         0 print {$fh} Dumper( $self->{cache_rh} )
359 0 0       0 or die 'print ', $self->{cache_file}, ": $OS_ERROR";
360              
361             close $fh
362 0 0       0 or die 'close ', $self->{cache_file}, ": $OS_ERROR";
363              
364 0         0 return 1;
365             }
366              
367             sub DESTROY {
368 3     3   1318 my ($self) = @_;
369              
370 3         4 $self->_store_cache();
371              
372 3         61 return;
373             }
374              
375             1;