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   1756 use strict;
  5         5  
  5         111  
2 5     5   11 use warnings;
  5         4  
  5         163  
3             package WWW::xkcd;
4             # ABSTRACT: Synchronous and asynchronous interfaces to xkcd comics
5             $WWW::xkcd::VERSION = '0.007';
6 5     5   13 use Carp;
  5         4  
  5         302  
7 5     5   2253 use JSON;
  5         40076  
  5         16  
8 5     5   2233 use Try::Tiny;
  5         3742  
  5         207  
9 5     5   3179 use HTTP::Tiny;
  5         187525  
  5         3323  
10              
11             my $can_async = try { require AnyEvent; require AnyEvent::HTTP; 1 }
12             catch { 0 };
13              
14             sub new {
15 5     5 1 572 my $class = shift;
16 5         20 my %args = (
17             baseurl => 'http://xkcd.com',
18             infopath => 'info.0.json',
19             @_,
20             );
21              
22 5         24 return bless { %args }, $class;
23             }
24              
25             sub fetch_metadata {
26 14     14 1 10417 my $self = shift;
27 14         29 my $base = $self->{'baseurl'};
28 14         22 my $path = $self->{'infopath'};
29 14         29 my ( $comic, $cb ) = $self->_parse_args(@_);
30              
31 14 100       48 my $url = defined $comic ? "$base/$comic/$path" : "$base/$path";
32              
33 14 100       31 if ($cb) {
34             # this is async
35 7 100       85 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   254841 my $body = shift;
40 6         35 my $meta = $self->_decode_json($body);
41              
42 6         19 return $cb->($meta);
43 6         22 } );
44              
45 6         14895 return 0;
46             }
47              
48             # this is sync
49 7         47 my $result = HTTP::Tiny->new->get($url);
50              
51             $result->{'success'} or croak "Can't fetch $url: " .
52 7 100       203500 $result->{'reason'};
53              
54 6         43 my $meta = $self->_decode_json( $result->{'content'} );
55              
56 6         43 return $meta;
57             }
58              
59             sub fetch_random {
60 2     2 1 1403 my $self = shift;
61 2 100       9 my $callback = shift if ref $_[0];
62            
63 2 100       8 if ($callback) {
64             $self->fetch_metadata( sub {
65 1     1   1 my $metadata = shift;
66 1         4 my $random = int(rand($metadata->{num})) + 1;
67 1         4 return $self->fetch($random, $callback);
68 1         7 } );
69 1         2 return 0;
70             }
71              
72 1         3 my $metadata = $self->fetch_metadata;
73 1         56 my $random = int(rand($metadata->{num})) + 1;
74 1         6 return $self->fetch($random);
75             }
76              
77             sub fetch {
78 7     7 1 2376 my $self = shift;
79 7         48 my $base = $self->{'baseurl'};
80 7         13 my $path = $self->{'infopath'};
81 7         22 my ( $comic, $cb ) = $self->_parse_args(@_);
82              
83 7 100       18 if ($cb) {
84             $self->fetch_metadata( $comic, sub {
85 3     3   4 my $meta = shift;
86 3         6 my $img = $meta->{'img'};
87              
88             AnyEvent::HTTP::http_get( $img, sub {
89 3         950301 my $img_data = shift;
90              
91             # call original callback
92 3         30 return $cb->( $img_data, $meta );
93 3         18 } );
94 3         16 } );
95              
96 3         34 return 0;
97             }
98              
99 4         10 my $meta = $self->fetch_metadata($comic);
100 4         469 my $img = $meta->{'img'};
101 4         29 my $result = HTTP::Tiny->new->get($img);
102              
103             $result->{'success'} or croak "Can't fetch $img: " .
104 4 100       394090 $result->{'reason'};
105              
106 3         279 return ( $result->{'content'}, $meta );
107             }
108              
109             sub _parse_args {
110 21     21   23 my $self = shift;
111 21         31 my @args = @_;
112 21         19 my ( $comic, $cb );
113              
114             # @_ = $num, $cb
115             # @_ = $num
116             # @_ = $cb
117 21 100       73 if ( @_ == 2 ) {
    100          
118 7         8 ( $comic, $cb ) = @_;
119             } elsif ( @_ == 1 ) {
120 11 100       25 if ( ref $_[0] ) {
121 3         4 $cb = $_[0];
122             } else {
123 8         10 $comic = $_[0];
124             }
125             }
126              
127 21         39 return ( $comic, $cb );
128             }
129              
130             sub _decode_json {
131 14     14   955 my $self = shift;
132 14         48 my $json = shift;
133 14     14   731 my $data = try { decode_json $json }
134 14     1   155 catch { croak "Can't decode '$json': $_" };
  1         130  
135              
136 13         176 return $data;
137             }
138              
139             1;
140              
141             __END__