Summary Files Commits Branches @commit @diff @stats
path: root / make
diff options
context:
space:
mode:
Commit375d11b33142e971a82f28ddb4cc7d386e3551cf (patch)
Author
 Syndamia <kamen@syndamia.com>
2026-11-01 17:50:07 +0200
Committer
 Syndamia <kamen@syndamia.com>
2026-11-01 17:50:07 +0200
Files85373ae415fcff7142ee1194594e8bb1e381176b /make
.files-375d11b33142e971a82f28ddb4cc7d386e3551cf.tar
.files-375d11b33142e971a82f28ddb4cc7d386e3551cf.tar.gz
.files-375d11b33142e971a82f28ddb4cc7d386e3551cf.zip
Parente41b78ee956a6adcfc07b3c453f70198214ca599 (diff)
feat!: Rework how .files are managed
1 files changed, 183 insertions, 0 deletions
-rwxr-xr-xmake183
diff --git a/make b/make
new file mode 100755
index 0000000..9731008
--- /dev/null
+++ b/make
@@ -0,0 +1,183 @@
+#!/bin/sh
+# IMPORTANT NOTE: The "local" keyword is not defined in POSIX, so I've decided to work without it.
+# For this reason, I do "$X/$f" instead of assigning local variables.
+# Another thing to consider is that in recursive functions, backtracking logic
+# CANNOT depend on variables, which are changed somewhere in the body before
+# the recursive step.
+
+cd "${0%/*}"
+
+[ -z "$HOSTNAME" ] && HOSTNAME="$(hostname)"
+if [ -z "$HOSTNAME" ]
+then
+ echo "Set HOSTNAME variable to the system's name!" >/dev/stderr
+ exit 1
+fi
+if [ ! -f "./SYSTEM/$HOSTNAME" ]
+then
+ echo "No configuration in ./SYSTEM for '$HOSTNAME'!" >/dev/stderr
+ exit 1
+fi
+
+# __merge_link <.FILES_DIR> <HOME_DIR>
+__merge_link() {
+ while read -r f
+ do
+ # Output file doesn't exist -> Create link
+ # This is also true on broken symlinks
+ if [ ! -e "$2/$f" ]
+ then
+ if [ -h "$2/$f" ]
+ then
+ echo "Warn: '$2/$f' is a broken link to '$(readlink "$2/$f")', updating!"
+ rm "$2/$f"
+ fi
+ ln -s "$1/$f" "$2/$f"
+ # Output file is already linked to current -> Do nothing
+ elif [ -h "$2/$f" -a "$(readlink $2/$f)" = "$1/$f" ]
+ then
+ :
+ # Current file is a file (and output exists) -> Create link but also update current
+ elif [ -f "$1/$f" ]
+ then
+ if [ -f "$2/$f" ]
+ then
+ # When output is NOT a link inside .files,
+ # update current file with the contents of the output
+ # Otherwise,
+ # replace output file with (link to) current
+ if ! cmp -s "$1/$f" "$2/$f"
+ then
+ if [ -h "$2/$f" ]
+ then
+ real="$(readlink "$2/$f")"
+ if [ "${real#$PWD}" = "$real" ]
+ then
+ cp "$real" "$1/$f" 2>/dev/null
+ echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr
+ fi
+ else
+ cp "$2/$f" "$1/$f"
+ echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr
+ fi
+ fi
+ rm "$2/$f"
+
+ ln -s "$1/$f" "$2/$f"
+ else
+ echo "Error: '$2/$f' already exists and is not a regular file!"
+ fi
+ # Current file is a directory (and output exists) -> Link current's files & dirs in output directory
+ elif [ -d "$1/$f" ]
+ then
+ if [ -d "$2/$f" ]
+ then
+ # If output is a link to directory in .files, continuing with merge
+ # will place links inside a directory in .files, which is bad.
+ # Instead, we'll make the link to directory become a directory with
+ # links to all inner files.
+ if [ -h "$2/$f" ]
+ then
+ real="$(realpath "$2/$f")"
+ if [ "${real#$PWD}" != "$real" ]
+ then
+ rm "$2/$f"
+ mkdir "$2/$f"
+ while read -r ff
+ do
+ ln -s "$real/$ff" "$2/$f/$ff"
+ done <<FILES
+$(ls -A1 "$real")
+FILES
+ fi
+ fi
+ __merge_link "$1/$f" "$2/$f"
+ else
+ echo "Error: '$2/$f' already exists and is not a directory!"
+ fi
+ else
+ echo "Error: '$1/$f' is neither a file or directory!"
+ fi
+ done <<FILES
+$(ls -A1 "$1")
+FILES
+}
+
+link() {
+ while read -r dir
+ do
+ dir="${dir%%*/}"
+ if [ ! -d "./$dir" ]
+ then
+ echo "Unknown directory '$dir'!" >/dev/stderr
+ continue
+ fi
+
+ __merge_link "$PWD/$dir" "$HOME"
+ echo "Linked $dir"
+ done <"./SYSTEM/$HOSTNAME"
+}
+
+# __remove_link <.FILES_DIR> <HOME_DIR>
+__remove_link() {
+ while read -r f
+ do
+ if [ -h "$2/$f" ]
+ then
+ real="$(realpath "$2/$f")"
+ # Output file exists and is a link to current -> Remove output
+ if [ "$real" = "$1/$f" ]
+ then
+ rm "$2/$f"
+ continue
+ # Output file exists and is a link in .files -> Do nothing (do not remove from .files)
+ # This is possible in the case when we have .files/X/A and .files/Y/A, and we linked
+ # .files/X but are now unlinking .files/Y
+ elif [ "${real#$PWD}" != "$real" ]
+ then
+ continue
+ fi
+ fi
+ if [ -d "$1/$f" -a -d "$2/$f" ]
+ then
+ __remove_link "$1/$f" "$2/$f"
+ fi
+ done <<FILES
+$(ls -A1 "$1")
+FILES
+ # See IMPORTANT NOTE, since we're aiming for full POSIX compatibility,
+ # the variable f will be overwritten by recursive calls, so this loop
+ # cannot be merged with the loop above (remove dirs on backtracking)
+ while read -r f
+ do
+ # Output file exists and is an empty directory -> Remove it
+ # We're assuming this directory was created by the script and/or the user
+ # will not care for empty dirs
+ if [ -d "$2/$f" -a -z "$(ls -A "$2/$f" 2>/dev/null)" ]
+ then
+ rm -r "$2/$f"
+ fi
+ done <<FILES
+$(ls -A1 "$1")
+FILES
+}
+
+unlink() {
+ while read -r dir
+ do
+ dir="${dir%%*/}"
+ if [ ! -d "./$dir" ]
+ then
+ echo "Unknown directory '$dir'!" >/dev/stderr
+ continue
+ fi
+
+ __remove_link "$PWD/$dir" "$HOME"
+ echo "Unlinked $dir"
+ done <"./SYSTEM/$HOSTNAME"
+}
+
+case "$1" in
+ (unlink) unlink ;;
+ (link|*) link ;;
+esac