File Coverage

blib/lib/WWW/Google/Translate.pm
Criterion Covered Total %
statement 118 156 75.6
branch 32 74 43.2
condition 9 23 39.1
subroutine 16 16 100.0
pod 3 4 75.0
total 178 273 65.2


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