Add a bootleg stow utility
This commit is contained in:
parent
779b8c6c9d
commit
2fb59d407e
242
bootleg-stow
Executable file
242
bootleg-stow
Executable file
@ -0,0 +1,242 @@
|
||||
#! /bin/bash
|
||||
#
|
||||
# bootleg-stow
|
||||
# Copyright (C) 2021 Vintage Salt <rehashedsalt@cock.li>
|
||||
#
|
||||
# Distributed under terms of the MIT license.
|
||||
#
|
||||
set -e
|
||||
|
||||
# Read-only set-once variables
|
||||
declare -r _name="$(basename -- "$0")"
|
||||
declare -i _opthelp
|
||||
declare -i _optverbose
|
||||
# Modes
|
||||
declare -i _optstow=1
|
||||
declare -i _optunstow
|
||||
# Working variables
|
||||
declare -a _args
|
||||
declare _return
|
||||
declare _files
|
||||
declare _directories
|
||||
|
||||
# Helper functions
|
||||
log() {
|
||||
# Print a line to the terminal if _optverbose is greater than $2
|
||||
# $2 defaults to 0
|
||||
# loglevel 0: Daily-use messages
|
||||
# loglevel 1: Detailed but not quite debugging
|
||||
# loglevel 2: Definitely debugging
|
||||
[ -z "$1" ] && return 1
|
||||
if (( _optverbose >= ${2:-0} )); then
|
||||
printf "%s\\n" "$1"
|
||||
fi
|
||||
}
|
||||
warn() {
|
||||
# Print a yellow line to the terminal, respecting _optverbose
|
||||
[ -z "$1" ] && return 1
|
||||
if (( _optverbose >= ${2:-0} )); then
|
||||
if [ -t 1 ]; then
|
||||
printf "\\e[33m%s\\e[0m\\n" "$1"
|
||||
else
|
||||
printf "WARN: %s\\n" "$1"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
error() {
|
||||
# Print a red line to the terminal, exit if $2 is specified
|
||||
[ -z "$1" ] && return 1
|
||||
if [ -t 2 ]; then
|
||||
printf "\\e[31m%s\\e[0m\\n" "$1" 1>&2
|
||||
else
|
||||
printf "ERROR: %s\\n" "$1" 1>&2
|
||||
fi
|
||||
[ -z "$2" ] && return
|
||||
exit "${2:-1}"
|
||||
}
|
||||
has() {
|
||||
# Parse out all arguments and try to find them in path
|
||||
# If an argument cannot be found, set _return and fail
|
||||
for prog in "$@"; do
|
||||
if ! command -v "$prog" > /dev/null 2>&1; then
|
||||
_return="$prog"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
return 0
|
||||
}
|
||||
checkconflict() {
|
||||
# Take a directory as argument 1 and stow its contents in ..
|
||||
[ -z "$1" ] && return 1
|
||||
if ! [ -d "$1" ]; then
|
||||
error "Could not find directory: $1" 2
|
||||
fi
|
||||
if ! [ -r "$1" ]; then
|
||||
error "Could not read directory: $1" 2
|
||||
fi
|
||||
# Get our list of files
|
||||
local files="$(find "$1" -type f)"
|
||||
local directories="$(find "$1" -type d)"
|
||||
local -a conflict
|
||||
# Iterate over them
|
||||
for file in $files; do
|
||||
# Get the basename of the file
|
||||
filename="${file#"$1"/}"
|
||||
log "$filename" 2
|
||||
if [ -f ../"$filename" ]; then
|
||||
if [ -h ../"$filename" ]; then
|
||||
continue
|
||||
else
|
||||
conflict+=("$filename")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if [ -n "${conflict[*]}" ]; then
|
||||
warn "The following non-symlinks would be touched by this operation:"
|
||||
for file in "${conflict[@]}"; do
|
||||
echo "$file"
|
||||
done
|
||||
error "Please resolve these conflicts manually." 3
|
||||
fi
|
||||
_files="$files"
|
||||
_directories="$directories"
|
||||
}
|
||||
stow() {
|
||||
# Stow all of _files and _directories in $1 as a package name
|
||||
# Note that you should checkconflict first
|
||||
[ -z "$1" ] && return 1
|
||||
stowdir="$(basename -- "$PWD")"
|
||||
log "Stow directory is: $stowdir" 2
|
||||
pushd .. > /dev/null 2>&1
|
||||
for dir in $_directories; do
|
||||
dirname="${dir#"$1"/}"
|
||||
if [ "$dir" == "$1" ]; then
|
||||
log "Skipping package directory: $dir" 2
|
||||
continue
|
||||
fi
|
||||
if ! [ -d "$dirname" ]; then
|
||||
log "Creating directory: $dirname" 2
|
||||
mkdir -p "$dirname"
|
||||
fi
|
||||
done
|
||||
for file in $_files; do
|
||||
filename="${file#"$1"/}"
|
||||
if has realpath dirname; then
|
||||
path="$(realpath --relative-to="$(dirname "$filename")" "$PWD/$stowdir/$1/$filename")"
|
||||
else
|
||||
path="$PWD/$stowdir/$1/$filename"
|
||||
fi
|
||||
log "Linking file: $filename to $path" 2
|
||||
if [ -h "$filename" ]; then
|
||||
rm "$filename"
|
||||
fi
|
||||
ln -s "$path" "$filename"
|
||||
done
|
||||
popd > /dev/null 2>&1
|
||||
}
|
||||
unstow() {
|
||||
# Unstow all of _files and _directories
|
||||
pushd .. > /dev/null 2>&1
|
||||
for file in $_files; do
|
||||
filename="${file#"$1"/}"
|
||||
if [ -h "$filename" ]; then
|
||||
rm "$filename"
|
||||
else
|
||||
warn "File is not a symlink: $filename"
|
||||
fi
|
||||
done
|
||||
_directories="$(echo "$_directories" | tac)"
|
||||
for dir in $_directories; do
|
||||
# We silently ignore errors here so that rmdiring a directory with stuff in
|
||||
# it doesn't break our loop.
|
||||
rmdir -p "$dir" > /dev/null 2>&1 || continue
|
||||
done
|
||||
popd > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# Core program functions
|
||||
printhelp() {
|
||||
cat << EOF
|
||||
Usage: $_name [OPTION]... PACKAGE...
|
||||
Bootleg stow for the poor sods who can't get it.
|
||||
|
||||
-h Print this help text
|
||||
-R Unstow a directory.
|
||||
-S Stow a directory. Default operation.
|
||||
-v Print more status messages. Stacks
|
||||
|
||||
Copyright (c) 2021 rehashedsalt@cock.li
|
||||
Licensed under the MIT license
|
||||
EOF
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
# Parse out arguments
|
||||
while [ -n "$1" ]; do
|
||||
# Parse out flags
|
||||
while getopts ":hRSv" opt; do
|
||||
case $opt in
|
||||
h)
|
||||
_opthelp=1
|
||||
;;
|
||||
R)
|
||||
_optunstow=1
|
||||
;;
|
||||
S)
|
||||
_optstow=1
|
||||
;;
|
||||
v)
|
||||
_optverbose+=1
|
||||
;;
|
||||
:)
|
||||
error "Option requires argument: -$OPTARG" 2
|
||||
;;
|
||||
*)
|
||||
error "Invalid option: -$OPTARG" 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
# Store arguments
|
||||
shift $((OPTIND - 1))
|
||||
if [ -n "$1" ]; then
|
||||
_args+=("$1")
|
||||
shift
|
||||
fi
|
||||
unset OPTIND
|
||||
done
|
||||
# Early hook for help
|
||||
[ -n "$_opthelp" ] && printhelp && exit 0
|
||||
# Validate critical options
|
||||
# TODO: That
|
||||
# Validate core program dependencies
|
||||
log "Validating dependencies" 2
|
||||
if ! has basename find tac; then
|
||||
error "Failed to find program: $_return" 1
|
||||
fi
|
||||
|
||||
# Do the do
|
||||
if [ -n "$_optunstow" ]; then
|
||||
if [ -n "${_args[*]}" ]; then
|
||||
for package in "${_args[@]}"; do
|
||||
checkconflict "$package"
|
||||
unstow "$package"
|
||||
done
|
||||
else
|
||||
error "No package specified" 1
|
||||
fi
|
||||
elif [ -n "$_optstow" ]; then
|
||||
if [ -n "${_args[*]}" ]; then
|
||||
for package in "${_args[@]}"; do
|
||||
checkconflict "$package"
|
||||
stow "$package"
|
||||
done
|
||||
else
|
||||
error "No package specified" 1
|
||||
fi
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
Loading…
Reference in New Issue
Block a user