File Coverage

blib/lib/App/Filite/Client.pm
Criterion Covered Total %
statement 104 134 77.6
branch 22 36 61.1
condition 5 13 38.4
subroutine 22 25 88.0
pod 6 6 100.0
total 159 214 74.3


line stmt bran cond sub pod time code
1 2     2   457007 use 5.010001;
  2         18  
2 2     2   10 use strict;
  2         4  
  2         37  
3 2     2   8 use warnings;
  2         4  
  2         129  
4              
5             package App::Filite::Client;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.001001';
9              
10 2     2   11 use Carp qw( croak );
  2         4  
  2         111  
11 2     2   997 use File::XDG;
  2         10181  
  2         71  
12 2     2   1652 use Getopt::Long qw( GetOptionsFromArray );
  2         21578  
  2         8  
13 2     2   1831 use HTTP::Tiny;
  2         87048  
  2         96  
14 2     2   1032 use HTTP::Tiny::Multipart;
  2         4382  
  2         68  
15 2     2   1631 use JSON::PP qw( encode_json decode_json );
  2         28545  
  2         153  
16 2     2   15 use MIME::Base64 qw( encode_base64 );
  2         4  
  2         103  
17 2     2   1710 use Path::Tiny qw( path );
  2         25566  
  2         249  
18              
19             use Class::Tiny {
20 0         0 password => sub { croak "Missing option: password" },
21 0         0 server => sub { croak "Missing option: server" },
22 1         16 useragent => sub { shift->_build_useragent },
23 1         2218 errors => sub { 0 },
24 2     2   1026 };
  2         3671  
  2         22  
25              
26 2     2   2453 use namespace::autoclean;
  2         36781  
  2         8  
27              
28             sub new_from_config {
29 1     1 1 83455 my ( $class ) = ( shift );
30            
31 1         11 state $xdg = File::XDG->new( name => 'filite-client', api => 1 );
32 1   33     96 my $config_file = $ENV{FILITE_CLIENT_CONFIG} // $xdg->config_home->child( 'config.json' );
33 1 50       7 if ( not ref $config_file ) {
34 1         28 $config_file = path( $config_file );
35             }
36 1 50       70 croak "Expected config file: $config_file" unless $config_file->is_file;
37            
38 1         85 my $args = decode_json( $config_file->slurp_utf8 );
39 1         1978 my $self = $class->new( %$args );
40 1         147 return $self;
41             }
42              
43             sub _build_useragent {
44 1     1   3 my ( $self ) = ( shift );
45 1         18 my $auth = encode_base64( sprintf( 'admin:%s', $self->password ) );
46 1         15 chomp $auth;
47 1         30 return HTTP::Tiny->new(
48             agent => sprintf( '%s/%s ', __PACKAGE__, $VERSION ),
49             default_headers => { 'Authorization' => "Basic $auth" },
50             );
51             }
52              
53             sub _parse_opts {
54 0     0   0 my ( $self, $args ) = ( shift, @_ );
55            
56 0         0 my $opts = {};
57 0         0 GetOptionsFromArray(
58             $args => $opts,
59             'text|T',
60             'file|F',
61             'link|L',
62             'highlight|H',
63             'help|usage',
64             );
65 0         0 return $opts;
66             }
67              
68             ## no Test::Tabs
69             sub _print_usage {
70 0     0   0 print <<"STDERR"; return 0;
  0         0  
71             filite-client: share via a filite server
72              
73             Usage:
74             filite-client -T [filename]
75             filite-client -F [filename]
76             filite-client -L [url]
77             cat blah | filite-client [options]
78              
79             Options:
80             --text, -T Share as text
81             --file, -F Share as file
82             --link, -L Share as link
83             --highlight, -H Syntax highligh text
84             --help, --usage Show this usage information
85              
86             STDERR
87             }
88             ## use Test::Tabs
89              
90             sub execute {
91 0     0 1 0 my ( $self, $args ) = ( shift, @_ );
92 0   0     0 $args //= [ @ARGV ];
93 0         0 my $opts = $self->_parse_opts( $args );
94 0 0       0 $args = [ '-' ] unless @$args;
95            
96 0 0       0 if ( $opts->{help} ) {
97 0         0 return $self->_print_usage;
98             }
99            
100 0         0 for my $file ( @$args ) {
101 0         0 my $url = $self->share( $file, $opts );
102 0         0 print "$url\n";
103             }
104            
105 0         0 $self->errors;
106             }
107              
108             sub _guess_mode {
109 7     7   15 my ( $self, $file, $opts ) = ( shift, @_ );
110 7 100       28 return 'link' if $opts->{link};
111 6 100       17 return 'text' if $opts->{text};
112 5 100       13 return 'file' if $opts->{file};
113 4 100       29 return 'link' if $file =~ m{\Ahttps?://\S+\z}is;
114 3 100       9 return 'text' if $opts->{highlight};
115 2 50       6 return 'text' if $file eq '-';
116 2 100       135 return 'file' if -B $file;
117 1         7 return 'text';
118             }
119              
120             sub share {
121 7     7 1 60589 my ( $self, $file, $opts ) = ( shift, @_ );
122 7   50     25 $opts //= {};
123 7         21 my $mode = $self->_guess_mode( $file, $opts );
124 7         20 my $method = "share_$mode";
125 7         28 return $self->$method( $file, $opts );
126             }
127              
128             sub _get_endpoint {
129 3     3   13 my ( $self, $mode ) = ( shift, @_ );
130 3         82 my $server = $self->server;
131 3 50       35 $server = "http://$server" unless $server =~ m{https?:}i;
132 3 50       11 $server .= '/' unless $server =~ m{/$};
133 3         19 return sprintf( '%s%s', $server, lc( substr( $mode, 0, 1 ) ) );
134             }
135              
136             sub _handle_response {
137 3     3   8 my ( $self, $response ) = ( shift, @_ );
138 3 50       25 if ( $response->{success} ) {
139 3         24 return $response->{content};
140             }
141 0         0 my $errs = $self->errors;
142 0         0 ++$errs;
143 0         0 $self->errors( $errs );
144 0         0 warn sprintf( "ERROR: %s %s\n", $response->{status}, $response->{reason} );
145 0         0 return "-";
146             }
147              
148             sub share_file {
149 1     1 1 16249 my ( $self, $file, $opts ) = ( shift, @_ );
150 1   50     5 $opts //= {};
151            
152 1         3 my ( $filename, $content );
153 1 50       6 if ( $file eq '-' ) {
154 0         0 $filename = 'file.data';
155 0         0 local $/;
156 0         0 $content = ;
157             }
158             else {
159 1         5 my $pt = path( $file );
160 1         68 $filename = $pt->basename;
161 1         53 $content = $pt->slurp;
162             }
163            
164 1         255 my $endpoint = $self->_get_endpoint( 'file' );
165 1         24 my $response = $self->useragent->post_multipart(
166             $endpoint => {
167             file => {
168             filename => $filename,
169             content => $content,
170             content_type => 'application/octet-stream',
171             },
172             },
173             );
174            
175 1         27 return sprintf( '%s/%s', $endpoint, $self->_handle_response( $response ) );
176             }
177              
178             sub share_text {
179 1     1 1 12220 my ( $self, $file, $opts ) = ( shift, @_ );
180 1   50     4 $opts //= {};
181            
182 1         3 my $content;
183 1 50       6 if ( $file eq '-' ) {
184 0         0 local $/;
185 0         0 $content = ;
186             }
187             else {
188 1         5 $content = path( $file )->slurp;
189             }
190            
191             my $json = encode_json( {
192             contents => $content,
193 1 50       316 highlight => $opts->{highlight} ? \1 : \0,
194             } );
195            
196 1         175 my $endpoint = $self->_get_endpoint( 'text' );
197 1         20 my $response = $self->useragent->post(
198             $endpoint => {
199             content => $json,
200             headers => { 'Content-Type' => 'application/json' },
201             },
202             );
203            
204 1         20 return sprintf( '%s/%s', $endpoint, $self->_handle_response( $response ) );
205             }
206              
207             sub share_link {
208 1     1 1 15463 my ( $self, $file, $opts ) = ( shift, @_ );
209 1   50     5 $opts //= {};
210            
211 1         2 my $forward;
212 1 50       5 if ( $file eq '-' ) {
213 0         0 local $/;
214 0         0 $forward = <>;
215             }
216             else {
217 1         3 $forward = $file;
218             }
219            
220 1         2 chomp $forward;
221            
222 1         7 my $json = encode_json( {
223             forward => $forward,
224             } );
225            
226 1         168 my $endpoint = $self->_get_endpoint( 'link' );
227 1         19 my $response = $self->useragent->post(
228             $endpoint => {
229             content => $json,
230             headers => { 'Content-Type' => 'application/json' },
231             },
232             );
233            
234 1         21 return sprintf( '%s/%s', $endpoint, $self->_handle_response( $response ) );
235             }
236              
237             1;
238              
239             __END__