File Coverage

blib/lib/App/Open.pm
Criterion Covered Total %
statement 64 72 88.8
branch 21 28 75.0
condition 8 9 88.8
subroutine 14 15 93.3
pod 10 10 100.0
total 117 134 87.3


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: Open.pm
4             #
5             # DESCRIPTION: App::Open, Command-Line interface library
6             #
7             # FILES: ---
8             # BUGS: ---
9             # NOTES: ---
10             # AUTHOR: Erik Hollensbe (),
11             # COMPANY:
12             # VERSION: 1.0
13             # CREATED: 06/02/2008 01:50:56 AM PDT
14             # REVISION: ---
15             #===============================================================================
16              
17             package App::Open;
18              
19 2     2   108177 use strict;
  2         5  
  2         88  
20 2     2   13 use warnings;
  2         4  
  2         62  
21              
22 2     2   2139 use version;
  2         4973  
  2         13  
23             our $VERSION = version::qv("0.0.4");
24              
25 2     2   716 use File::Basename qw(basename);
  2         5  
  2         209  
26 2     2   19883 use URI;
  2         19271  
  2         1755  
27              
28             =head1 NAME
29              
30             App::Open - Library to drive the 'openit' command line tool
31              
32             =head1 USING
33              
34             If you are just looking to use the `openit` command and learn how to configure
35             it, please see App::Open::Using, which addresses this issue.
36              
37             =head1 SYNOPSIS
38              
39             See the `openit` script.
40              
41             =head1 WARNING
42              
43             While this probably can be re-used, it has a specific function to support a
44             specific tool. Use this at your own risk and expect breakage on upgrades.
45             Expect side-effects, even if the author himself detests them.
46              
47             =head1 METHODS
48              
49             =over 4
50              
51             =item new( $config, $filename )
52              
53             `$config` is a App::Open::Config object. `$filename` is a filename or URL which
54             `openit` will attempt to locate a program to launch for it.
55              
56             =cut
57              
58             sub new {
59 14     14 1 717 my ( $class, $config, $filename ) = @_;
60            
61 14         73 my $self = bless { filename => $filename, config => $config }, $class;
62              
63 14         52 $self->parse_filename;
64              
65 14 100 100     163 die "MISSING_ARGUMENT" unless ( $config && $self->{filename} );
66 11 100       97 die "INVALID_ARGUMENT" unless ( $config->isa('App::Open::Config') );
67 10 100 100     33 die "FILE_NOT_FOUND" unless ( $self->is_url || -e $self->{filename} );
68              
69 9         33 $self->config->load_backends;
70              
71 9         61 return $self;
72             }
73              
74             =item filename
75              
76             Produces the stored filename.
77              
78             =cut
79              
80 27     27 1 1022 sub filename { $_[0]->{filename} }
81              
82             =item config
83              
84             Produces the App::Open::Config object
85              
86             =cut
87              
88 16     16 1 106 sub config { $_[0]->{config} }
89              
90             =item is_url
91              
92             Predicate to indicate whether the `filename` is a URL or not. This is a bit
93             distracting as `file` URLs are not indicated by this method. I'll probably get
94             to fixing this shortly after I become a Nobel Laureate.
95              
96             =cut
97              
98 16     16 1 396 sub is_url { $_[0]->{is_url} }
99              
100             =item scheme
101              
102             In the event the `filename` is a URL, return the URL scheme (http, ftp, etc)
103              
104             =cut
105              
106 3     3 1 23 sub scheme { $_[0]->{scheme} }
107              
108             =item parse_filename
109              
110             Figure out if the file is a local file or not. `file` URLs are massaged into
111             filenames, see is_url().
112              
113             =cut
114              
115             sub parse_filename {
116 14     14 1 24 my $self = shift;
117              
118 14         41 my $u = URI->new($self->filename);
119              
120 14 100 66     55346 if (!$u->scheme || $u->scheme eq 'file') {
121 12 50       315 $self->{filename} = $u->path if $u->scheme;
122             } else {
123 2         351 $self->{scheme} = $u->scheme;
124 2         30 $self->{is_url} = 1;
125             }
126              
127 14         223 return;
128             }
129              
130             =item extensions
131              
132             Build a list of extensions from the filename. Since it's possible that files
133             may have multiple extensions (e.g., .tar.gz), we break this down into
134             increasingly diminuitive portions. The idea is that we handle the "largest"
135             extension first, for example, using tar to unpack .tar.gz files, and falling
136             back to gunzip if we have to.
137              
138             =cut
139              
140             sub extensions {
141 7     7 1 769 my $self = shift;
142              
143 7         25 my @extensions = split( /\./, basename($self->filename) );
144              
145 7         19 shift @extensions; # remove the filename
146              
147             #
148             # combine the extensions so that they are a list of full extensions,
149             # ranging from the largest combination to the smallest.
150             #
151             # e.g., foo.jpg.tar.gz would turn into this list:
152             #
153             # jpg.tar.gz, tar.gz, gz
154             #
155              
156 7         104 my @combined_extensions;
157              
158 7         28 while (@extensions) {
159 26         98 push @combined_extensions, join( ".", @extensions );
160 26         254 shift @extensions;
161             }
162              
163 7         53 return @combined_extensions;
164             }
165              
166             =item backends
167              
168             Return the backend list. Please note that these are objects, not merely package
169             names.
170              
171             =cut
172              
173             sub backends {
174 6     6 1 13 my $self = shift;
175              
176 6         25 $self->config->backend_order;
177             }
178              
179             =item lookup_program
180              
181             Locate the program to execute the file, searching the provided backends. If we
182             have found a program and it has a template, it will replace '%s' with the
183             filename in all occurrences. Otherwise, it will append it to the end of the
184             command.
185              
186             This method returns a list suitable for sending to system(). It makes no
187             attempt to correct your potentially problematic shell quoting, but it does
188             ensure that the filename, whether templated or appended, is fully intact and
189             not split across list elements.
190              
191             =cut
192              
193             sub lookup_program {
194 5     5 1 2793 my $self = shift;
195              
196 5         9 my $program;
197             my @command;
198              
199 5         10 foreach my $backend ( @{ $self->backends } ) {
  5         19  
200              
201 5 100       20 if ($self->is_url) {
202 2         9 $program = $backend->lookup_url($self->scheme);
203             } else {
204 3         11 foreach my $ext ( $self->extensions ) {
205 9         33 $program = $backend->lookup_file($ext);
206 9 100       26 last if $program;
207             }
208             }
209              
210 5 100       22 last if $program;
211             }
212              
213 5 100       15 if ($program) {
214 4         18 @command = split(/\s+/, $program);
215 4         88 my $command_changed = 0;
216              
217 4         12 foreach (@command) {
218 5 100       24 if (/%s/) {
219 1         5 s/%s/$self->filename/eg;
  1         5  
220 1         5 $command_changed = 1;
221             }
222             }
223              
224             # if the filename's already in the command, assume we don't need to append
225             # it.
226 4 100       21 push @command, $self->filename unless ($command_changed);
227             }
228              
229 5         43 return @command;
230             }
231              
232             =item execute_program
233              
234             Execute the program against the filename supplied by the constructor.
235              
236             In most cases, this is the only method you need to call; it does all the work
237             for you.
238              
239             =cut
240              
241             sub execute_program {
242 0     0 1   my $self = shift;
243              
244 0           my @command = $self->lookup_program;
245              
246 0 0         die "NO_PROGRAM" unless @command;
247              
248 0 0         if ( $self->config->config->{"fork"} ) {
249 0 0         if (fork) { exec( @command ); }
  0            
250 0           return 0;
251             } else {
252 0           return system( @command );
253             }
254             }
255              
256             =back
257              
258             =head1 LICENSE
259              
260             This file and all portions of the original package are (C) 2008 Erik Hollensbe.
261             Please see the file COPYING in the package for more information.
262              
263             =head1 BUGS AND PATCHES
264              
265             Probably a lot of them. Report them to if you're feeling
266             kind. Report them to CPAN RT if you'd prefer they never get seen.
267              
268             =cut
269              
270             1;