From 9674738792a1edf1d08726d704d8cf00ad45d896 Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Thu, 14 Jul 2016 11:37:42 -0500 Subject: Apply environment expansion to options in .stowrc files Expand environment variables used in stowrc, as requested in https://savannah.gnu.org/bugs/?41826 This is achieved by creating a new function expand_environment() that replaces any substring of the form '$VAR' or '${VAR}' with contents of environment variable $VAR. Literal '$' can be given by '\$'. N.B. The function is only applied to the --target and --dir options, and only for options specified in .stowrc; cli options are left untouched. Undefined variables are expanded to the empty string, as they would be in normal shell parameter expansion. Unit tests added accordingly: - Test expand_environment(): * Expand $HOME * Expand ${HOME} * Expand ${WITH SPACE} * Expand '\$HOME'. Expected is '$HOME' * Expand ${UNDEFINED}. Expected is ''. - Test that it's applied to the correct options. - Test that CLI options are not expanded. --- bin/stow.in | 54 +++++++++++++++++++++++++++++++++++++++++++++++++-- t/cli_options.t | 13 ++++++++++++- t/rc_options.t | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/bin/stow.in b/bin/stow.in index d056cec..dd6fb8e 100755 --- a/bin/stow.in +++ b/bin/stow.in @@ -632,7 +632,8 @@ sub check_packages { # Returns : (\%rc_options, \@rc_pkgs_to_unstow, \@rc_pkgs_to_stow) # Throws : a fatal error if a bad option is given # Comments : Parses the contents of '~/.stowrc' and '.stowrc' with the same -# parser as the command line options. +# parser as the command line options. Additionally expands any +# environment variables in --target or --dir options. #============================================================================= sub get_config_file_options { my @defaults = (); @@ -647,7 +648,56 @@ sub get_config_file_options { close $FILE or die "Could not close open file: $file\n"; } } - return parse_options(@defaults); + + # Parse the options + my ($rc_options, $rc_pkgs_to_unstow, $rc_pkgs_to_stow) = parse_options(@defaults); + + # Expand environment variables and glob characters. + if (exists $rc_options->{target}) { + $rc_options->{target} = + expand_environment($rc_options->{target}, '--target option'); + } + if (exists $rc_options->{dir}) { + $rc_options->{dir} = + expand_environment($rc_options->{dir}, '--dir option'); + } + + return ($rc_options, $rc_pkgs_to_unstow, $rc_pkgs_to_stow); +} + +#===== SUBROUTINE ============================================================ +# Name : expand_environment() +# Purpose : Expands evironment variables. +# Parameters: $path => string to perform expansion on. +# : $source => where the string came from +# Returns : String with replacements performed. +# Throws : n/a +# Comments : Variable replacement mostly based on SO answer +# : http://stackoverflow.com/a/24675093/558820 +#============================================================================= +sub expand_environment { + my ($path, $source) = @_; + # Replace non-escaped $VAR and ${VAR} with $ENV{VAR} + # If $ENV{VAR} does not exist, perl will raise a warning + # and then happily treat it as an empty string. + $path =~ s/(? 9; +use Test::More tests => 10; use testutil; @@ -82,5 +82,16 @@ local @ARGV = ( ($options, $pkgs_to_delete, $pkgs_to_stow) = process_options(); is_deeply($options->{ignore}, [ qr(~\z), qr(\.#.*\z) ] => 'ignore temp files'); +# +# Check that expansion not applied. +# +local @ARGV = ( + "--target=$OUT_DIR/".'$HOME', + 'dummy' +); +make_dir("$OUT_DIR/".'$HOME'); +($options, $pkgs_to_delete, $pkgs_to_stow) = process_options(); +is($options->{target}, "$OUT_DIR/".'$HOME', 'no expansion'); +remove_dir("$OUT_DIR/".'$HOME'); # vim:ft=perl diff --git a/t/rc_options.t b/t/rc_options.t index 5f0fe87..c6c2061 100755 --- a/t/rc_options.t +++ b/t/rc_options.t @@ -7,7 +7,7 @@ use strict; use warnings; -use Test::More tests => 4; +use Test::More tests => 15; use testutil; @@ -72,6 +72,64 @@ make_file($RC_FILE, $rc_contents); is_deeply($options->{defer}, [qr(\Ainfo), qr(\Aman)], 'defer man and info'); +# ======== Filepath Expansion Tests ======== +# Test proper filepath expansion in rc file. +# Expansion is only applied to options that +# take a filepath, namely target and dir. +# ========================================== + + +# +# Test environment variable expansion function. +# +# Basic expansion +is(expand_environment('$HOME/stow'), "$OUT_DIR/stow", 'expand $HOME'); +is(expand_environment('${HOME}/stow'), "$OUT_DIR/stow", 'expand ${HOME}'); + +delete $ENV{UNDEFINED}; # just in case +foreach my $var ('$UNDEFINED', '${UNDEFINED}') { + eval { + expand_environment($var, "--foo option"); + }; + is( + $@, + "--foo option references undefined environment variable \$UNDEFINED; " . + "aborting!\n", + "expand $var" + ); +} + +# Expansion with an underscore. +$ENV{'WITH_UNDERSCORE'} = 'test string'; +is(expand_environment('${WITH_UNDERSCORE}'), 'test string', + 'expand ${WITH_UNDERSCORE}'); +delete $ENV{'WITH_UNDERSCORE'}; +# Expansion with escaped $ +is(expand_environment('\$HOME/stow'), '$HOME/stow', 'expand \$HOME'); + +# +# Test that environment variable expansion is applied. +# +$rc_contents = <<'HERE'; +--dir=$HOME/stow +--target=$HOME/stow +--ignore=\$HOME +--defer=\$HOME +--override=\$HOME +HERE +make_file($RC_FILE, $rc_contents); +($options, $pkgs_to_delete, $pkgs_to_stow) = get_config_file_options(); +is($options->{dir}, "$OUT_DIR/stow", + "apply environment expansion on stowrc --dir"); +is($options->{target}, "$OUT_DIR/stow", + "apply environment expansion on stowrc --target"); +is_deeply($options->{ignore}, [qr(\$HOME\z)], + "environment expansion not applied on --ignore"); +is_deeply($options->{defer}, [qr(\A\$HOME)], + "environment expansion not applied on --defer"); +is_deeply($options->{override}, [qr(\A\$HOME)], + "environment expansion not applied on --override"); + # Clean up files used for testing. # unlink $RC_FILE or die "Unable to clean up $RC_FILE.\n"; -- cgit v1.2.1