File Coverage

blib/lib/WWW/Shorten/Yourls.pm
Criterion Covered Total %
statement 85 207 41.0
branch 23 104 22.1
condition 12 84 14.2
subroutine 19 26 73.0
pod 10 10 100.0
total 149 431 34.5


line stmt bran cond sub pod time code
1             package WWW::Shorten::Yourls;
2              
3 4     4   130584 use strict;
  4         6  
  4         93  
4 4     4   12 use warnings;
  4         4  
  4         74  
5 4     4   12 use Carp ();
  4         3  
  4         39  
6 4     4   1762 use File::HomeDir ();
  4         15281  
  4         120  
7 4     4   21 use File::Spec ();
  4         3  
  4         61  
8 4     4   1541 use JSON::MaybeXS;
  4         18983  
  4         221  
9 4     4   2865 use Path::Tiny qw(path);
  4         32779  
  4         241  
10 4     4   22 use Scalar::Util qw(blessed);
  4         5  
  4         249  
11 4     4   639 use URI ();
  4         3624  
  4         64  
12              
13 4     4   38 use base qw( WWW::Shorten::generic Exporter );
  4         5  
  4         3992  
14              
15             our $VERSION = '1.001';
16             $VERSION = eval $VERSION;
17              
18             our @EXPORT = qw(new);
19              
20             # _attr (private)
21             sub _attr {
22 54     54   45 my $self = shift;
23 54   50     68 my $attr = lc(_trim(shift) || '');
24             # attribute list is small enough to just grep each time. meh.
25 54 50       45 Carp::croak("Invalid attribute") unless grep {$attr eq $_} @{_attrs()};
  216         237  
  54         62  
26 54 100       165 return $self->{$attr} unless @_;
27              
28 24         20 my $val = shift;
29 24 100       36 unless (defined($val)) {
30 6         9 $self->{$attr} = undef;
31 6         10 return $self;
32             }
33 18         23 $self->{$attr} = $val;
34 18         28 return $self;
35             }
36              
37             # _attrs (static, private)
38             {
39             my $attrs; # mimic the state keyword
40             sub _attrs {
41 56 100   56   83 return [@{$attrs}] if $attrs;
  54         115  
42 2         6 $attrs = [
43             qw(username password server signature),
44             ];
45 2         3 return [@{$attrs}];
  2         6  
46             }
47             }
48              
49             # _json_request (static, private)
50             sub _json_request {
51 0     0   0 my $url = shift;
52 0 0 0     0 Carp::croak("Invalid URI object") unless $url && blessed($url) && $url->isa('URI');
      0        
53 0         0 my $ua = __PACKAGE__->ua();
54 0         0 my $res = $ua->get($url);
55 0 0       0 Carp::croak("Invalid response") unless $res;
56 0 0       0 unless ($res->is_success) {
57 0         0 Carp::croak($res->status_line);
58             }
59              
60 0         0 my $content_type = $res->header('Content-Type');
61 0         0 my $content = $res->decoded_content();
62 0 0 0     0 unless ($content_type && $content_type =~ m{application/json}) {
63 0         0 Carp::croak("Unexpected response: $content");
64             }
65 0         0 my $json = decode_json($content);
66 0 0       0 Carp::croak("Invalid data returned: $content") unless $json;
67 0         0 return $json;
68             }
69              
70             # _parse_args (static, private)
71             sub _parse_args {
72 0     0   0 my $args;
73 0 0 0     0 if ( @_ == 1 && ref $_[0] ) {
    0 0        
    0          
74 0         0 my %copy = eval { %{ $_[0] } }; # try shallow copy
  0         0  
  0         0  
75 0 0       0 Carp::croak("Argument to method could not be dereferenced as a hash") if $@;
76 0         0 $args = \%copy;
77             }
78             elsif (@_==1 && !ref($_[0])) {
79 0         0 $args = {single_arg => $_[0]};
80             }
81             elsif ( @_ % 2 == 0 ) {
82 0         0 $args = {@_};
83             }
84             else {
85 0         0 Carp::croak("Method got an odd number of elements");
86             }
87 0         0 return $args;
88             }
89              
90             # _parse_config (static, private)
91             {
92             my $config; # mimic the state keyword
93             sub _parse_config {
94             # always give back a shallow copy
95 2 50   2   9 return {%{$config}} if $config;
  0         0  
96             # only parse the file once, please.
97 2         3 $config = {};
98 2 50       12 my $file = $^O eq 'MSWin32'? '_yourls': '.yourls';
99 2 50       9 $file .= '_test' if $ENV{YOURLS_TEST_CONFIG};
100 2         17 my $path = path(File::Spec->catfile(File::HomeDir->my_home(), $file));
101              
102 2 50 33     289 if ($path && $path->is_file) {
103 0         0 my @lines = $path->lines_utf8({chomp => 1});
104 0         0 my $attrs = _attrs();
105              
106 0         0 for my $line (@lines) {
107 0   0     0 $line = _trim($line) || '';
108 0 0       0 next if $line =~ /^\s*[;#]/; # skip comments
109 0         0 $line =~ s/\s+[;#].*$//gm; # trim off comments
110 0 0 0     0 next unless $line && $line =~ /=/; # make sure we have a =
111              
112 0         0 my ($key, $val) = split(/(?
113 0   0     0 $key = lc(_trim($key) || '');
114 0         0 $val = _trim($val);
115 0 0 0     0 next unless $key && $val;
116 0 0       0 $key = 'username' if $key eq 'user';
117 0 0       0 $key = 'server' if $key eq 'base';
118 0 0       0 next unless grep {$key eq $_} @{$attrs};
  0         0  
  0         0  
119 0         0 $config->{$key} = $val;
120             }
121             }
122 2         73 return {%{$config}};
  2         18  
123             }
124             }
125              
126             # _trim (static, private)
127             sub _trim {
128 60     60   54 my $input = shift;
129 60 50 33     276 return $input unless defined $input && !ref($input) && length($input);
      33        
130 60         185 $input =~ s/\A\s*//;
131 60         163 $input =~ s/\s*\z//;
132 60         143 return $input;
133             }
134              
135             sub new {
136 2     2 1 31 my $class = shift;
137 2         4 my $args;
138 2 50 33     16 if ( @_ == 1 && ref $_[0] ) {
    50          
139 0         0 my %copy = eval { %{ $_[0] } }; # try shallow copy
  0         0  
  0         0  
140 0 0       0 Carp::croak("Argument to $class->new() could not be dereferenced as a hash") if $@;
141 0         0 $args = \%copy;
142             }
143             elsif ( @_ % 2 == 0 ) {
144 2         5 $args = {@_};
145             }
146             else {
147 0         0 Carp::croak("$class->new() got an odd number of elements");
148             }
149              
150 2         8 my $attrs = _attrs();
151             # start with what's in our config file (if anything)
152 2         8 my $href = _parse_config();
153              
154             # override with anything passed in
155 2         3 for my $key (keys %{$args}) {
  2         58  
156 0         0 my $lc_key = lc($key);
157 0 0       0 $lc_key = 'username' if $lc_key eq 'user';
158 0 0       0 $lc_key = 'server' if $lc_key eq 'base';
159 0 0       0 next unless grep {$lc_key eq $_} @{$attrs};
  0         0  
  0         0  
160 0         0 $href->{$lc_key} = $args->{$key};
161             }
162 2 50       8 my $server = $href->{server} ? $href->{server} : 'https://yourls.org/yourls-api.php';
163 2         4 my $self = bless $href, $class;
164 2         9 return $self->server($server);
165             }
166              
167             sub clicks {
168 0     0 1 0 my $self = shift;
169 0 0       0 Carp::croak("You must tell us which server to use.") unless my $server = $self->server();
170              
171 0         0 my $args = _parse_args(@_);
172 0   0     0 my $short_url = $args->{shortUrl} || $args->{single_arg} || $args->{URL} || $args->{url} || '';
173 0 0       0 Carp::croak("A shortUrl parameter is required.\n") unless $short_url;
174              
175 0         0 my $url = $server->clone();
176 0         0 my $params = {
177             shorturl => $short_url,
178             format => 'json',
179             action => 'url-stats',
180             };
181 0 0       0 if (my $sig = $self->signature()) {
182 0         0 $params->{signature} = $sig;
183             }
184             else {
185 0         0 my $user = $self->username();
186 0         0 my $pass = $self->password();
187 0 0 0     0 unless ($user && $pass) {
188 0         0 Carp::croak("Username and password required when not using a signature");
189             }
190 0         0 $params->{username} = $user;
191 0         0 $params->{password} = $pass;
192             }
193 0         0 $url->query_form(%$params);
194 0         0 return _json_request($url);
195             }
196              
197             sub expand {
198 0     0 1 0 my $self = shift;
199 0 0       0 Carp::croak("You must tell us which server to use.") unless my $server = $self->server();
200              
201 0         0 my $args = _parse_args(@_);
202 0   0     0 my $short_url = $args->{shortUrl} || $args->{single_arg} || $args->{URL} || $args->{url} || '';
203 0 0       0 Carp::croak("A shortUrl parameter is required.\n") unless $short_url;
204              
205 0         0 my $url = $server->clone();
206 0         0 my $params = {
207             shorturl => $short_url,
208             format => 'json',
209             action => 'expand',
210             };
211 0 0       0 if (my $sig = $self->signature()) {
212 0         0 $params->{signature} = $sig;
213             }
214             else {
215 0         0 my $user = $self->username();
216 0         0 my $pass = $self->password();
217 0 0 0     0 unless ($user && $pass) {
218 0         0 Carp::croak("Username and password required when not using a signature");
219             }
220 0         0 $params->{username} = $user;
221 0         0 $params->{password} = $pass;
222             }
223 0         0 $url->query_form(%$params);
224 0         0 return _json_request($url);
225             }
226              
227             sub makealongerlink {
228 0     0 1 0 my $self;
229 0 0 0     0 if ($_[0] && blessed($_[0]) && $_[0]->isa('WWW::Shorten::Yourls')) {
      0        
230 0         0 $self = shift;
231             }
232 0 0       0 my $url = shift or Carp::croak('No URL passed to makealongerlink');
233 0   0     0 $self ||= __PACKAGE__->new(@_);
234 0         0 my $res = $self->expand(shortUrl=>$url);
235 0 0 0     0 return '' unless ref($res) eq 'HASH' and $res->{longurl};
236 0         0 return $res->{longurl};
237             }
238              
239             sub makeashorterlink {
240 0     0 1 0 my $self;
241 0 0 0     0 if ($_[0] && blessed($_[0]) && $_[0]->isa('WWW::Shorten::Yourls')) {
      0        
242 0         0 $self = shift;
243             }
244 0 0       0 my $url = shift or Carp::croak('No URL passed to makeashorterlink');
245 0   0     0 $self ||= __PACKAGE__->new(@_);
246 0         0 my $res = $self->shorten(longUrl=>$url, @_);
247 0         0 return $res->{shorturl};
248             }
249              
250 18     18 1 1253 sub password { return shift->_attr('password', @_); }
251              
252             sub server {
253 30     30 1 1842 my $self = shift;
254 30 100       83 return $self->{server} unless @_;
255 16         12 my $val = shift;
256 16 100 100     117 if (!defined($val) || $val eq '') {
    100 66        
    100 66        
257 4         7 $self->{server} = undef;
258 4         21 return $self;
259             }
260             elsif (blessed($val) && $val->isa('URI')) {
261 4         59 $self->{server} = $val->clone();
262 4         31 return $self;
263             }
264             elsif ($val && !ref($val)) {
265 6         15 $self->{server} = URI->new(_trim($val));
266 6         12988 return $self;
267             }
268              
269 2         320 Carp::croak("The server attribute must be set to a URI object");
270             }
271              
272             sub shorten {
273 0     0 1 0 my $self = shift;
274 0 0       0 Carp::croak("You must tell us which server to use.") unless my $server = $self->server();
275              
276 0         0 my $args = _parse_args(@_);
277 0   0     0 my $long_url = $args->{longUrl} || $args->{single_arg} || $args->{URL} || $args->{url} || '';
278 0 0       0 Carp::croak("A longUrl parameter is required.\n") unless $long_url;
279              
280 0         0 my $url = $server->clone();
281 0         0 my $params = {
282             url => $long_url,
283             format => 'json',
284             action => 'shorturl',
285             };
286 0 0       0 if (my $sig = $self->signature()) {
287 0         0 $params->{signature} = $sig;
288             }
289             else {
290 0         0 my $user = $self->username();
291 0         0 my $pass = $self->password();
292 0 0 0     0 unless ($user && $pass) {
293 0         0 Carp::croak("Username and password required when not using a signature");
294             }
295 0         0 $params->{username} = $user;
296 0         0 $params->{password} = $pass;
297             }
298 0         0 $url->query_form(%$params);
299 0         0 return _json_request($url);
300             }
301              
302 18     18 1 983 sub signature { return shift->_attr('signature', @_); }
303              
304 18     18 1 877 sub username { return shift->_attr('username', @_); }
305              
306             1; # End of WWW::Shorten::Yourls
307              
308             __END__