Created
October 2, 2024 00:03
-
-
Save ascarter/01bf9acdfdad9edd238f38fe4e1b6174 to your computer and use it in GitHub Desktop.
stow
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# dfstow is a simplified version of GNU Stow, a tool that helps manage multiple | |
# software packages in a single directory. | |
# | |
# It does this by creating symbolic links from the destination directory | |
# to the original files and directories. | |
# | |
# The destination directory is created if it doesn't exist. | |
# | |
# If a file or directory already exists in the destination directory, | |
# it's replaced with a symbolic link. | |
# | |
# If a file or folder starts with `dot-`, the `dot-` prefix is replaced with a | |
# dot (`.`) when creating the symlink. | |
# | |
# Tree folding is not supported. This means that all directories and files | |
# in the source directory are linked to the destination directory. | |
# | |
# See https://www.gnu.org/software/stow/ for reference. | |
set -eu | |
# set -x | |
STOW_DIR=${STOW_DIR:-$PWD} | |
TARGET_DIR=${TARGET_DIR:-$STOW_DIR/..} | |
# Set default options | |
ADOPT=0 | |
DELETE=0 | |
LIST=0 | |
VERBOSE=0 | |
usage() { | |
echo "Usage: $0 [options] package1 [package2 ...]" >&2 | |
echo "Options:" | |
echo " -d DIR Set stow directory (default: $STOW_DIR)" | |
echo " -t DIR Set target directory (default: $TARGET_DIR)" | |
echo " -a Adopt existing files" | |
echo " -D Delete existing symlinks" | |
echo " -l List packages" | |
echo " -v Verbose" | |
} | |
log() { | |
if [ "$#" -eq 1 ]; then | |
printf "%s\n" "$1" | |
elif [ "$#" -gt 1 ]; then | |
printf " $(tput bold)%-10s$(tput sgr0)\t%s\n" "$1" "$2" | |
fi | |
} | |
vlog() { | |
if [ $VERBOSE -eq 1 ]; then | |
log "$@" | |
fi | |
} | |
err() { | |
echo "$*" >&2 | |
} | |
main() { | |
if [ "$#" -lt 1 ]; then | |
usage | |
exit 1 | |
fi | |
mkdir -p "$TARGET_DIR" | |
# Convert to absolute paths | |
STOW_DIR=$(readlink -f $STOW_DIR) | |
TARGET_DIR=$(readlink -f $TARGET_DIR) | |
vlog "STOW_DIR" "$STOW_DIR" | |
vlog "TARGET_DIR" "$TARGET_DIR" | |
for package in "$@"; do | |
log "stow $package" | |
for f in $(find ${package} -type f -print); do | |
p=$(readlink -f ${f}) | |
t=${TARGET_DIR}/${f#${package}/} | |
# Convert all `dot-` to `.` in the path | |
t=$(echo $t | sed 's/dot-/\./g') | |
# Check if source is in ignore list | |
if [ -f "${STOW_DIR}/.stowignore" ]; then | |
if grep -q "${f}" "${STOW_DIR}/.stowignore"; then | |
log "ignore" "${f}" | |
continue | |
fi | |
fi | |
# Check if symlink exists | |
if [ -h "${t}" ]; then | |
if [ $DELETE -eq 1 ] && [ $LIST -eq 0 ]; then | |
# Delete symlink | |
log "unlink" "${t}" | |
rm "${t}" | |
else | |
log "exists" "${t}" | |
fi | |
else | |
if [ $DELETE -eq 0 ] && [ $LIST -eq 0 ]; then | |
# Check if a file already exists | |
if [ -e "${t}" ]; then | |
if [ $ADOPT -eq 1 ]; then | |
# Adopt existing file | |
log "adopt" "${t}" | |
mv ${t} ${p} | |
else | |
# Conflict | |
log "conflict" "${t}" | |
continue | |
fi | |
fi | |
# Symlink file | |
log "link" "${p} -> ${t}" | |
mkdir -p $(dirname "${t}") | |
ln -s ${p} ${t} | |
else | |
log "missing" "${t}" | |
fi | |
fi | |
done | |
done | |
# Remove empty directories | |
if [ $DELETE -eq 1 ] && [ $LIST -eq 0 ]; then | |
find ${TARGET_DIR} -type d -empty -delete | |
fi | |
} | |
while getopts ":d:t:aDlv" opt_char; do | |
case $opt_char in | |
a) ADOPT=1 ;; | |
D) DELETE=1 ;; | |
d) STOW_DIR="$OPTARG" ;; | |
l) LIST=1 ;; | |
t) TARGET_DIR="$OPTARG" ;; | |
v) VERBOSE=1 ;; | |
\?) usage && exit 1 ;; | |
esac | |
done | |
shift "$(($OPTIND - 1))" | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment