Compare commits

...

2 Commits

Author SHA1 Message Date
2c4341e5e8 Update README 2021-01-27 02:21:04 -06:00
2fb59d407e Add a bootleg stow utility 2021-01-27 02:20:08 -06:00
2 changed files with 243 additions and 0 deletions

View File

@ -35,5 +35,6 @@ Included within are also a handful of management scripts.
| script | description |
| --- | --- |
| `bootleg-stow` | USE AT YOUR OWN RISK. Tries its best to emulate Stow in an environment where it's not available. Should at the very least allow you to install and update these dotfiles without too much headache. |
| `deploy-all.sh` | Deploys all packages. Note that I reserve the right to produce packages with conflicting contents, so in the future this may be refined to only deploy a specific subset of packages (base, desktop-common, etc.). |
| `submodule-checkout-masters-and-update.sh` | For every submodule in the repo, checks out its master branch (instead of the exact commit specified in the base repo) and then pulls from upstream. Effectively, this updates any submodules in the repo, such as Vim plugins, themes, etc. |

242
bootleg-stow Executable file
View 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 "$@"