File Coverage

blib/lib/Net/Moo.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1 6     6   9125 use strict;
  6         14  
  6         301  
2              
3             package Net::Moo;
4 6     6   38 use base qw(LWP::UserAgent);
  6         12  
  6         10173  
5              
6             $Net::Moo::VERSION = '0.11';
7              
8             =head1 NAME
9              
10             Net::Moo - OOP wrapper for the Moo.com API
11              
12             =head1 SYNOPSIS
13              
14             use Net::Moo;
15              
16             my $moo = Net::Moo->new();
17             my $rsp = $moo->api_call('build', 'stickers', \@designs);
18              
19             print $rsp->findvalue("start_url");
20              
21             # Or, if you're feeling verbose...
22              
23             use Net::Moo;
24             use Net::Moo::Validate;
25              
26             my $xml = $moo->builder('stickers', \@designs);
27              
28             my $vld = Net::Moo::Validate->new();
29             my $rpt = $vld->report_errors($xml)
30              
31             if ($vld->is_valid_xml($rpt)){
32             my $res = $moo->execute_request($xml);
33             my $rsp = $moo->parse_response($res);
34             print $rsp->findvalue("start_url");
35             }
36              
37             =head1 DESCRIPTION
38              
39             Net::Moo is an OOP wrapper for the Moo.com API.
40              
41             =head1 OPTIONS
42              
43             Options are passed to Net::Moo using a Config::Simple object or
44             a valid Config::Simple config file. Options are grouped by "block".
45              
46             =head2 moo
47              
48             =over 4
49              
50             =item * B
51              
52             String. I
53              
54             A valid Moo API key.
55              
56             =item * B
57              
58             Boolean.
59              
60             Indicates whether product requests should be validated before they are
61             submitted to the Moo API for processing.
62              
63             Default is false.
64              
65             =back
66              
67             =head1 LOGGING AND ERROR HANDLING
68              
69             All errors are logged using the object's I method which dispatches notices
70             to an internal I object. By default, only error messages are logged
71             to STDERR.
72              
73             =head1 METHODS, PRODUCTS AND DESIGNS
74              
75             This section describes the various arguments passed to the I method as well
76             as the various other helper methods that it calls to generate requests to the Moo API.
77              
78             =head2 Methods
79              
80             =over 4
81              
82             =item * B
83              
84             For submitting a list of images that the user can then modify to make before placing
85             an order.
86              
87             =item * B
88              
89             For submitting a list of images that will be used to make place and order.
90              
91             =back
92              
93             =head2 Products
94              
95             Whatever the Moo API supports. As of this writing, this includes :
96              
97             =over 4
98              
99             =item * B
100              
101             Small cards.
102              
103             =item * B
104              
105             Square cards.
106              
107             =item * B
108              
109             Sticky cards. Small ones.
110              
111             =item * B
112              
113             Cards from the past, to the future.
114              
115             =item * B
116              
117             OH HAI.
118              
119             =head2 Designs
120              
121             Designs are the list of images and any formatting used when placing and order. Designs
122             are passed in as an array reference of hash references, with the following keys :
123              
124             =over 4
125              
126             =item * B
127              
128             The URL for the image. Really, the only thing you sort of have to include.
129              
130             =item * B
131              
132             There's not much point in passing this at the moment as the API docs indicated
133             its value must always be B for now.
134              
135             =item * B
136              
137             Defines how an image will be cropped. Possible values are B and B.
138              
139             Default is I.
140              
141             =item * B
142              
143             Required only if the I args is set to B, it is a hash ref containing
144             the following keys :
145              
146             =over 4
147              
148             =item * B
149              
150             The top left (x) co-ordinate of the cropping rectangle in pixels.
151              
152             =item * B
153              
154             The top left (y) co-ordinate of the cropping rectangle in pixels.
155              
156             =item * B
157              
158             The width of the cropping rectangle in pixels.
159              
160             =item * B
161              
162             The height of the cropping rectangle in pixels.
163              
164             =back
165              
166             =item * B
167              
168             The text for the back of a card. This is also an array reference of hash references,
169             each with the following keys :
170              
171             =over 4
172              
173             =item * B
174              
175             The id of the text line that tells us where to place it on the back of the card. The IDs
176             allowed are defined in the schema document. Links to examples of where the ids go are below.
177              
178             =item * B
179              
180             The text you want to add. As a general rule, if the id is a number, then the string can only
181             be on one line.
182              
183             =item * B
184              
185             This can either be (the string literals) 'true' or 'false'. If ommitted, the default is 'false'
186              
187             =item * B
188              
189             This is either left, right or center. Some products only allow a subset of these (such as
190             greetingcard). The default value is left unless otherwise stated in the schema.
191              
192             =item * B
193              
194             You can specify one of three fonts: modern (arial/helvetica), traditional (georgia) and
195             typewriter (courier). The default is modern.
196              
197             =item * B
198              
199             A hexidecimal string (with the #) for the colour of the line of text. The default is
200             #000000 (i.e. black).
201              
202             =back
203              
204             =item * B
205              
206             But wait! There's more!! When you are specifying text for greeting card products, it gets
207             a little more involved.
208              
209             Rather than pass an array of hash references, you need to pass a hash of arrays of hash
210             references where the top level keys represent the page on which the text will be placed. (See
211             the examples section below.)
212              
213             Valid keys are :
214              
215             =over 4
216              
217             =item * B
218              
219             Contains an array reference of hash references whose keys (described above) may be : 'string',
220             'align', 'font', 'colour'
221              
222             =item * B
223              
224             Contains an array reference of hash references whose keys (described above) may be : 'id',
225             'string', 'bold', 'align', 'font', 'colour'
226              
227             =back
228              
229             =back
230              
231             =head2 Examples
232              
233             More examples are available in the tests (./t) directory for this package but here's
234             an idea of how you specify a list of "designs" :
235              
236             my @minicards = ({'url' => 'http://farm3.static.flickr.com/2300/2179038972_23d2a1ff40_o.jpg',
237             'text' => [{'id' => 1, 'string' => 'Bold / left / modern / red', 'bold' => 'true', 'align' => 'left', 'font' => 'modern', 'colour' => '#ff0000'},
238             {'id' => 2, 'string' => 'normal / center / traditional / green', 'bold' => 'false', 'align' => 'center', 'font' => 'traditional', 'colour' => '#00ff00'},
239             {'id' => 3, 'string' => 'bold / right / typewriter / blue', 'bold' => 'true', 'align' => 'right', 'font' => 'typewriter', 'colour' => '#0000ff'},
240             {'id' => 4, 'string' => 'normal / left / modern / yellow', 'bold' => 'false', 'align' => 'left', 'font' => 'modern', 'colour' => '#fff000'},
241             {'id' => 5, 'string' => 'bold / center / traditional / purple', 'bold' => 'true', 'align' => 'center', 'font' => 'traditional', 'colour' => '#ff00ff'},
242             {'id' => 6, 'string' => 'normal / right / typewriter / cyan', 'bold' => 'false', 'align' => 'right', 'font' => 'typewriter', 'colour' => '#00ffff'}],
243             });
244              
245             my @greeting_cards = ({
246             'url' => 'http://farm3.static.flickr.com/2300/2179038972_23d2a1ff40_o.jpg',
247             'text' => {'main' => [{'string' => qq(Script to the right (red)), 'align' => 'right', 'font' => 'script', 'colour' => '#ff0000'}],
248             'back' => [{'id' => 1, 'string' => qq(Can has cheese burger?)}] },
249             });
250              
251             =cut
252              
253 6     6   373495 use HTTP::Request;
  6         17  
  6         188  
254 6     6   6106 use IO::String;
  6         32947  
  6         258  
255 6     6   6872 use Config::Simple;
  6         98709  
  6         80  
256 6     6   3491 use XML::XPath;
  0            
  0            
257              
258             use Log::Dispatch;
259             use Log::Dispatch::Screen;
260              
261             use Net::Moo::Document;
262             use Net::Moo::Validate;
263              
264             =head1 PACKAGE METHODS
265              
266             =cut
267              
268             =head2 __PACKAGE__->new($cfg)
269              
270             Where B<$cfg> is either a valid I object or the path
271             to a file that can be parsed by I.
272              
273             Returns a I object.
274              
275             =cut
276              
277             sub new {
278             my $pkg = shift;
279             my %opts = @_;
280              
281             # Otherwise, LWP::UserAgent complains of
282             # unknown options...
283              
284             my $cfg = $opts{'config'};
285             delete($opts{'config'});
286              
287             my $self = $pkg->SUPER::new(%opts);
288              
289             if (! $self){
290             warn "Unable to instantiate parent class, $!";
291             return undef;
292             }
293              
294             #
295             # Configs
296             #
297              
298             $self->{'cfg'} = (UNIVERSAL::isa($cfg, "Config::Simple")) ? $cfg : Config::Simple->new($cfg);
299              
300             #
301             # Logs
302             #
303              
304             my $log_fmt = sub {
305             my %args = @_;
306            
307             my $msg = $args{'message'};
308             chomp $msg;
309            
310             if ($args{'level'} eq "error") {
311            
312             my ($ln, $sub) = (caller(4))[2,3];
313             $sub =~ s/.*:://;
314            
315             return sprintf("[%s][%s, ln%d] %s\n",
316             $args{'level'}, $sub, $ln, $msg);
317             }
318            
319             return sprintf("[%s] %s\n", $args{'level'}, $msg);
320             };
321            
322             my $logger = Log::Dispatch->new(callbacks => $log_fmt);
323              
324             my $error = Log::Dispatch::Screen->new(name => '__error',
325             min_level => 'error',
326             stderr => 1);
327            
328             $logger->add($error);
329             $self->{'log'} = $logger;
330              
331             #
332             # Happy happy!
333             #
334              
335             bless $self, $pkg;
336             return $self;
337             }
338              
339             =head1 OBJECT METHODS YOU SHOULD CARE ABOUT
340              
341             =cut
342              
343             =head2 $obj->api_call($method, $product, \@designs)
344              
345             Submit a set of designs to the Moo API for processing.
346              
347             Returns a I object (referencing the Moo API response
348             element) on success and undef if an error was encountered.
349              
350             =cut
351              
352             sub api_call {
353             my $self = shift;
354             my $method = shift;
355             my $product = shift;
356             my $designs = shift;
357              
358             my $xml = ($method eq 'choose') ? $self->chooser($product, $designs) : $self->builder($product, $designs);
359              
360             if ($self->{'cfg'}->param("moo.validate")){
361             my $validator = Net::Moo::Validate->new();
362              
363             if (my $errors = $validator->report_errors($xml)){
364              
365             foreach my $msg (@$errors){
366             $self->log()->error($msg);
367             }
368              
369             return undef;
370             }
371             }
372              
373             my $res = $self->execute_request($xml);
374            
375             return $self->parse_response($res);
376             }
377              
378             =head1 OBJECT METHODS YOU MAY CARE ABOUT
379              
380             =cut
381              
382             =head2 $obj->builder($product, \@designs)
383              
384             Generate the required XML document for submitting a list of images that will be used
385             to make cards or stickers.
386              
387             Returns a string.
388              
389             =cut
390              
391             sub builder {
392             my $self = shift;
393             my $product = shift;
394             my $designs = shift;
395              
396             my $xml = '';
397             my $fh = IO::String->new(\$xml);
398              
399             my $writer = Net::Moo::Document->new($fh);
400             $writer->startDocument({'api_key' => $self->{'cfg'}->param("moo.api_key")});
401              
402             $writer->startTag("products");
403             $writer->product($product, $designs);
404             $writer->endTag("products");
405              
406             $writer->endDocument();
407              
408             $fh->close();
409             return $xml;
410             }
411              
412             =head2 $obj->chooser($product, \@urls)
413              
414             Generate the required XML document for submitting a list of images (\@urls) that the
415             user can pick from and/or modify to make cards or stickers.
416              
417             Returns a string.
418              
419             =cut
420              
421             sub chooser {
422             my $self = shift;
423             my $product = shift;
424             my $urls = shift;
425              
426             my $xml = '';
427             my $fh = IO::String->new(\$xml);
428              
429             my $writer = Net::Moo::Document->new($fh);
430             $writer->startDocument({'api_key' => $self->{'cfg'}->param("moo.api_key")});
431              
432             $writer->startTag("chooser");
433              
434             $writer->startTag("product_type");
435             $writer->characters($product);
436             $writer->endTag("product_type");
437              
438             foreach my $url (@$urls){
439             $self->image({'url' => $url});
440             }
441              
442             $writer->endTag("chooser");
443             $writer->endDocument();
444              
445             $fh->close();
446             return $xml;
447             }
448              
449             =head2 $obj->execute_request($xml)
450              
451             Issue a request to the Moo API and get back a reponse (fancy talk for HTTP).
452              
453             Returns a I object.
454              
455             =cut
456              
457             sub execute_request {
458             my $self = shift;
459             my $xml = shift;
460              
461             my $req = HTTP::Request->new('POST' => 'http://www.moo.com/api/api.php');
462             $req->content_type('application/x-www-form-urlencoded');
463             $req->content("method=direct&xml=" . $xml);
464              
465             # print $req->as_string() . "\n";
466              
467             my $res = $self->send_request($req);
468             return $res;
469             }
470              
471             =head2 $obj->parse_response(HTTP::Response)
472              
473             Parse a response from the Moo API and return the payload information.
474              
475             Returns a I object (referencing the Moo API response
476             element) on success and undef if an error was encountered.
477              
478             =cut
479              
480             sub parse_response {
481             my $self = shift;
482             my $res = shift;
483              
484             my $xml = undef;
485              
486             eval {
487             $xml = XML::XPath->new('xml' => $res->content());
488             };
489              
490             if ($@){
491             $self->log()->error("Failed to parse response from the Moo API, $@");
492             return undef;
493             }
494              
495             if (my $err = $xml->findvalue("/moo/response/error")){
496             $self->log()->error("Error reported by the Moo API : $err");
497             return undef;
498             }
499            
500             return ($xml->findnodes("/moo/payload"))[0];
501             }
502              
503             =head2 $obj->config()
504              
505             Returns a I object.
506              
507             =cut
508              
509             sub config {
510             my $self = shift;
511             return $self->{'cfg'};
512             }
513              
514             =head2 $obj->log()
515              
516             Returns a I object.
517              
518             =cut
519              
520             sub log {
521             my $self = shift;
522             return $self->{'log'};
523             }
524              
525             =head1 VERSION
526              
527             0.11
528              
529             =head1 DATE
530              
531             $Date: 2008/06/19 15:15:34 $
532              
533             =head1 AUTHOR
534              
535             Aaron Straup Cope Eascope@cpan.orgE
536              
537             =head1 SEE ALSO
538              
539             L
540              
541             L
542              
543             =head1 BUGS
544              
545             Sure, why not.
546              
547             Please report all bugs via http://rt.cpan.org/
548              
549             =head1 LICENSE
550              
551             Copyright (c) 2008 Aaron Straup Cope. All rights reserved.
552              
553             This is free software. You may redistribute it and/or
554             modify it under the same terms as Perl itself.
555              
556             =cut
557              
558             return 1;