From 29cf0a7dce70cd0673a0ac0abd0975f5c9fd88ae Mon Sep 17 00:00:00 2001 From: chocolateboy Date: Sun, 2 Aug 2020 17:40:11 +0100 Subject: [PATCH] allow option bundling, e.g. `-c -D` can be written as `-cD` + add --default-directory as an alias for -D + allow the user agent to be defined via $WAX_USER_AGENT --- Changes | 6 ++ bin/README.md | 12 ++-- bin/wax | 9 +-- lib/App/Wax.pm | 131 +++++++++++++++++------------------------- t/bundling.t | 18 ++++++ t/default-directory.t | 28 +++++++++ t/lib/Test/App/Wax.pm | 28 +++++++-- 7 files changed, 136 insertions(+), 96 deletions(-) create mode 100644 t/bundling.t create mode 100644 t/default-directory.t diff --git a/Changes b/Changes index ab6a8ff..5e96802 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ Revision history for Perl extension App::Wax. +2.4.0 - TBD + + - allow option bundling, e.g. `-c -D` can be written as `-cD` + - add --default-directory as an alias for -D + - allow the user agent to be defined via $WAX_USER_AGENT + 2.3.3 - 2020-05-04 - version bump to fix deletion of previous version from PAUSE diff --git a/bin/README.md b/bin/README.md index 23b8ef9..83cd589 100644 --- a/bin/README.md +++ b/bin/README.md @@ -8,7 +8,7 @@ - [OPTIONS](#options) - [-c, --cache](#-c---cache) - [-d, --dir, --directory STRING](#-d---dir---directory-string) - - [-D](#-d) + - [-D, --default-directory](#-d---default-directory) - [-h, -?, --help](#-h-----help) - [-m, --mirror](#-m---mirror) - [-s, --separator STRING](#-s---separator-string) @@ -77,10 +77,6 @@ Note that the `--cache` and `--mirror` options are mutually exclusive i.e. only one (or neither) should be supplied. Supplying both will cause `wax` to terminate with an error. -Note that option bundling is not currently supported e.g. `wax --cache ---verbose ...` can't be condensed to `wax -cv ...`, and would need to be -written as `wax -c -v ...`. - ### -c, --cache Don't remove the downloaded file(s) after the command exits. Subsequent @@ -91,7 +87,7 @@ If a local file no longer exists, the resource is re-downloaded. Note: by default, files are saved to the system's temp directory, which is typically cleared when the system restarts. To save files to another directory, -use the `-D` or `--directory` option. +use the `--directory` or `--default-directory` option. ### -d, --dir, --directory STRING @@ -101,10 +97,10 @@ Specify the directory to download files to. Default: the system's If the directory doesn't exist, it is created if its parent directory exists. Otherwise, an error is raised. -### -D +### -D, --default-directory Download files to `$XDG_CACHE_HOME/wax` or `$HOME/.cache/wax` rather than the -system's temp directory. Can be overriden by `-d`. +system's temp directory. Can be overriden by `--directory`. If the directory doesn't exist, it is created if its parent directory exists. Otherwise, an error is raised. diff --git a/bin/wax b/bin/wax index 8c9e654..21764b9 100755 --- a/bin/wax +++ b/bin/wax @@ -75,9 +75,6 @@ Note that the C<--cache> and C<--mirror> options are mutually exclusive i.e. only one (or neither) should be supplied. Supplying both will cause C to terminate with an error. -Note that option bundling is not currently supported e.g. C -can't be condensed to C, and would need to be written as C. - =head2 -c, --cache Don't remove the downloaded file(s) after the command exits. Subsequent @@ -88,7 +85,7 @@ If a local file no longer exists, the resource is re-downloaded. Note: by default, files are saved to the system's temp directory, which is typically cleared when the system restarts. To save files to another directory, -use the C<-D> or C<--directory> option. +use the C<--directory> or C<--default-directory> option. =head2 -d, --dir, --directory STRING @@ -98,10 +95,10 @@ L. If the directory doesn't exist, it is created if its parent directory exists. Otherwise, an error is raised. -=head2 -D +=head2 -D, --default-directory Download files to C<$XDG_CACHE_HOME/wax> or C<$HOME/.cache/wax> rather than the -system's temp directory. Can be overriden by C<-d>. +system's temp directory. Can be overriden by C<--directory>. If the directory doesn't exist, it is created if its parent directory exists. Otherwise, an error is raised. diff --git a/lib/App/Wax.pm b/lib/App/Wax.pm index b69d3bd..65dab7e 100644 --- a/lib/App/Wax.pm +++ b/lib/App/Wax.pm @@ -6,6 +6,7 @@ use Digest::SHA qw(sha1_hex); use File::Slurper qw(read_text write_text); use File::Spec; use File::Temp; +use Getopt::Long qw(GetOptionsFromArray :config posix_default require_order bundling no_auto_abbrev no_ignore_case); use IPC::System::Simple qw(EXIT_ANY $EXITVAL systemx); use LWP::UserAgent; use Method::Signatures::Simple; @@ -31,7 +32,7 @@ use constant { SEPARATOR => '--', TEMPLATE => 'XXXXXXXX', TIMEOUT => 60, - USER_AGENT => 'Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0', + USER_AGENT => ($ENV{WAX_USER_AGENT} || 'Mozilla/5.0 (Windows NT 10.0; rv:68.0) Gecko/20100101 Firefox/68.0'), VERBOSE => 0, }; @@ -51,11 +52,9 @@ use constant INFER_EXTENSION => { use constant { OK => 0, E_DOWNLOAD => -1, - E_INVALID_OPTION => -2, + E_INVALID_DIRECTORY => -2, E_INVALID_OPTIONS => -3, - E_INVALID_DIRECTORY => -4, - E_NO_ARGUMENTS => -5, - E_NO_COMMAND => -6, + E_NO_COMMAND => -4, }; has app_name => ( @@ -262,6 +261,19 @@ func _escape ($arg) { return $arg; } +method _use_default_directory () { + # "${XDG_CACHE_HOME:-$HOME/.cache}/wax" + require File::BaseDir; + $self->directory(File::BaseDir::cache_home($self->app_name)); +} + +# dump the version (a combination of the app version and the module version) and +# exit +method version () { + printf "%s (%s %s)$/", $self->app_version, __PACKAGE__, $VERSION; + exit 0; +} + # log a message to stderr with the app's name and message's log level method log ($level, $template, @args) { my $name = $self->app_name; @@ -312,7 +324,7 @@ method is_url ($url) { my ($scheme, $domain, $path, $query, $fragment) = uri_split($url); if ($scheme && ($domain || $path)) { # no domain for file:// URLs - return [ $scheme, $domain, $path, $query, $fragment ]; + return [$scheme, $domain, $path, $query, $fragment]; } } } @@ -443,83 +455,44 @@ method resolve_temp ($_url) { # parse the supplied arrayref of options and return a triple of: # -# command: an arrayref containing the command to execute +# command: an arrayref containing the command to execute and its arguments # resolve: an arrayref of [index, URL] pairs, where index refers to the URL's # (0-based) index in the commmand array # test: true if --test was seen; false otherwise method _parse ($argv) { - my @argv = @$argv; + my @argv = @$argv; # don't mutate the original + my $test = 0; - unless (@argv) { + my $parsed = GetOptionsFromArray(\@argv, + 'c|cache' => sub { $self->cache(1) }, + 'd|dir|directory=s' => sub { $self->directory($_[1]) }, + 'D|default-directory' => sub { $self->_use_default_directory }, + 'h|?|help' => sub { pod2usage(-input => $0, -verbose => 2, -exitval => 0) }, + 'm|mirror' => sub { $self->mirror(1) }, + 's|separator=s' => sub { $self->separator($_[1]) }, + 'S|no-separator' => sub { $self->clear_separator() }, + 't|timeout=i' => sub { $self->timeout($_[1]) }, + 'test' => \$test, + 'u|user-agent=s' => sub { $self->user_agent($_[1]) }, + 'v|verbose' => sub { $self->verbose(1) }, + 'V|version' => sub { $self->version }, + ); + + unless ($parsed) { pod2usage( - -exitval => E_NO_ARGUMENTS, + -exitval => E_INVALID_OPTIONS, -input => $0, - -msg => 'no arguments supplied', -verbose => 0, ); } - my $wax_options = 1; - my $seen_url = 0; - my $test = 0; my (@command, @resolve); + my $seen_url = 0; while (@argv) { - my $arg = shift @argv; - - my $val = sub { - if (@argv) { - return shift(@argv); - } else { - pod2usage( - -exitval => E_INVALID_OPTION, - -input => $0, - -msg => "missing value for $arg option", - -verbose => 1, - ); - } - }; + my $arg = shift(@argv); - if ($wax_options) { - if ($arg =~ /^(?:-c|--cache)$/) { - $self->cache(1); - } elsif ($arg =~ /^(?:-d|--dir|--directory)$/) { - $self->directory($val->()); - } elsif ($arg eq '-D') { - # "${XDG_CACHE_HOME:-$HOME/.cache}/wax" - require File::BaseDir; - $self->directory(File::BaseDir::cache_home($self->app_name)); - } elsif ($arg =~ /^(?:-v|--verbose)$/) { - $self->verbose(1); - } elsif ($arg =~ /^(?:-[?h]|--help)$/) { - pod2usage(-input => $0, -verbose => 2, -exitval => 0); - } elsif ($arg =~ /^(?:-m|--mirror)$/) { - $self->mirror(1); - } elsif ($arg =~ /^(?:-s|--separator)$/) { - $self->separator($val->()); - } elsif ($arg =~ /^(?:-S|--no-separator)$/) { - $self->clear_separator(); - } elsif ($arg eq '--test') { - $test = 1; - } elsif ($arg =~ /^(?:-t|--timeout)$/) { - $self->timeout($val->()); - } elsif ($arg =~ /^(?:-u|--user-agent)$/) { - $self->user_agent($val->()); - } elsif ($arg =~ /^(?:-V|--version)$/) { - printf "%s (%s %s)$/", $self->app_version, __PACKAGE__, $VERSION; - exit 0; - } elsif ($arg =~ /^-/) { # unknown option - pod2usage( - -exitval => E_INVALID_OPTION, - -input => $0, - -msg => "invalid option: $arg", - -verbose => 1, - ); - } else { # non-option: exit the wax-options processing stage - push @command, $arg; - $wax_options = 0; - } - } elsif ($self->has_separator && ($arg eq $self->separator)) { + if ($self->has_separator && ($arg eq $self->separator)) { push @command, @argv; last; } elsif ($self->is_url($arg)) { @@ -530,17 +503,26 @@ method _parse ($argv) { } my $url_index = @resolve + 1; # 1-based - my $_url = [ $arg, $url_index ]; + my $_url = [$arg, $url_index]; $self->debug('url (%d): %s', $url_index, $arg); push @command, $arg; - push @resolve, [ $#command, $_url ]; + push @resolve, [$#command, $_url]; } else { push @command, $arg; } } + unless (@command) { + pod2usage( + -exitval => E_NO_COMMAND, + -input => $0, + -msg => 'no command supplied', + -verbose => 0, + ) + } + return \@command, \@resolve, $test; } @@ -550,22 +532,17 @@ method run ($argv) { my $unlink = []; my ($command, $resolve, $test) = $self->_parse($argv); - unless (@$command) { - $self->log(ERROR => 'no command supplied'); - return $test ? $command : E_NO_COMMAND; - } - if (@$resolve == 1) { my ($command_index, $_url) = @{ $resolve->[0] }; my @resolved = $self->resolve($_url); - $error = $self->_handle([ $command_index, @resolved ], $command, $unlink); + $error = $self->_handle([$command_index, @resolved], $command, $unlink); } elsif (@$resolve) { $self->debug('jobs: %d', scalar(@$resolve)); my @resolved = parallel_map { my ($command_index, $_url) = @$_; - [ $command_index, $self->resolve($_url) ] + [$command_index, $self->resolve($_url)] } @$resolve; for my $resolved (@resolved) { diff --git a/t/bundling.t b/t/bundling.t new file mode 100644 index 0000000..ab82ab2 --- /dev/null +++ b/t/bundling.t @@ -0,0 +1,18 @@ +use strict; +use warnings; + +use FindBin qw($Bin); +use lib "$Bin/lib"; + +use Test::App::Wax qw(@DEFAULT @URL wax_is); +use Test::More tests => 2; + +wax_is( + "wax -cD cmd --foo $URL[0]", + "cmd --foo $DEFAULT[0]" +); + +wax_is( + "wax -Dc cmd --foo $URL[0]", + "cmd --foo $DEFAULT[0]" +); diff --git a/t/default-directory.t b/t/default-directory.t new file mode 100644 index 0000000..ede82da --- /dev/null +++ b/t/default-directory.t @@ -0,0 +1,28 @@ +use strict; +use warnings; + +use FindBin qw($Bin); +use lib "$Bin/lib"; + +use Test::App::Wax qw(@DEFAULT @URL wax_is); +use Test::More tests => 4; + +wax_is( + "wax --cache --default-directory cmd --foo $URL[0]", + "cmd --foo $DEFAULT[0]" +); + +wax_is( + "wax --cache -D cmd --foo $URL[0]", + "cmd --foo $DEFAULT[0]" +); + +wax_is( + "wax -c --default-directory cmd --foo $URL[0]", + "cmd --foo $DEFAULT[0]" +); + +wax_is( + "wax -c -D cmd --foo $URL[0]", + "cmd --foo $DEFAULT[0]" +); diff --git a/t/lib/Test/App/Wax.pm b/t/lib/Test/App/Wax.pm index 7fe6bf0..1ccf72a 100644 --- a/t/lib/Test/App/Wax.pm +++ b/t/lib/Test/App/Wax.pm @@ -11,17 +11,19 @@ use App::Wax; use Method::Signatures::Simple; use Test::Differences qw(eq_or_diff); use Test::TinyMocker qw(mock); +use URI::Split qw(uri_split); my @FILENAMES = ('1.json', '2.html'); -our @KEEP = map { "/cache/file$_" } @FILENAMES; +our @DEFAULT = map { "/home/test/.cache/wax/file$_" } @FILENAMES; +our @KEEP = map { "/keep/file$_" } @FILENAMES; our @TEMP = map { "/tmp/file$_" } @FILENAMES; our @URL = map { "https://example.com/$_" } @FILENAMES; my %FILENAME_TEMP = map { $URL[$_] => $TEMP[$_] } 0 .. $#FILENAMES; my %FILENAME_KEEP = map { $URL[$_] => $KEEP[$_] } 0 .. $#FILENAMES; -our @EXPORT_OK = qw(@KEEP @TEMP @URL wax_is); +our @EXPORT_OK = qw(@DEFAULT @KEEP @TEMP @URL wax_is); # a test helper (assertion) which takes a wax command (string/arrayref) and the # command we expect it to be translated into (string/arrayref) then calls wax @@ -40,7 +42,9 @@ func wax_is ($args, $want) { $wax->dump_command(\@want) ); - my $got = $wax->run([ '--test', @args ]); + local $ENV{XDG_CACHE_HOME} = '/home/test/.cache'; + + my $got = $wax->run(['--test', @args]); local $Test::Builder::Level = $Test::Builder::Level + 1; eq_or_diff $got, \@want, $description; @@ -53,11 +57,25 @@ func wax_is ($args, $want) { mock( 'App::Wax::resolve' => method ($_url) { my ($url, $url_index) = @$_url; - my $filename = $self->keep ? $FILENAME_KEEP{$url} : $FILENAME_TEMP{$url}; + my $dir = $self->directory; + my $filename; + + if ($dir) { + my ($scheme, $domain, $path, $query, $fragment) = uri_split($url); + $path =~ s{^/}{}; + $filename = "$dir/file$path"; + } else { + $filename = $self->keep ? $FILENAME_KEEP{$url} : $FILENAME_TEMP{$url}; + } + my @resolved = ($filename, undef); # (filename, error) return wantarray ? @resolved : \@resolved; - } + }, ); +# don't check/create the directory specified by --directory or implied by +# --default-directory +mock('App::Wax::_check_directory' => sub {}); + 1;