File Coverage

lib/App/Adenosine.pm
Criterion Covered Total %
statement 143 176 81.2
branch 54 66 81.8
condition 21 24 87.5
subroutine 26 35 74.2
pod 0 15 0.0
total 244 316 77.2


line stmt bran cond sub pod time code
1             package App::Adenosine;
2             $App::Adenosine::VERSION = '2.001008';
3 1     1   746551 use strict;
  1         3  
  1         35  
4 1     1   6 use warnings;
  1         2  
  1         31  
5              
6             # ABSTRACT: Handy CLI HTTP tool
7              
8 1     1   744388 use URI;
  1         6090  
  1         36  
9 1     1   1238 use Getopt::Long qw(:config pass_through no_ignore_case);
  1         28027  
  1         11  
10 1     1   311 use File::Path 'mkpath';
  1         3  
  1         3956  
11 1     1   10 use URI::Escape 'uri_escape';
  1         3  
  1         65  
12 1     1   1070 use File::Spec::Functions 'splitpath';
  1         1319  
  1         83  
13 1     1   1021 use Path::Class;
  1         63338  
  1         69  
14 1     1   752 use Text::ParseWords;
  1         1218  
  1         64  
15 1     1   5 use Scalar::Util 'blessed';
  1         1  
  1         55  
16 1     1   866 use Module::Runtime 'use_module';
  1         1489  
  1         5  
17              
18             our $verb_regex = '(?:HEAD|OPTIONS|GET|DELETE|PUT|POST|TRACE|PATCH)';
19              
20 16     16 0 78 sub verbose { $_[0]->{verbose} }
21 1     1 0 2 sub plugins { @{$_[0]->{plugins}} }
  1         4  
22             sub enable_xdg {
23 8 100   8 0 148 return $_[0]->{enable_xdg} if exists $_[0]->{enable_xdg};
24 1         21 return 1
25             }
26              
27             sub _plugin_name {
28 2     2   4 my ($self, $name) = @_;
29 2 100       13 $name = "App::Adenosine::Plugin$name" if $name =~ /^::/;
30 2         9 use_module($name)
31             }
32              
33             sub new {
34 14     14 0 59499 my ($class, $args) = @_;
35              
36 14 100       58 if (my $p = $args->{plugins}) {
37 5 100 100     56 die "plugins must be an arrayref" unless ref $p && ref $p eq 'ARRAY';
38             $args->{plugins} = [
39 3         9 map {;
40 3         5 my $ret = $_;
41 3 100       15 unless (blessed($ret)) {
42 2         4 my @args;
43 2 100       9 if (ref $ret eq 'HASH') {
44 1         6 ($ret, @args) = %$ret;
45             }
46 2         14 $ret = $class->_plugin_name($ret);
47 2         82 $ret = $ret->new(@args)
48             }
49 3         76 $ret;
50 3         6 } @{$args->{plugins}}];
51             } else {
52 9         30 $args->{plugins} = []
53             }
54              
55 12         86 my $self = { %$args };
56              
57 12         28 bless $self, $class;
58              
59 12         22 local @ARGV = @{$self->argv};
  12         46  
60              
61 12         28 my $action = shift @ARGV;
62              
63 12         41 my $uri_base = $self->uri_base;
64              
65 12 100       70 $self->stdout("$uri_base\n"), return if !$action;
66              
67 11 100       164 if ($action =~ m/^$verb_regex$/) {
    50          
68 8         15 my $quote = 1;
69 8         16 my $interactive_edit = 0;
70 8         15 my $query = '';
71 8         19 $self->{verbose} = 0;
72              
73 8         9 my ($path, $data);
74 8 100 100     67 $path = shift @ARGV unless $ARGV[0] && $ARGV[0] =~ /^-/;
75 8 100 100     49 $data = shift @ARGV unless $ARGV[0] && $ARGV[0] =~ /^-/;
76              
77 8   100     25 $path ||= '';
78 8   100     34 $data ||= '';
79              
80             GetOptions (
81 1     1   450 Q => sub { $quote = 0 },
82             "q=s" => \$query,
83             V => \$interactive_edit,
84 1     1   342 v => sub { $self->{verbose} = 1 },
85 8         147 );
86              
87 8         2931 my @extra = (@ARGV, $self->_get_extra_options);
88 8         33 my $wantdata;
89 8 100       41 $wantdata = 1 if $action =~ m/^(?:PUT|POST|TRACE|PATCH)$/;
90 8 100 100     33 if ($wantdata && $interactive_edit) {
91 1         14 require File::Temp;
92 1         10 my ($fh, $fn) = File::Temp::tempfile();
93              
94 1   50     19543 system($ENV{EDITOR} || 'vi', $fn);
95              
96 1         68 $data = file($fn)->slurp;
97 1         941 unlink $fn;
98             }
99              
100 8 100       29 push @extra, '--data-binary' if $data;
101 8 100       27 push @extra, '-I' if $action eq 'HEAD';
102              
103 8         19 my $_path = $uri_base;
104 8         36 $_path =~ s/\*/$path/;
105              
106 8 100       88 $query = uri_escape($query) if $quote;
107              
108 8         154 push @extra, $self->host_method_config( $self->host($uri_base), $action );
109              
110 8 100       736 $query = "?$query" if $query;
111              
112 8         14 my @curl = @{$self->curl_command({
  8         46  
113             method => $action,
114             data => $data,
115             cookie_jar => $self->cookie_jar($uri_base),
116             rest => \@extra,
117             location => "$_path$query",
118             })};
119              
120 8 100       61 $self->stderr(join(" ", map "'$_'", @curl) . "\n") if $self->verbose;
121              
122 8         40 my ($out, $err, $ret) = $self->capture_curl(@curl);
123 8         120 return $self->handle_curl_output($out, $err, $ret);
124             } elsif ($action eq 'exports') {
125 0         0 print <<'SHELL';
126             function HEAD() { adenosine HEAD "$@"; };
127             function OPTIONS() { adenosine OPTIONS "$@"; };
128             function GET() { adenosine GET "$@"; };
129             function POST() { adenosine POST "$@"; };
130             function PUT() { adenosine PUT "$@"; };
131             function DELETE() { adenosine DELETE "$@"; };
132             function TRACE() { adenosine TRACE "$@"; };
133             function PATCH() { adenosine TRACE "$@"; };
134             SHELL
135             } else {
136 3         8 my $uri_base = $self->uri_base($action);
137 3         13 $self->_set_extra_options(@ARGV);
138 3         24 $self->stdout("$uri_base\n"), return
139             }
140             }
141              
142             sub config_location {
143 8     8 0 13 my $loc;
144 8 100 66     38 if ($_[0]->enable_xdg and $ENV{XDG_CONFIG_HOME}) {
145 1         3 my $h = $ENV{XDG_CONFIG_HOME};
146 1         6 $loc = "$h/resty"
147             } else {
148 7         26 $loc = "$ENV{HOME}/.resty"
149             }
150 8         49 my $ret = dir($loc);
151              
152 8 50       669 $ret->mkpath unless -d $ret->stringify;
153              
154 8         400 $ret
155             }
156              
157 0     0 0 0 sub stdout { print STDOUT $_[1] }
158 0     0 0 0 sub stderr { print STDERR $_[1] }
159              
160             sub capture_curl {
161 0     0 0 0 my ($self, @rest) = @_;
162              
163 0         0 my @wrappers = grep { $_->does('App::Adenosine::Role::WrapsCurlCommand') }
  0         0  
164             $self->plugins;
165             my $wrapped = sub {
166 0     0   0 my @rest = @_;
167 0         0 require Capture::Tiny;
168 0         0 Capture::Tiny::capture(sub { system(@rest) });
  0         0  
169 0         0 };
170 0         0 for my $wrapper (@wrappers) {
171 0         0 $wrapped = $wrapper->wrap($wrapped)
172             }
173 0         0 $wrapped->(@rest);
174             }
175              
176             sub handle_curl_output {
177 8     8 0 19 my ($self, $out, $err, $ret) = @_;
178              
179 8         1319 my ( $http_code ) = ($err =~ m{.*HTTP/1\.[01] (\d)\d\d });
180 8 100       24 if ($self->verbose) {
181 1         9 my @filters = grep { $_->does('App::Adenosine::Role::FiltersStdErr') }
  1         8  
182             $self->plugins;
183 1         61 $err = $_->filter_stderr($err) for @filters;
184 1         15 $self->stderr($err)
185             }
186 8 50       48 $out .= "\n" unless $out =~ m/\n\Z/m;
187 8         44 $self->stdout($out);
188 8 100       69 return if $http_code == 2;
189 7         84 return $http_code;
190             }
191              
192 12     12 0 105 sub argv { $_[0]->{argv} }
193              
194             sub uri_base {
195 15     15 0 29 my ($self, $base) = @_;
196              
197 15 100       36 if ($base) {
198 3 100       12 $base .= '*' unless $base =~ /\*/;
199 3 100       17 $base = "http://$base" unless $base =~ m(^https?://);
200 3         12 $self->_set_uri_base($base);
201 3         14 return $base
202             } else {
203 12         44 $self->_get_uri_base
204             }
205             }
206              
207             sub _set_uri_base {
208 0     0   0 my ($self, $base) = @_;
209              
210 0         0 my $file = $self->config_location->file('host');
211              
212 0 0       0 $file->touch unless -f $file->stringify;
213 0         0 $file->spew($base);
214             }
215              
216             sub _get_uri_base {
217 0     0   0 my $self = shift;
218              
219 0         0 my $file = $self->config_location->file('host');
220 0 0       0 return '' unless -f $file->stringify;
221 0         0 ($file->slurp(chomp => 1))[0]
222             }
223              
224             sub _set_extra_options {
225 0     0   0 my ($self, @rest) = @_;
226              
227 0         0 my $file = $self->config_location->file('options');
228              
229 0 0       0 $file->touch unless -f $file->stringify;
230 0         0 $file->spew(@rest);
231             }
232              
233             sub _get_extra_options {
234 0     0   0 my $self = shift;
235              
236 0         0 my $file = $self->config_location->file('options');
237 0         0 $file->slurp(chomp => 1)
238             }
239              
240             sub curl_command {
241 8     8 0 317 my %arg = %{$_[1]};
  8         46  
242              
243 8         62 [qw(curl -sLv), $arg{data} || (), '-X', $arg{method},
244             '-b', $arg{cookie_jar}, '-c', $arg{cookie_jar},
245 8   66     56 @{$arg{rest}}, $arg{location}]
246             }
247              
248             sub cookie_jar {
249 8     8 0 22 my ($self, $uri) = @_;
250 8         37 my $cookie_dir = $self->config_location->subdir('c');
251 8         507 $cookie_dir->mkpath;
252 8         667 my $path = $cookie_dir->file($self->host($uri));
253              
254 8 50       1526 $path->touch unless -f $path->stringify;
255              
256 8         416 return $path->stringify;
257             }
258              
259             sub _load_host_method_config {
260 0     0   0 my ($self, $host) = @_;
261              
262 0         0 my $file = $self->config_location->file($host);
263 0 0       0 $file->touch unless -f $file->stringify;
264 0         0 $file->slurp(chomp => 1);
265             }
266              
267             sub host_method_config {
268 8     8 0 10803 my ($self, $host, $method) = @_;
269              
270 24 100       356 my %config = map {
271 8         326 m/^\s*($verb_regex)\s+(.*)/
272             ? (uc($1), $2)
273             : ()
274             } $self->_load_host_method_config($host);
275              
276 8 100       36 if (my $ret = $config{$method}) {
277 7         38 return ( shellwords($ret) )
278             }
279             return ()
280 1         4 }
281              
282 16     16 0 98 sub host { URI->new($_[1])->host }
283              
284             1;
285              
286             __END__