summaryrefslogtreecommitdiff
path: root/bin/hstow
diff options
context:
space:
mode:
authorSean Whitton <spwhitton@spwhitton.name>2023-01-24 12:44:52 -0700
committerSean Whitton <spwhitton@spwhitton.name>2023-01-27 15:16:03 -0700
commit168d234cfd27016de7e7353ee8655ada18352ec7 (patch)
treef8095312e8be44e1c9ba3a7102bccadc276fb75e /bin/hstow
parent6a1c94e77a2b5cf73903df83ca9d0f381cb4fc5c (diff)
downloaddotfiles-168d234cfd27016de7e7353ee8655ada18352ec7.tar.gz
hstow: use find(1)'s -path argument rather than filtering its output
The -path argument is included in POSIX.1-2017. Also improve the awk code in the case where .hstow-always-adopt doesn't exist.
Diffstat (limited to 'bin/hstow')
-rwxr-xr-xbin/hstow88
1 files changed, 59 insertions, 29 deletions
diff --git a/bin/hstow b/bin/hstow
index 7dbcf0ec..9e2e42c2 100755
--- a/bin/hstow
+++ b/bin/hstow
@@ -78,11 +78,37 @@ readlinks () {
}
disjoin_file () {
- if [ -e "$DIR/$1" ]; then
- while read -r line; do
- [ -n "$line" ] && printf "|$2" "$line"
- done <"$DIR/$1" | sed 's#^.##; s#/#\\/#g'
- fi
+ while read -r line; do
+ [ -n "$line" ] && printf "|$2" "$line"
+ done <"$DIR/$1" | sed 's#^.##; s#/#\\/#g'
+}
+
+globs_to_find_args () {
+ local file="$DIR/$1"; shift
+ printf '%s\n' "$@" | cat - $([ -e "$file" ] && echo "$file") \
+ | awk -F'\n' -vOFS='\t' \
+ '/\/\*$/ { sub(/..$/, ""); prune[++c] = "./" $0; next }
+ $0 { notpath[++d] = "./" $0 }
+ # We want to prevent find(1) recursing into directories
+ # of these names, but not prevent the remainder of the
+ # find(1) expression from matching the names themselves.
+ # This is what is correct for globs of the form "dir/*".
+ # While it is true that neither of the find(1) commands
+ # which use globs_to_find_args() match directories, such
+ # that it would not make sense to add a line to one of
+ # .hstow-*-ignore with the aim of excluding the contents
+ # of a directory but not the directory itself, it could
+ # be that the name is currently a symlink that we should
+ # unstow. (Also note that we do not pass -L to find(1),
+ # so we will not recurse into symlinks to dirs anyway.)
+ END { if (c) {
+ print "(", "(", "-path", prune[1]
+ for (i = 2; i <= c; i++)
+ print "-o", "-path", prune[i]
+ print ")", "-prune", "-o", "-name", "*", ")"
+ }
+ for (j in notpath) print "!", "-path", notpath[j]
+ }'
}
usage () {
@@ -104,22 +130,24 @@ stow () {
}
stow1 () {
- ignores="$(disjoin_file .hstow-local-ignore "./%s")"
-
- # Files that (i) always/often have their symlinks replaced with
- # regular files when applications access them; and (ii) we don't
- # ever want to edit the copy under $DIR directly, but only via the
- # link/copy under $HOME.
- $always_adopt \
- && adoptp=1 \
- || adoptp="rel ~ /^($(disjoin_file .hstow-always-adopt "%s"))/"
-
- find . ! -name . ! -type d ! -name "$cchars" \
- ! -name .gitignore \
- ! -name .hstow-local-ignore \
- ! -name .hstow-always-adopt \
- ! -name .hstow-unstow-ignore \
- | awk -F'\n' -vOFS='\t' '! /^(\.\/\.git\/|'"$ignores"')/ \
+ if $always_adopt; then
+ adoptp=1
+ elif ! [ -e .hstow-always-adopt ]; then
+ adoptp=0
+ else
+ # EREs matching files that (i) always/often have their symlinks
+ # replaced with regular files when applications access them; and
+ # (ii) we don't ever want to edit the copy under $DIR directly,
+ # but only via the link/copy under $HOME.
+ # We might list globs in this file & convert them to EREs here.
+ adoptp="$(printf 'rel ~ /^(%s)/' \
+ "$(disjoin_file .hstow-always-adopt "%s")")"
+ fi
+ find . $(globs_to_find_args .hstow-local-ignore ".git/*") \
+ ! -name . ! -type d ! -name "$cchars" ! -name .gitignore \
+ ! -name .hstow-local-ignore ! -name .hstow-always-adopt \
+ ! -name .hstow-unstow-ignore -print \
+ | awk -F'\n' -vOFS='\t' '
{ rel = $1; gsub(/\/dot[-.]/, "/.", rel); gsub(/^\.\//, "", rel)
dotdotslashes = rel
sub(/[^\/]*$/, "", dotdotslashes)
@@ -157,11 +185,9 @@ stow1 () {
unstow () {
cd "$HOME"
- # For speed, skip directories into which we'll never stow anything.
- ignores="$(disjoin_file .hstow-unstow-ignore "./%s")"
-
- dir_pat=".$(echo $DIR | cut -c$(echo $HOME | wc -m | tr -d ' ')-)/"
- dirs_pat="$(echo "^($dir_pat|$ignores)" | sed -e 's#\.#\\.#g')"
+ # .hstow-unstow-ignore is a list of globs matching dirs into which
+ # we'll never stow anything. We have this for the sake of speed.
+ #
# awk's close() calls pclose(3), completing all the link deletions.
# POSIX.1 "Utility Description Defaults", "Consequences of Errors"
# implies that should rmdir(1) encounter a non-empty directory, it
@@ -169,9 +195,13 @@ unstow () {
# Thus, here, -p means that we do not need to sort the operands.
# We don't know the code with which rmdir(1) will exit, and if it is
# 255 then xargs will give up. So we wrap in a call to sh -c.
- find . ! \( -user "$(id -un)" -o -group "$(id -gn)" \) \
- -prune -o -type l ! -name . ! -name "$cchars" -print \
- | grep -Ev "$dirs_pat" | readlinks 0 true \
+ find . ! \( -user "$(id -un)" -o -group "$(id -gn)" \) -prune -o \
+ $(globs_to_find_args \
+ .hstow-unstow-ignore \
+ "$(echo "$DIR" \
+ | cut -c$((1 + $(echo "$HOME" | wc -m)))-)/*") \
+ -type l ! -name . ! -name "$cchars" -print \
+ | readlinks 0 true \
| awk -F'\t' -vOFS='\t' '$2 ~ /^(\.\.\/)*\.STOW\/'"$NAME"'\// \
{ gsub(/"/, "\"'"'"'\"'"'"'\"", $1)
printf "\"%s\"\n", $1 | "xargs -E '' -- rm -f"