From bb8b79e0313fbf7bb2762a791406548323e98ffe Mon Sep 17 00:00:00 2001 From: Adam Spiers Date: Tue, 25 Jun 2019 19:05:38 +0100 Subject: [PATCH 1/8] Don't warn when .stowrc is used We fully support and expect some users using .stowrc, so there is no reason to issue a warning when they do. --- bin/stow.in | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/stow.in b/bin/stow.in index d64c381..56fc8fb 100755 --- a/bin/stow.in +++ b/bin/stow.in @@ -604,7 +604,6 @@ sub get_config_file_options { my @defaults = (); for my $file ("$ENV{HOME}/.stowrc", '.stowrc') { if (-r $file) { - warn "Loading defaults from $file\n"; open my $FILE, '<', $file or die "Could not open $file for reading\n"; while (my $line = <$FILE>){ From 048203b7f9808acbaec10acbd5892104d3129760 Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Fri, 24 Jun 2016 15:06:06 -0500 Subject: [PATCH 2/8] Change init_test_dirs to point $HOME at $OUT_DIRS Why: * Want to add a new feature to parsing of stowrc files. * Need ability to write .stowrc files for testing without risk of squashing existing files. This change addresses the need by: * Reusing logic in init_test_dirs * init_test_dirs already creates new directory structure and overwrites $HOME to point into /tmp. * This commit changes init_test_dirs to point $HOME at the newly created directory structure ($OUT_DIR) instead of /tmp. * Grants ability to write .stowrc to $HOME without fear. * Pointing $HOME at $OUT_DIR instead of /tmp also makes cleanup easier. * Remove $OUT_DIR vs remove specific files in /tmp. --- t/testutil.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/testutil.pm b/t/testutil.pm index 1e73dfe..a9d673f 100755 --- a/t/testutil.pm +++ b/t/testutil.pm @@ -55,7 +55,7 @@ sub init_test_dirs { } # Don't let user's ~/.stow-global-ignore affect test results - $ENV{HOME} = '/tmp/fake/home'; + $ENV{HOME} = $OUT_DIR; } sub new_Stow { From 237288fb7e87edd0a27348293bd7bd09d820c7d4 Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Fri, 1 Jul 2016 18:38:25 -0500 Subject: [PATCH 3/8] Add test harness for stowrc files Why: * Planning on developing a new feature for parsing of stowrc files. * Need a test harness that performs initialization and clean up * Initialization: Create directory structure that allows creation of stowrc files without worrying about squashing existing files. * Clean up: Remove all files created during testing. This change addresses the need by: * Add intialization and cleanup harness in t/rc_options.t * Define the location to write stowrc files to in $RC_FILE * Ensures that location $RC_FILE does not already exist. * Calls the init_test_dirs to bootstrap directory tree. * After all tests are run, removes $RC_FILE and the testing directory tree. * Add basic test of stowrc parsing to t/rc_options.t * Provides a template of how to create and test a stowrc file. * Newly created t/rc_options.t file added to MANIFEST --- MANIFEST | 1 + t/rc_options.t | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100755 t/rc_options.t diff --git a/MANIFEST b/MANIFEST index 91ae7bf..ed1b9b8 100644 --- a/MANIFEST +++ b/MANIFEST @@ -45,6 +45,7 @@ t/ignore.t t/join_paths.t t/parent.t t/stow.t +t/rc_options.t t/testutil.pm t/unstow.t t/unstow_orig.t diff --git a/t/rc_options.t b/t/rc_options.t new file mode 100755 index 0000000..bf88c68 --- /dev/null +++ b/t/rc_options.t @@ -0,0 +1,53 @@ +#!/usr/local/bin/perl + +# +# Test processing of stowrc file. +# + +use strict; +use warnings; + +use Test::More tests => 2; + +use testutil; + +require 'stow'; + +# stowrc file used for testing. +my $RC_FILE = "$OUT_DIR/.stowrc"; +# Take the safe route and cowardly refuse to continue if there's +# already a file at $RC_FILE. +if (-e $RC_FILE) { + die "RC file location $RC_FILE already exists!\n"; +} + +# Define the variable that will be used to write stowrc. +my $rc_contents; + +# Init testing directory structure and overwrite ENV{HOME} to prevent +# squashing existing stowrc file. +init_test_dirs(); + +# =========== RC Loading Tests =========== +# Basic parsing and loading rc file tests. +# ======================================== + +# +# Test stowrc file with one options per line. +# +local @ARGV = ('dummy'); +$rc_contents = <{target}, "$OUT_DIR/target", "rc options different lines"); +is($options->{dir}, "$OUT_DIR/stow", "rc options different lines"); + +# +# Clean up files used for testing. +# +unlink $RC_FILE or die "Unable to clean up $RC_FILE.\n"; +remove_dir($OUT_DIR); + From ebc895a540e17058042250e351d33da083d5d1dd Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Thu, 14 Jul 2016 08:55:55 -0500 Subject: [PATCH 4/8] Add test for conflicting stowrc and cli args Why: * Want to add feature to stowrc parsing. * Missing regression test for conflicting cli and stowrc options. This change addresses the need by: * Add missing regression tests to rc_options.t * Two types of tests for the two types of options possible. * For scalar options such as --target, cli arguments should overwrite stowrc arguments. * For options that result in a list, such as --ignore, the arguments from cli and stowrc files should be merged. --- t/rc_options.t | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/t/rc_options.t b/t/rc_options.t index bf88c68..5f0fe87 100755 --- a/t/rc_options.t +++ b/t/rc_options.t @@ -7,7 +7,7 @@ use strict; use warnings; -use Test::More tests => 2; +use Test::More tests => 4; use testutil; @@ -46,6 +46,32 @@ is($options->{target}, "$OUT_DIR/target", "rc options different lines"); is($options->{dir}, "$OUT_DIR/stow", "rc options different lines"); # +# Test that scalar cli option overwrites conflicting stowrc option. +# +local @ARGV = ('-d', "$OUT_DIR/stow",'dummy'); +$rc_contents = <{dir}, "$OUT_DIR/stow", "cli overwrite scalar rc option."); + +# +# Test that list cli option merges with conflicting stowrc option. +# Documentation states that stowrc options are prepended to cli options. +# +local @ARGV = ( + '--defer=man', + 'dummy' +); +$rc_contents = <{defer}, [qr(\Ainfo), qr(\Aman)], + 'defer man and info'); + # Clean up files used for testing. # unlink $RC_FILE or die "Unable to clean up $RC_FILE.\n"; From feb885fda6ce3e8adb455f9aad638da56722bf10 Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Fri, 24 Jun 2016 16:34:13 -0500 Subject: [PATCH 5/8] Factor out parsing of options Why: * Want to be able to selectively apply filepath expansion to: 1. Only options in a stowrc file. 2. Only options that take a file path. * Because of need 1, any expansion must be performed on stowrc options before merging with cli options. * Because of need 2, stowrc options need to be parsed before expansion in order to know which options need expanding. This change addresses the need by: * Create parse_options() * Implements option parsing logic previously in process_options() * Takes an array as parameter instead of assuming ARGV * Edit process_options() to still work as expected. * Only change was to call parse_options() instead of directly parsing ARGV * By factoring out the option parsing code, we can reuse the existing parsing code for stowrc options. * Allows expansion of only the option itself, i.e expansion on "$HOME/target" rather than "--target=$HOME/target" * Allows easy determination of which options need expansion. --- bin/stow.in | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/bin/stow.in b/bin/stow.in index 56fc8fb..fb42dbf 100755 --- a/bin/stow.in +++ b/bin/stow.in @@ -432,7 +432,7 @@ use warnings; require 5.006_001; use POSIX qw(getcwd); -use Getopt::Long; +use Getopt::Long qw(GetOptionsFromArray); @USE_LIB_PMDIR@ use Stow; @@ -481,23 +481,49 @@ sub main { #===== SUBROUTINE =========================================================== # Name : process_options() -# Purpose : parse command line options +# Purpose : Parse and process command line and .stowrc file options # Parameters: none # Returns : (\%options, \@pkgs_to_unstow, \@pkgs_to_stow) -# Throws : a fatal error if a bad command line option is given +# Throws : a fatal error if a bad option is given # Comments : checks @ARGV for valid package names #============================================================================ sub process_options { + + unshift @ARGV, get_config_file_options(); + #$,="\n"; print @ARGV,"\n"; # for debugging rc file + + my ($options, + $pkgs_to_unstow, + $pkgs_to_stow) = parse_options(@ARGV); + + sanitize_path_options($options); + check_packages($pkgs_to_unstow, $pkgs_to_stow); + + return ($options, $pkgs_to_unstow, $pkgs_to_stow); +} + +#===== SUBROUTINE =========================================================== +# Name : parse_options() +# Purpose : parse command line options +# Parameters: @arg_array => array of options to parse +# Example: parse_options(@ARGV) +# Returns : (\%options, \@pkgs_to_unstow, \@pkgs_to_stow) +# Throws : a fatal error if a bad command line option is given +# Comments : Used for parsing both command line options and rc file. Used +# for parsing only. Sanity checks and post-processing belong in +# process_options(). +#============================================================================ +sub parse_options { my %options = (); my @pkgs_to_unstow = (); my @pkgs_to_stow = (); my $action = 'stow'; - unshift @ARGV, get_config_file_options(); - #$,="\n"; print @ARGV,"\n"; # for debugging rc file + #$,="\n"; print @_,"\n"; # for debugging rc file Getopt::Long::config('no_ignore_case', 'bundling', 'permute'); - GetOptions( + GetOptionsFromArray( + \@_, \%options, 'verbose|v:+', 'help|h', 'simulate|n|no', 'version|V', 'compat|p', 'dir|d=s', 'target|t=s', @@ -548,12 +574,8 @@ sub process_options { usage() if $options{help}; version() if $options{version}; - sanitize_path_options(\%options); - check_packages(\@pkgs_to_unstow, \@pkgs_to_stow); - return (\%options, \@pkgs_to_unstow, \@pkgs_to_stow); } - sub sanitize_path_options { my ($options) = @_; From 4d1167ffd72c77748a22a8dc1646a0d944be1441 Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Fri, 1 Jul 2016 20:07:28 -0500 Subject: [PATCH 6/8] Parse cli and stowrc files separately Why: * We want to selectively apply expansion to: * Only options from stowrc files. * Only options that take a path. * This requires ability to: * Differentiate cli options from stowrc options * Distinguish between individual stowrc options. This change addresses the need by: * Options from ARGV and stowrc files are separately parsed, resulting in two options hashes. * Creating an option hash from stowrc files allows modification of specific arguments without having to rely on something like regex parsing of the options. * get_config_file_options modified to return options hash. * Uses the same parse_options function that parses ARGV * Add Hash:Merge to dependencies in order to merge the two option hashes. * process_options() merges the two option hashes. * Get option hash for ARGV * Get option hash from get_config_file_options(). * Merge the two hashes with Hash::Merge. * Sanitation and checks are ran on the merged options. * The options -S, -D, and -R are ignored in stowrc files. * Due to the way argument parsing happens, the effect of these actions is not carried from stowrc into parsing of cli options. * It doesn't seem to make sense to put these options in stowrc anyway. --- Build.PL | 1 + META.json | 1 + META.yml | 1 + bin/stow.in | 38 +++++++++++++++++++++++++------------- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Build.PL b/Build.PL index a14b93b..3f63c0c 100644 --- a/Build.PL +++ b/Build.PL @@ -45,6 +45,7 @@ my $build = Module::Build->new( 'perl' => '5.006', 'Carp' => 0, 'IO::File' => 0, + 'Hash::Merge' => 0, }, script_files => [ 'bin/stow', 'bin/chkstow' ], all_from => 'lib/Stow.pm.in', diff --git a/META.json b/META.json index 2a455a4..dd806c9 100644 --- a/META.json +++ b/META.json @@ -17,6 +17,7 @@ "build" : { "requires" : { "IO::Scalar" : "0", + "Hash::Merge": "0", "Test::More" : "0", "Test::Output" : "0" } diff --git a/META.yml b/META.yml index f94d472..9bcbadb 100644 --- a/META.yml +++ b/META.yml @@ -4,6 +4,7 @@ author: - unknown build_requires: IO::Scalar: '0' + Hash::Merge: '0' Test::More: '0' Test::Output: '0' configure_requires: diff --git a/bin/stow.in b/bin/stow.in index fb42dbf..d056cec 100755 --- a/bin/stow.in +++ b/bin/stow.in @@ -438,6 +438,8 @@ use Getopt::Long qw(GetOptionsFromArray); use Stow; use Stow::Util qw(parent error); +use Hash::Merge qw( merge ); + my $ProgramName = $0; $ProgramName =~ s{.*/}{}; @@ -488,17 +490,29 @@ sub main { # Comments : checks @ARGV for valid package names #============================================================================ sub process_options { - - unshift @ARGV, get_config_file_options(); - #$,="\n"; print @ARGV,"\n"; # for debugging rc file - - my ($options, + # Get cli options. + my ($cli_options, $pkgs_to_unstow, $pkgs_to_stow) = parse_options(@ARGV); + # Get the .stowrc options. + # Note that rc_pkgs_to_unstow and rc_pkgs_to_stow are ignored. + my ($rc_options, + $rc_pkgs_to_unstow, + $rc_pkgs_to_stow) = get_config_file_options(); + + # Merge .stowrc and command line options. + # Preference is given to cli options. + # rc options come first in merged arrays. + # cli options overwrite conflicting rc options. + Hash::Merge::set_behavior('RIGHT_PRECEDENT'); + my $options = merge($rc_options, $cli_options); + + # Run checks on the merged options. sanitize_path_options($options); check_packages($pkgs_to_unstow, $pkgs_to_stow); + # Return merged and processed options. return ($options, $pkgs_to_unstow, $pkgs_to_stow); } @@ -611,16 +625,14 @@ sub check_packages { } } - #===== SUBROUTINE ============================================================ # Name : get_config_file_options() # Purpose : search for default settings in any .stowrc files # Parameters: none -# Returns : a list of default options -# Throws : no exceptions -# Comments : prepends the contents of '~/.stowrc' and '.stowrc' to the command -# : line so they get parsed just like normal arguments. (This was -# : hacked in so that Emil and I could set different preferences). +# 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. #============================================================================= sub get_config_file_options { my @defaults = (); @@ -635,7 +647,7 @@ sub get_config_file_options { close $FILE or die "Could not close open file: $file\n"; } } - return @defaults; + return parse_options(@defaults); } #===== SUBROUTINE =========================================================== @@ -650,7 +662,7 @@ sub usage { my ($msg) = @_; if ($msg) { - print "$ProgramName: $msg\n\n"; + warn "$ProgramName: $msg\n\n"; } print <<"EOT"; From 9674738792a1edf1d08726d704d8cf00ad45d896 Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Thu, 14 Jul 2016 11:37:42 -0500 Subject: [PATCH 7/8] 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"; From dc42c34107ff1ad6173b418695bc6d0e4ddbfaf5 Mon Sep 17 00:00:00 2001 From: Charles LeDoux Date: Thu, 14 Jul 2016 14:48:40 -0500 Subject: [PATCH 8/8] Add function to expand ~ in .stowrc files (#14) Add a new expand_tilde() function that performs tilde expansion of strings, and corresponding unit tests: * A ~ at the beginning of a path is expanded to the user's home directory. * Literal '~' can be provided with '\~' Combine this with expand_environment() in a new expand_filepath() function which applies all (both) required expansion functions to a string, and use that in get_config_file_options() to expand .stowrc options. Add more tests to check that tilde expanded in correct places, i.e.: * expanded for --target and --dir * not expanded for --ignore, --defer, or --override Update documentation on stowrc files according to this functionality change. Fixes #14: https://github.com/aspiers/stow/issues/14 --- NEWS | 10 +++++++ bin/stow.in | 73 +++++++++++++++++++++++++++++++++++++++++++++++--- doc/stow.texi | 23 +++++++++++++--- t/rc_options.t | 35 +++++++++++++++++++++++- 4 files changed, 134 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 3cc5562..3b676da 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,16 @@ News file for Stow. Thanks to Joris Vankerschaver for this feature! +*** Shell-like expansion in .stowrc files + + For options within .stowrc files which describe file paths, "~" can + be included to expand to the current value of $HOME, and + environment variables can be referenced e.g. via "$FOO" or + "${FOO}". To prevent expansion, escape with a backslash. + + Thanks a lot to Charles LeDoux for his diligent work on this + feature! + *** chkstow now honours the $STOW_DIR environment variable The stow script already honoured the $STOW_DIR environment diff --git a/bin/stow.in b/bin/stow.in index dd6fb8e..9afe2a1 100755 --- a/bin/stow.in +++ b/bin/stow.in @@ -343,6 +343,28 @@ Stow will re-fold the tree by removing the symlinks to the surviving package, removing the directory, then linking the directory back to the surviving package. +=head1 RESOURCE FILES + +F searches for default command line options at F<.stowrc> (current +directory) and F<~/.stowrc> (home directory) in that order. If both +locations are present, the files are effectively appended together. + +The effect of options in the resource file is similar to simply prepending +the options to the command line. For options that provide a single value, +such as F<--target> or F<--dir>, the command line option will overwrite any +options in the resource file. For options that can be given more than once, +F<--ignore> for example, command line options and resource options are +appended together. + +Environment variables and the tilde character (F<~>) will be expanded for +options that take a file path. + +The options F<-D>, F<-R>, F<-S>, and any packages listed in the resource +file are ignored. + +See the info manual for more information on how stow handles resource +file. + =head1 SEE ALSO The full documentation for F is maintained as a Texinfo manual. @@ -633,7 +655,8 @@ sub check_packages { # 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. Additionally expands any -# environment variables in --target or --dir options. +# environment variables or ~ character in --target or --dir +# options. #============================================================================= sub get_config_file_options { my @defaults = (); @@ -655,16 +678,36 @@ sub get_config_file_options { # Expand environment variables and glob characters. if (exists $rc_options->{target}) { $rc_options->{target} = - expand_environment($rc_options->{target}, '--target option'); + expand_filepath($rc_options->{target}, '--target option'); } if (exists $rc_options->{dir}) { $rc_options->{dir} = - expand_environment($rc_options->{dir}, '--dir option'); + expand_filepath($rc_options->{dir}, '--dir option'); } return ($rc_options, $rc_pkgs_to_unstow, $rc_pkgs_to_stow); } +#===== SUBROUTINE ============================================================ +# Name : expand_filepath() +# Purpose : Handles expansions that need to be applied to +# : file paths. Currently expands environment +# : variables and the tilde. +# Parameters: $path => string to perform expansion on. +# : $source => where the string came from +# Returns : String with replacements performed. +# Throws : n/a +# Comments : n/a +#============================================================================= +sub expand_filepath { + my ($path, $source) = @_; + + $path = expand_environment($path, $source); + $path = expand_tilde($path); + + return $path; +} + #===== SUBROUTINE ============================================================ # Name : expand_environment() # Purpose : Expands evironment variables. @@ -700,6 +743,30 @@ sub _safe_expand_env_var { return $ENV{$var}; } +#===== SUBROUTINE ============================================================ +# Name : expand_tilde() +# Purpose : Expands tilde to user's home directory path. +# Parameters: $path => string to perform expansion on. +# Returns : String with replacements performed. +# Throws : n/a +# Comments : http://docstore.mik.ua/orelly/perl4/cook/ch07_04.htm +#============================================================================= +sub expand_tilde { + my ($path) = @_; + # Replace tilde with home path. + $path =~ s{ ^ ~ ( [^/]* ) } + { $1 + ? (getpwnam($1))[7] + : ( $ENV{HOME} || $ENV{LOGDIR} + || (getpwuid($<))[7] + ) + }ex; + # Replace espaced tilde with regular tilde. + $path =~ s/\\~/~/g; + return $path +} + + #===== SUBROUTINE =========================================================== # Name : usage() # Purpose : print program usage message and exit diff --git a/doc/stow.texi b/doc/stow.texi index 1042ad4..d4e4087 100644 --- a/doc/stow.texi +++ b/doc/stow.texi @@ -881,9 +881,10 @@ directory. Default command line options may be set in @file{.stowrc} (current directory) or @file{~/.stowrc} (home directory). These are parsed in -that order, and effectively prepended to the command line arguments -(with the notable difference that they won't be processed by the shell). -This feature can be used for some interesting effects. +that order, and are appended together if they both exist. The effect of +the options in the resource file is similar to simply prepending the +options to the command line. This feature can be used for some +interesting effects. For example, suppose your site uses more than one stow directory, perhaps in order to share around responsibilities with a number of systems @@ -922,6 +923,22 @@ immediate parent directory @file{/usr/local/stow}), overriding any pre-existing links to bin files or man pages, and ignoring some cruft that gets installed by default. +If an option is provided both on the command line and in a resource file, +the command line option takes precedence. For options that provide a single +value, such as @command{--target} or @command{--dir}, the command line +option will overwrite any options in the resource file. For options that can +be given more than once, @command{--ignore} for example, command line +options and resource options are appended together. + +For options that take a file path, environment variables and the tilde +character (@command{~}) are expanded. An environment variable can be +given in either the @command{$VAR} or @command{$@{VAR@}} form. To +prevent expansion, escape the @command{$} or @command{~} with a +backslash. + +The options @command{-D}, @command{-S}, and @command{-R} are ignored in +resource files. This is also true of any package names given in the +resource file. @c =========================================================================== @node Compile-time vs Install-time, Bootstrapping, Resource Files, Top diff --git a/t/rc_options.t b/t/rc_options.t index c6c2061..6905df5 100755 --- a/t/rc_options.t +++ b/t/rc_options.t @@ -7,7 +7,7 @@ use strict; use warnings; -use Test::More tests => 15; +use Test::More tests => 23; use testutil; @@ -107,6 +107,16 @@ delete $ENV{'WITH_UNDERSCORE'}; # Expansion with escaped $ is(expand_environment('\$HOME/stow'), '$HOME/stow', 'expand \$HOME'); +# +# Test tilde (~) expansion +# +# Basic expansion +is(expand_tilde('~/path'), "$ENV{HOME}/path", 'tilde expansion to $HOME'); +# Should not expand if middle of path +is(expand_tilde('/path/~/here'), '/path/~/here', 'middle ~ not expanded'); +# Test escaped ~ +is(expand_tilde('\~/path'), '~/path', 'escaped tilde'); + # # Test that environment variable expansion is applied. # @@ -130,6 +140,29 @@ is_deeply($options->{defer}, [qr(\A\$HOME)], is_deeply($options->{override}, [qr(\A\$HOME)], "environment expansion not applied on --override"); +# +# Test that tilde expansion is applied in correct places. +# +$rc_contents = <<'HERE'; +--dir=~/stow +--target=~/stow +--ignore=~/stow +--defer=~/stow +--override=~/stow +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(~/stow\z)], + "environment expansion not applied on --ignore"); +is_deeply($options->{defer}, [qr(\A~/stow)], + "environment expansion not applied on --defer"); +is_deeply($options->{override}, [qr(\A~/stow)], + "environment expansion not applied on --override"); + # Clean up files used for testing. # unlink $RC_FILE or die "Unable to clean up $RC_FILE.\n";