From e938cd152dc751bd65f6573766b2f61243434f3e Mon Sep 17 00:00:00 2001 From: Edd Salkield Date: Tue, 14 Jan 2020 22:59:31 +0000 Subject: [PATCH] Allow user to specify the target directory A new default target can be set using: * A target option in rcrc(5) * -T flag A per-dotfiles target can be set using a target manifest file --- Makefile.am | 6 ++++ bin/lsrc.in | 12 ++++--- bin/rcdn.in | 5 +-- bin/rcup.in | 7 ++-- man/lsrc.1 | 5 ++- man/rcdn.1 | 2 ++ man/rcm.7.mustache | 50 ++++++++++++++++++++++++++ man/rcrc.5 | 2 ++ man/rcup.1 | 21 +++++++++-- share/rcm.sh.in | 22 ++++++++++-- test/rcrc-target.t | 13 +++++++ test/rcup-link-files-target-subst.t | 19 ++++++++++ test/rcup-link-files-target-trailing.t | 19 ++++++++++ test/rcup-link-files-target.t | 33 +++++++++++++++++ test/rcup-target-subst.t | 11 ++++++ test/rcup-target.t | 10 ++++++ 16 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 test/rcrc-target.t create mode 100644 test/rcup-link-files-target-subst.t create mode 100644 test/rcup-link-files-target-trailing.t create mode 100644 test/rcup-link-files-target.t create mode 100644 test/rcup-target-subst.t create mode 100644 test/rcup-target.t diff --git a/Makefile.am b/Makefile.am index 16164d9e..04e3eda2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -9,6 +9,12 @@ dist_man_MANS = man/lsrc.1 man/mkrc.1 man/rcdn.1 man/rcup.1 man/rcrc.5 man/rcm.7 dist_pkgdata_DATA = share/rcm.sh TESTS = \ + test/rcup-target.t \ + test/rcup-target-subst.t \ + test/rcrc-target.t \ + test/rcup-link-files-target.t \ + test/rcup-link-files-target-trailing.t \ + test/rcup-link-files-target-subst.t \ test/lsrc-dotfiles-dirs.t \ test/lsrc-excludes.t \ test/lsrc-hostname.t \ diff --git a/bin/lsrc.in b/bin/lsrc.in index 6fb6162c..902f4762 100755 --- a/bin/lsrc.in +++ b/bin/lsrc.in @@ -156,7 +156,7 @@ is_metafile() { host_portion="$(echo "$1" | sed -e 's/host-.*/host-/')" tag_portion="$(echo "$1" | sed -e 's/tag-.*/tag-/')" - [ "x$host_portion" = 'xhost-' -o "x$tag_portion" = 'xtag-' -o "x$1" = "xhooks" ] + [ "x$host_portion" = 'xhost-' -o "x$tag_portion" = 'xtag-' -o "x$1" = "xhooks" -o "x$1" = "xtarget" ] } dotfiles_dir_excludes() { @@ -255,12 +255,13 @@ handle_command_line() { local undotted= local never_undotted= - while getopts :FVqvhI:x:B:S:s:U:u:t:d: opt; do + while getopts :FVqvhI:x:B:S:s:U:u:t:d:T: opt; do case "$opt" in F) show_sigils=1;; h) show_help ;; I) includes="$includes $OPTARG";; t) arg_tags="$arg_tags $OPTARG";; + T) TARGET=$(echo $OPTARG | envsubst) ;; v) verbosity=$(($verbosity + 1));; q) verbosity=$(($verbosity - 1));; d) dotfiles_dirs="$dotfiles_dirs $OPTARG";; @@ -342,6 +343,7 @@ for DOTFILES_DIR in $DOTFILES_DIRS; do $DEBUG "undotted_file_globs: $undotted_file_globs" never_undotted_file_globs="$(dotfiles_dir_excludes "$DOTFILES_DIR" "$NEVER_UNDOTTED")" $DEBUG "never_undotted_file_globs: $never_undotted_file_globs" + current_dest_dir=$(get_target $DOTFILES_DIR) cd -- "$DOTFILES_DIR" DIR_STACK=":$DOTFILES_DIR" @@ -356,7 +358,7 @@ for DOTFILES_DIR in $DOTFILES_DIRS; do dotted=1 fi - handle_file "$file" "$DEST_DIR" "$host_files" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" + handle_file "$file" "$current_dest_dir" "$host_files" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" done popdir fi @@ -373,7 +375,7 @@ for DOTFILES_DIR in $DOTFILES_DIRS; do if is_excluded "$file" "$undotted_file_globs" "$never_undotted_file_globs"; then dotted=1 fi - handle_file "$file" "$DEST_DIR" "$DOTFILES_DIR/tag-$tag" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" + handle_file "$file" "$current_dest_dir" "$DOTFILES_DIR/tag-$tag" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" done popdir fi @@ -392,6 +394,6 @@ for DOTFILES_DIR in $DOTFILES_DIRS; do dotted=1 fi - handle_file "$file" "$DEST_DIR" "$DOTFILES_DIR" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" + handle_file "$file" "$current_dest_dir" "$DOTFILES_DIR" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" done done diff --git a/bin/rcdn.in b/bin/rcdn.in index a2a84728..c864f77f 100755 --- a/bin/rcdn.in +++ b/bin/rcdn.in @@ -10,7 +10,7 @@ remove_link() { $DEBUG "remove_link $1 $2 $3" - if [ "x$dest" = "x/" -o "x$dest" = "x$DEST_DIR" ]; then + if [ "x$dest" = "x/" -o "x$dest" = "x$TARGET" ]; then $VERBOSE "not a symlink, skipping: $original" elif [ -L "$dest" -o "x$sigil" = "xX" ]; then rm_v -rf "$dest" @@ -44,7 +44,7 @@ handle_command_line() { local never_undotted= local hostname= - while getopts :VqvhIKk:x:S:s:U:u:t:d:B: opt; do + while getopts :VqvhIKk:x:S:s:U:u:t:d:B:T: opt; do case "$opt" in h) show_help ;; B) hostname="$OPTARG" ;; @@ -52,6 +52,7 @@ handle_command_line() { k) run_hooks=1 ;; K) run_hooks=0 ;; t) arg_tags="$arg_tags $OPTARG" ;; + T) TARGET=$(echo $OPTARG | envsubst) ;; S) symlink_dirs="$symlink_dirs $OPTARG";; s) never_symlink_dirs="$never_symlink_dirs $OPTARG";; U) undotted="$undotted $OPTARG";; diff --git a/bin/rcup.in b/bin/rcup.in index 910e5345..a0823bf4 100755 --- a/bin/rcup.in +++ b/bin/rcup.in @@ -122,7 +122,7 @@ replace_file() { } is_nested() { - echo "$1" | sed "s:$DEST_DIR/::" | grep '/' >/dev/null + echo "$1" | sed "s:$(get_target $1)/::" | grep '/' >/dev/null } is_identical() { @@ -213,7 +213,7 @@ handle_command_line() { REPLACE_ALL=0 GENERATE= - while getopts :CVqvfghikKI:x:S:s:U:u:t:d:B: opt; do + while getopts :CVqvfghikKI:x:S:s:U:u:t:d:B:T: opt; do case "$opt" in B) hostname="$OPTARG" ;; C) always_copy=1 ;; @@ -227,6 +227,7 @@ handle_command_line() { K) run_hooks=0 ;; q) verbosity=$(($verbosity - 1)) ;; t) arg_tags="$arg_tags $OPTARG" ;; + T) TARGET=$(echo $OPTARG | envsubst) ;; S) symlink_dirs="$symlink_dirs $OPTARG";; s) never_symlink_dirs="$never_symlink_dirs $OPTARG";; U) undotted="$undotted $OPTARG";; @@ -301,7 +302,7 @@ handle_command_line "$@" run_hooks pre up -dests_and_srcs="$(lsrc $LS_ARGS)" +dests_and_srcs="$(lsrc -T $TARGET $LS_ARGS)" saved_ifs="$IFS" IFS=' diff --git a/man/lsrc.1 b/man/lsrc.1 index 5abf7eed..4da48303 100644 --- a/man/lsrc.1 +++ b/man/lsrc.1 @@ -19,7 +19,8 @@ .Op files ... .Sh DESCRIPTION This program lists all configuration files, both the sources in the -dotfiles directories and the destinations in your home directory. +dotfiles directories and the destinations in the destination +directory (by default your home directory). . See .Xr rcup 1 , @@ -90,6 +91,8 @@ protect it from your shell. . .It Fl t Ar TAG list dotfiles according to TAG +.It Fl T Ar DIR +consider dotfiles having destinations within target directory DIR . .It Fl U Ar excl_pat the rc files or directories matching this pattern will not be symlinked or diff --git a/man/rcdn.1 b/man/rcdn.1 index df83e60e..b63008e6 100644 --- a/man/rcdn.1 +++ b/man/rcdn.1 @@ -98,6 +98,8 @@ This can be repeated. .It Fl t Ar TAG remove dotfiles according to .Ar TAG +.It Fl T Ar DIR +remove dotfiles relative to target directory DIR .It Fl U Ar EXCL_PAT any rc file or directory that matches .Ar EXCL_PAT diff --git a/man/rcm.7.mustache b/man/rcm.7.mustache index 949c9dd3..ad6ec296 100644 --- a/man/rcm.7.mustache +++ b/man/rcm.7.mustache @@ -139,6 +139,27 @@ option. For example: .Pp .Dl mkrc -U bin . +.Ss COMMON PROBLEM: CHANGING THE TARGET LOCATION DEPENDENT ON THE VALUE OF AN ENVIRONMENT VARIABLE +By default, the rcm suite will assume that all dotfiles are installed relative to +.Pa $HOME . +If instead, the dotfiles are to be installed to, say, +.Pa $XDG_CONFIG_HOME , +there are two options: + +The default target can be redefined by using +.Va TARGET +location in the rcrc file, or by using the +.Fl T +flag. References to environment variables can be specified in any of these, +e.g. +.Pp +.Dl rcup -T $XDG_CONFIG_HOME + +A new target can be specified for just one dotfiles directory by using the +.Pa target +meta-file (see +.Xr rcup 1). +. .Sh QUICK START FOR EMPTY DOTFILES DIRECTORIES This section is for those who do not have an existing dotfiles directory and whose dotfiles are standard. @@ -249,6 +270,35 @@ macOS users should see the .Sx BUGS section for more details. . +.Sh ALTERNATIVE TARGET INSTALL LOCATIONS +. +Sometimes it is useful to instruct rcm to install dotfiles to a location +other than +.Sx $HOME . + +The default target directory is taken to be the first specified by: +.Bl -enum offset indent -compact +.It +command-line flag +.Fl T , +for an invocation of rcup, rcdn, or lsrc. +.It +Variable +.Va TARGET +in the rcrc file. (see +.Xr rcrc 5 +and the +.Sx FILES +section below) +.It +$HOME +.El + +A target location can also be specified on a per dotfiles directory basis. +This is achieved by placing meta-file +.Pa target +in the root, which contains a single line denoting the desired target directory. + .Sh STANDALONE INSTALLATION SCRIPT . The diff --git a/man/rcrc.5 b/man/rcrc.5 index a0343178..c0fca3fa 100644 --- a/man/rcrc.5 +++ b/man/rcrc.5 @@ -54,6 +54,8 @@ command, but this command is non-standard and can prove unreliable. The variable forces a known hostname. .It Va TAGS the default tags. +.It Va TARGET +the new default location to manage dotfiles relative to . .It Va SYMLINK_DIRS a space-separated list of patterns. Directories matching a pattern are diff --git a/man/rcup.1 b/man/rcup.1 index f4fb89e5..2930296e 100644 --- a/man/rcup.1 +++ b/man/rcup.1 @@ -93,6 +93,8 @@ This option can be repeated. .It Fl t Ar TAG install dotfiles according to .Ar TAG +.It Fl T Ar DIR +install dotfiles relative to target directory DIR .It Fl U Ar EXCL_PAT any rc file that matches .Ar EXCL_PAT @@ -148,8 +150,8 @@ option in .Xr rcrc 5 can be used to list files that must only be copied. .Pp -Three meta files are supported: host-specific files, tagged files, -hooks. +Four meta files are supported: host-specific files, tagged files, +hooks, targets. .Pp Host-specific files go in a directory named for the host, prefixed with .Pa host- . @@ -192,6 +194,21 @@ and .Pa hooks/pre-up/4-eyes will run before .Pa hooks/post-up/2-u-nothing-compares . +.Pp +Target files go directly in the dotfiles directory, and are named +.Pa target . +The first line in the file that is non-empty and refers to an existant +directory is selected to be the target for the dotfiles directory. +If no lines satisfy these requirements, then rcm falls back to the +default target. +Lines in the target file can consist of environment variables, prefixed +with $. E.g. + +.Dl Sx $XDG_CONFIG_HOME +.Dl $HOME/.config + +Directs rcm to manage dotfiles in the standard XDG compliant configuration directory. + .Sh ALGORITHM It is instructive to understand the process .Nm rcup diff --git a/share/rcm.sh.in b/share/rcm.sh.in index a2b44922..666f2775 100644 --- a/share/rcm.sh.in +++ b/share/rcm.sh.in @@ -3,7 +3,7 @@ VERSION="@PACKAGE_VERSION@" #set -x DEBUG=: -DEST_DIR="$HOME" +TARGET="$HOME" PRINT=echo PROMPT=echo_n ERROR=echo_error @@ -149,8 +149,8 @@ run_hooks() { de_dot() { $DEBUG "de_dot $1" - $DEBUG " with DEST_DIR: $DEST_DIR" - echo "$1" | sed -e "s|$DEST_DIR/||" | sed -e 's/^\.//' + $DEBUG " with TARGET: $TARGET" + echo "$1" | sed -e "s|$TARGET/||" | sed -e 's/^\.//' } DELIMITER="\a" @@ -167,8 +167,24 @@ decode() { echo "$file" | tr "$DELIMITER" " " } +get_target() { + while read line; do + line=$(echo "$line" | envsubst) + [ -z "$line" ] && continue + if [ -d $line ]; then + echo $line + return 0 + fi + done 2> /dev/null < "$1/target" + + if [ $? -ne 0 ]; then + echo $TARGET + fi +} + : ${RCRC:=$HOME/.rcrc} if [ -r "$RCRC" ]; then . "$RCRC" fi + diff --git a/test/rcrc-target.t b/test/rcrc-target.t new file mode 100644 index 00000000..1eae3e85 --- /dev/null +++ b/test/rcrc-target.t @@ -0,0 +1,13 @@ + $ . "$TESTDIR/helper.sh" + +The target for rcup should be specifiable on the command line + + $ touch .dotfiles/example + > mkdir "$HOME/target2" + + $ echo 'TARGET="$HOME/target2"' > $HOME/.rcrc + + $ rcup -v > /dev/null + + $ assert_linked "$HOME/target2/.example" "$HOME/.dotfiles/example" + diff --git a/test/rcup-link-files-target-subst.t b/test/rcup-link-files-target-subst.t new file mode 100644 index 00000000..53d0d54f --- /dev/null +++ b/test/rcup-link-files-target-subst.t @@ -0,0 +1,19 @@ + $ . "$TESTDIR/helper.sh" + +Should create symlinks for files and directories, at the location specified by +the target metafile. Should correctly handly variable substitution + + $ touch .dotfiles/example + > mkdir .dotfiles/nested/ + > touch .dotfiles/nested/example + > mkdir .dotfiles/nested/deeply + > touch .dotfiles/nested/deeply/example + + $ mkdir "$HOME/home2" + > echo '$HOME/home2' > .dotfiles/target + + $ rcup -v > /dev/null + + $ assert_linked "$HOME/home2/.example" "$HOME/.dotfiles/example" + $ assert_linked "$HOME/home2/.nested/example" "$HOME/.dotfiles/nested/example" + $ assert_linked "$HOME/home2/.nested/deeply/example" "$HOME/.dotfiles/nested/deeply/example" diff --git a/test/rcup-link-files-target-trailing.t b/test/rcup-link-files-target-trailing.t new file mode 100644 index 00000000..c99d883e --- /dev/null +++ b/test/rcup-link-files-target-trailing.t @@ -0,0 +1,19 @@ + $ . "$TESTDIR/helper.sh" + +Should create symlinks for files and directories, at the location specified by +the target metafile, with trailing forward slash + + $ touch .dotfiles/example + > mkdir .dotfiles/nested/ + > touch .dotfiles/nested/example + > mkdir .dotfiles/nested/deeply + > touch .dotfiles/nested/deeply/example + + $ mkdir "$HOME/home2" + > echo "$HOME/home2/" > .dotfiles/target + + $ rcup -v > /dev/null + + $ assert_linked "$HOME/home2/.example" "$HOME/.dotfiles/example" + $ assert_linked "$HOME/home2/.nested/example" "$HOME/.dotfiles/nested/example" + $ assert_linked "$HOME/home2/.nested/deeply/example" "$HOME/.dotfiles/nested/deeply/example" diff --git a/test/rcup-link-files-target.t b/test/rcup-link-files-target.t new file mode 100644 index 00000000..1cdda31b --- /dev/null +++ b/test/rcup-link-files-target.t @@ -0,0 +1,33 @@ + $ . "$TESTDIR/helper.sh" + +Should create symlinks for files and directories, at the location specified by +the target metafile + + $ touch .dotfiles/example + > mkdir .dotfiles/nested/ + > touch .dotfiles/nested/example + > mkdir .dotfiles/nested/deeply + > touch .dotfiles/nested/deeply/example + + $ mkdir "$HOME/.dotfiles2" + > touch .dotfiles2/example + > mkdir .dotfiles2/nested/ + > touch .dotfiles2/nested/example + > mkdir .dotfiles2/nested/deeply + > touch .dotfiles2/nested/deeply/example + + $ mkdir "$HOME/home2" + > mkdir "$HOME/home3" + > echo -e " \n\$BLAH\n$HOME/home2" > .dotfiles/target + > echo -e " \n\$BLAH\n$HOME/home3" > .dotfiles2/target + + $ echo 'DOTFILES_DIRS="$HOME/.dotfiles2 $HOME/.dotfiles"' > $HOME/.rcrc + + $ rcup -v > /dev/null + + $ assert_linked "$HOME/home2/.example" "$HOME/.dotfiles/example" + $ assert_linked "$HOME/home2/.nested/example" "$HOME/.dotfiles/nested/example" + $ assert_linked "$HOME/home2/.nested/deeply/example" "$HOME/.dotfiles/nested/deeply/example" + $ assert_linked "$HOME/home3/.example" "$HOME/.dotfiles2/example" + $ assert_linked "$HOME/home3/.nested/example" "$HOME/.dotfiles2/nested/example" + $ assert_linked "$HOME/home3/.nested/deeply/example" "$HOME/.dotfiles2/nested/deeply/example" diff --git a/test/rcup-target-subst.t b/test/rcup-target-subst.t new file mode 100644 index 00000000..a92b600e --- /dev/null +++ b/test/rcup-target-subst.t @@ -0,0 +1,11 @@ + $ . "$TESTDIR/helper.sh" + +Should be able to modify the global dotfiles target using a command line argument, +with environment variables within being expanded + + $ touch .dotfiles/example + > mkdir "$HOME/target" + + $ rcup -v -T '$HOME/target' > /dev/null + + $ assert_linked "$HOME/target/.example" "$HOME/.dotfiles/example" diff --git a/test/rcup-target.t b/test/rcup-target.t new file mode 100644 index 00000000..211aef26 --- /dev/null +++ b/test/rcup-target.t @@ -0,0 +1,10 @@ + $ . "$TESTDIR/helper.sh" + +Should be able to modify the global dotfiles target using a command line argument + + $ touch .dotfiles/example + > mkdir "$HOME/target" + + $ rcup -v -T "$HOME/target" > /dev/null + + $ assert_linked "$HOME/target/.example" "$HOME/.dotfiles/example"