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   456190 use 5.010001;
  2         16  
2 2     2   11 use strict;
  2         4  
  2         39  
3 2     2   9 use warnings;
  2         2  
  2         112  
4              
5             package App::Filite::Client;
6              
7             our $AUTHORITY = 'cpan:TOBYINK';
8             our $VERSION = '0.001000';
9              
10 2     2   10 use Carp qw( croak );
  2         13  
  2         108  
11 2     2   938 use File::XDG;
  2         9698  
  2         64  
12 2     2   1475 use Getopt::Long qw( GetOptionsFromArray );
  2         20897  
  2         9  
13 2     2   1805 use HTTP::Tiny;
  2         87755  
  2         97  
14 2     2   961 use HTTP::Tiny::Multipart;
  2         4470  
  2         69  
15 2     2   1580 use JSON::PP qw( encode_json decode_json );
  2         28553  
  2         144  
16 2     2   16 use MIME::Base64 qw( encode_base64 );
  2         4  
  2         104  
17 2     2   1886 use Path::Tiny qw( path );
  2         25622  
  2         270  
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         28 useragent => sub { shift->_build_useragent },
23 1         2319 errors => sub { 0 },
24 2     2   1018 };
  2         3538  
  2         29  
25              
26 2     2   2409 use namespace::autoclean;
  2         35617  
  2         7  
27              
28             sub new_from_config {
29 1     1 1 83241 my ( $class ) = ( shift );
30            
31 1         14 state $xdg = File::XDG->new( name => 'filite-client', api => 1 );
32 1   33     93 my $config_file = $ENV{FILITE_CLIENT_CONFIG} // $xdg->config_home->child( 'config.json' );
33 1 50       17 if ( not ref $config_file ) {
34 1         12 $config_file = path( $config_file );
35             }
36 1 50       59 croak "Expected config file: $config_file" unless $config_file->is_file;
37            
38 1         82 my $args = decode_json( $config_file->slurp_utf8 );
39 1         2105 my $self = $class->new( %$args );
40 1         154 return $self;
41             }
42              
43             sub _build_useragent {
44 1     1   3 my ( $self ) = ( shift );
45 1         24 my $auth = encode_base64( sprintf( 'admin:%s', $self->password ) );
46 1         36 chomp $auth;
47 1         16 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             $0: share via a filite server
72              
73             Usage:
74             $0 [options] [filename]
75             cat blah | $0 [options] -
76              
77             Options:
78             --text Share as text
79             --file Share as file
80             --link Share as link
81             --highlight Syntax highligh text
82             --help, --usage Show this usage information
83              
84             STDERR
85             }
86             ## use Test::Tabs
87              
88             sub execute {
89 0     0 1 0 my ( $self, $args ) = ( shift, @_ );
90 0   0     0 $args //= [ @ARGV ];
91 0         0 my $opts = $self->_parse_opts( $args );
92 0 0       0 $args = [ '-' ] unless @$args;
93            
94 0 0       0 if ( $opts->{help} ) {
95 0         0 return $self->_print_usage;
96             }
97            
98 0         0 for my $file ( @$args ) {
99 0         0 my $url = $self->share( $file, $opts );
100 0         0 print "$url\n";
101             }
102            
103 0         0 $self->errors;
104             }
105              
106             sub _guess_mode {
107 7     7   15 my ( $self, $file, $opts ) = ( shift, @_ );
108 7 100       20 return 'link' if $opts->{link};
109 6 100       21 return 'text' if $opts->{text};
110 5 100       12 return 'file' if $opts->{file};
111 4 100       24 return 'link' if $file =~ m{\Ahttps?://\S+\z}is;
112 3 100       10 return 'text' if $opts->{highlight};
113 2 50       6 return 'text' if $file eq '-';
114 2 100       137 return 'file' if -B $file;
115 1         9 return 'text';
116             }
117              
118             sub share {
119 7     7 1 61034 my ( $self, $file, $opts ) = ( shift, @_ );
120 7   50     23 $opts //= {};
121 7         18 my $mode = $self->_guess_mode( $file, $opts );
122 7         20 my $method = "share_$mode";
123 7         30 return $self->$method( $file, $opts );
124             }
125              
126             sub _get_endpoint {
127 3     3   10 my ( $self, $mode ) = ( shift, @_ );
128 3         81 my $server = $self->server;
129 3 50       40 $server = "http://$server" unless $server =~ m{https?:}i;
130 3 50       9 $server .= '/' unless $server =~ m{/$};
131 3         21 return sprintf( '%s%s', $server, lc( substr( $mode, 0, 1 ) ) );
132             }
133              
134             sub _handle_response {
135 3     3   9 my ( $self, $response ) = ( shift, @_ );
136 3 50       12 if ( $response->{success} ) {
137 3         20 return $response->{content};
138             }
139 0         0 my $errs = $self->errors;
140 0         0 ++$errs;
141 0         0 $self->errors( $errs );
142 0         0 warn sprintf( "ERROR: %s %s\n", $response->{status}, $response->{reason} );
143 0         0 return "-";
144             }
145              
146             sub share_file {
147 1     1 1 16199 my ( $self, $file, $opts ) = ( shift, @_ );
148 1   50     5 $opts //= {};
149            
150 1         5 my ( $filename, $content );
151 1 50       5 if ( $file eq '-' ) {
152 0         0 $filename = 'file.data';
153 0         0 local $/;
154 0         0 $content = ;
155             }
156             else {
157 1         6 my $pt = path( $file );
158 1         51 $filename = $pt->basename;
159 1         45 $content = $pt->slurp;
160             }
161            
162 1         246 my $endpoint = $self->_get_endpoint( 'file' );
163 1         21 my $response = $self->useragent->post_multipart(
164             $endpoint => {
165             file => {
166             filename => $filename,
167             content => $content,
168             content_type => 'application/octet-stream',
169             },
170             },
171             );
172            
173 1         31 return sprintf( '%s/%s', $endpoint, $self->_handle_response( $response ) );
174             }
175              
176             sub share_text {
177 1     1 1 12623 my ( $self, $file, $opts ) = ( shift, @_ );
178 1   50     4 $opts //= {};
179            
180 1         3 my $content;
181 1 50       6 if ( $file eq '-' ) {
182 0         0 local $/;
183 0         0 $content = ;
184             }
185             else {
186 1         4 $content = path( $file )->slurp;
187             }
188            
189             my $json = encode_json( {
190             contents => $content,
191 1 50       315 highlight => $opts->{highlight} ? \1 : \0,
192             } );
193            
194 1         179 my $endpoint = $self->_get_endpoint( 'text' );
195 1         19 my $response = $self->useragent->post(
196             $endpoint => {
197             content => $json,
198             headers => { 'Content-Type' => 'application/json' },
199             },
200             );
201            
202 1         20 return sprintf( '%s/%s', $endpoint, $self->_handle_response( $response ) );
203             }
204              
205             sub share_link {
206 1     1 1 15323 my ( $self, $file, $opts ) = ( shift, @_ );
207 1   50     5 $opts //= {};
208            
209 1         3 my $forward;
210 1 50       4 if ( $file eq '-' ) {
211 0         0 local $/;
212 0         0 $forward = <>;
213             }
214             else {
215 1         3 $forward = $file;
216             }
217            
218 1         3 chomp $forward;
219            
220 1         8 my $json = encode_json( {
221             forward => $forward,
222             } );
223            
224 1         165 my $endpoint = $self->_get_endpoint( 'link' );
225 1         19 my $response = $self->useragent->post(
226             $endpoint => {
227             content => $json,
228             headers => { 'Content-Type' => 'application/json' },
229             },
230             );
231            
232 1         19 return sprintf( '%s/%s', $endpoint, $self->_handle_response( $response ) );
233             }
234              
235             1;
236              
237             __END__