File Coverage

blib/lib/WWW/xkcd.pm
Criterion Covered Total %
statement 81 81 100.0
branch 22 24 91.6
condition 3 5 60.0
subroutine 14 14 100.0
pod 4 4 100.0
total 124 128 96.8


line stmt bran cond sub pod time code
1             package WWW::xkcd;
2             # ABSTRACT: Synchronous and asynchronous interfaces to xkcd comics
3             $WWW::xkcd::VERSION = '0.009';
4 5     5   2008 use strict;
  5         28  
  5         117  
5 5     5   22 use warnings;
  5         6  
  5         106  
6 5     5   20 use Carp;
  5         7  
  5         381  
7 5     5   1573 use JSON::MaybeXS;
  5         26873  
  5         231  
8 5     5   2938 use HTTP::Tiny;
  5         221668  
  5         3912  
9              
10             my $can_async = eval { require AnyEvent; require AnyEvent::HTTP; 1 };
11              
12             sub new {
13 5     5 1 1246 my $class = shift;
14 5         30 my %args = (
15             'baseurl' => 'https://xkcd.com',
16             'infopath' => 'info.0.json',
17             @_,
18             );
19              
20 5         35 return bless { %args }, $class;
21             }
22              
23             sub fetch_metadata {
24 13     13 1 7084 my $self = shift;
25 13         35 my $base = $self->{'baseurl'};
26 13         28 my $path = $self->{'infopath'};
27 13         42 my ( $comic, $cb ) = $self->_parse_args(@_);
28              
29 13 100       68 my $url = defined $comic ? "$base/$comic/$path" : "$base/$path";
30              
31 13 100       40 if ($cb) {
32             # this is async
33 6 50       26 $can_async
34             or croak 'AnyEvent and AnyEvent::HTTP are required for async mode';
35              
36             AnyEvent::HTTP::http_get( $url, sub {
37 6     6   336628 my $body = shift;
38 6         20 my $meta = $self->_decode_json($body);
39              
40 6         19 return $cb->($meta);
41 6         29 } );
42              
43 6         19169 return 0;
44             }
45              
46             # this is sync
47 7         81 my $result = HTTP::Tiny->new->get($url);
48              
49             $result->{'success'} or croak "Can't fetch $url: " .
50 7 100       329611 $result->{'reason'};
51              
52 6         1831 my $meta = $self->_decode_json( $result->{'content'} );
53              
54 6         55 return $meta;
55             }
56              
57             sub fetch_random {
58 2     2 1 1938 my $self = shift;
59 2         12 my $callback = shift;
60              
61 2 100 66     18 if ( $callback && ref $callback ) {
62             $self->fetch_metadata( sub {
63 1     1   2 my $metadata = shift;
64 1         4 my $random = int(rand($metadata->{'num'})) + 1;
65 1         5 return $self->fetch($random, $callback);
66 1         7 } );
67 1         2 return 0;
68             }
69              
70 1         4 my $metadata = $self->fetch_metadata;
71 1         6 my $random = int(rand($metadata->{'num'})) + 1;
72 1         7 return $self->fetch($random);
73             }
74              
75             sub fetch {
76 7     7 1 3685 my $self = shift;
77 7         21 my $base = $self->{'baseurl'};
78 7         13 my $path = $self->{'infopath'};
79 7         27 my ( $comic, $cb ) = $self->_parse_args(@_);
80              
81 7 100       22 if ($cb) {
82             $self->fetch_metadata( $comic, sub {
83 3     3   8 my $meta = shift;
84 3         7 my $img = $meta->{'img'};
85              
86             AnyEvent::HTTP::http_get( $img, sub {
87 3         148123 my $img_data = shift;
88              
89             # call original callback
90 3         17 return $cb->( $img_data, $meta );
91 3         16 } );
92 3         18 } );
93              
94 3         36 return 0;
95             }
96              
97 4         15 my $meta = $self->fetch_metadata($comic);
98 4         743 my $img = $meta->{'img'};
99 4         34 my $result = HTTP::Tiny->new->get($img);
100              
101             $result->{'success'} or croak "Can't fetch $img: " .
102 4 100       240324 $result->{'reason'};
103              
104 3         1210 return ( $result->{'content'}, $meta );
105             }
106              
107             sub _parse_args {
108 20     20   33 my $self = shift;
109 20         40 my @args = @_;
110 20         31 my ( $comic, $cb );
111              
112             # @_ = $num, $cb
113             # @_ = $num
114             # @_ = $cb
115 20 100       84 if ( @_ == 2 ) {
    100          
116 6         9 ( $comic, $cb ) = @_;
117             } elsif ( @_ == 1 ) {
118 11 100       28 if ( ref $_[0] ) {
119 3         5 $cb = $_[0];
120             } else {
121 8         12 $comic = $_[0];
122             }
123             }
124              
125 20         58 return ( $comic, $cb );
126             }
127              
128             sub _decode_json {
129 14     14   914 my $self = shift;
130 14         28 my $json = shift;
131 14         29 my $data = {};
132              
133 14 50       46 defined $json or $json = '';
134              
135 14 100       30 eval { $data = decode_json $json; 1; } or do {
  14         258  
  13         49  
136 1   50     7 my $error = $@ || 'Zombie error';
137 1         156 croak "Can't decode JSON content '$json': $error";
138             };
139              
140 13         34 return $data;
141             }
142              
143             1;
144              
145             __END__