File Coverage

blib/lib/WWW/xkcd.pm
Criterion Covered Total %
statement 80 80 100.0
branch 22 22 100.0
condition n/a
subroutine 17 17 100.0
pod 4 4 100.0
total 123 123 100.0


line stmt bran cond sub pod time code
1 5     5   1992 use strict;
  5         6  
  5         120  
2 5     5   17 use warnings;
  5         6  
  5         191  
3             package WWW::xkcd;
4             # ABSTRACT: Synchronous and asynchronous interfaces to xkcd comics
5             $WWW::xkcd::VERSION = '0.006';
6 5     5   17 use Carp;
  5         6  
  5         322  
7 5     5   2477 use JSON;
  5         47478  
  5         20  
8 5     5   2488 use Try::Tiny;
  5         4201  
  5         225  
9 5     5   2969 use HTTP::Tiny;
  5         197689  
  5         2984  
10              
11             my $can_async = try { require AnyEvent; require AnyEvent::HTTP; 1 }
12             catch { 0 };
13              
14             sub new {
15 5     5 1 343 my $class = shift;
16 5         24 my %args = (
17             baseurl => 'http://xkcd.com',
18             infopath => 'info.0.json',
19             @_,
20             );
21              
22 5         264 return bless { %args }, $class;
23             }
24              
25             sub fetch_metadata {
26 14     14 1 10530 my $self = shift;
27 14         26 my $base = $self->{'baseurl'};
28 14         17 my $path = $self->{'infopath'};
29 14         31 my ( $comic, $cb ) = $self->_parse_args(@_);
30              
31 14 100       46 my $url = defined $comic ? "$base/$comic/$path" : "$base/$path";
32              
33 14 100       25 if ($cb) {
34             # this is async
35 7 100       77 croak 'AnyEvent and AnyEvent::HTTP are required for async mode'
36             unless $can_async;
37              
38             AnyEvent::HTTP::http_get( $url, sub {
39 6     6   108480 my $body = shift;
40 6         18 my $meta = $self->_decode_json($body);
41              
42 6         17 return $cb->($meta);
43 6         23 } );
44              
45 6         14815 return 0;
46             }
47              
48             # this is sync
49 7         37 my $result = HTTP::Tiny->new->get($url);
50              
51             $result->{'success'} or croak "Can't fetch $url: " .
52 7 100       190394 $result->{'reason'};
53              
54 6         31 my $meta = $self->_decode_json( $result->{'content'} );
55              
56 6         35 return $meta;
57             }
58              
59             sub fetch_random {
60 2     2 1 1958 my $self = shift;
61 2 100       8 my $callback = shift if ref $_[0];
62            
63 2 100       7 if ($callback) {
64             $self->fetch_metadata( sub {
65 1     1   2 my $metadata = shift;
66 1         4 my $random = int(rand($metadata->{num})) + 1;
67 1         4 return $self->fetch($random, $callback);
68 1         6 } );
69 1         1 return 0;
70             }
71              
72 1         3 my $metadata = $self->fetch_metadata;
73 1         26 my $random = int(rand($metadata->{num})) + 1;
74 1         3 return $self->fetch($random);
75             }
76              
77             sub fetch {
78 7     7 1 3219 my $self = shift;
79 7         15 my $base = $self->{'baseurl'};
80 7         10 my $path = $self->{'infopath'};
81 7         17 my ( $comic, $cb ) = $self->_parse_args(@_);
82              
83 7 100       18 if ($cb) {
84             $self->fetch_metadata( $comic, sub {
85 3     3   3 my $meta = shift;
86 3         6 my $img = $meta->{'img'};
87              
88             AnyEvent::HTTP::http_get( $img, sub {
89 3         268213 my $img_data = shift;
90              
91             # call original callback
92 3         14 return $cb->( $img_data, $meta );
93 3         13 } );
94 3         12 } );
95              
96 3         33 return 0;
97             }
98              
99 4         8 my $meta = $self->fetch_metadata($comic);
100 4         451 my $img = $meta->{'img'};
101 4         25 my $result = HTTP::Tiny->new->get($img);
102              
103             $result->{'success'} or croak "Can't fetch $img: " .
104 4 100       280039 $result->{'reason'};
105              
106 3         172 return ( $result->{'content'}, $meta );
107             }
108              
109             sub _parse_args {
110 21     21   16 my $self = shift;
111 21         29 my @args = @_;
112 21         16 my ( $comic, $cb );
113              
114             # @_ = $num, $cb
115             # @_ = $num
116             # @_ = $cb
117 21 100       59 if ( @_ == 2 ) {
    100          
118 7         10 ( $comic, $cb ) = @_;
119             } elsif ( @_ == 1 ) {
120 11 100       20 if ( ref $_[0] ) {
121 3         4 $cb = $_[0];
122             } else {
123 8         8 $comic = $_[0];
124             }
125             }
126              
127 21         34 return ( $comic, $cb );
128             }
129              
130             sub _decode_json {
131 14     14   770 my $self = shift;
132 14         40 my $json = shift;
133 14     14   631 my $data = try { decode_json $json }
134 14     1   104 catch { croak "Can't decode '$json': $_" };
  1         157  
135              
136 13         162 return $data;
137             }
138              
139             1;
140              
141             __END__