dotfiles/manage.sh
2025-11-18 16:13:07 +01:00

270 lines
5.5 KiB
Bash
Executable file

#!/usr/bin/env bash
# TODO check for duplicate lines in bindings
set -e
newline=$'\n'
esc=$'\e'
csi="$esc["
reset="${csi}0m"
bold="${csi}1m"
dim="${csi}2m"
default="${csi}39m"
red="${csi}31m"
green="${csi}32m"
yellow="${csi}33m"
##### STATUS #####
status_buffer=""
status_source_active=""
status_count_total=0
status_count_managed=0
status_count_unmanaged=0
status_count_nonexistent=0
status_target_begin() {
status_buffer=""
status_source_active=""
}
status_target_end() {
tilde_expanded_target=${1/\~/$HOME}
if [ ! -z "$status_source_active" ] ; then
status_buffer="${bold}${green}*${default} $1${reset}${status_buffer}"
((status_count_managed=status_count_managed+1))
else
if [ -e "$tilde_expanded_target" ] ; then
status_buffer="${yellow}*${default} $1${status_buffer}"
((status_count_unmanaged=status_count_unmanaged+1))
else
status_buffer="${dim}* $1${reset}${status_buffer}"
((status_count_nonexistent=status_count_nonexistent+1))
fi
fi
echo "$status_buffer"
echo
((status_count_total=status_count_total+1))
}
status_target_source() {
tilde_expanded_target=${1/\~/$HOME}
if [ -L "$tilde_expanded_target" ] ; then
symlink_target=$(readlink "$tilde_expanded_target")
if [ "$symlink_target" = "${PWD}/$2" ] ; then
status_source_active="y"
status_buffer="${status_buffer}${newline} $2"
else
status_buffer="${status_buffer}${newline} ${dim}$2${reset}"
fi
else
status_buffer="${status_buffer}${newline} ${dim}$2${reset}"
fi
if [ ! -e "$2" ] ; then
status_buffer="${status_buffer} ${bold}${yellow}!${reset}"
fi
}
status_finished() {
cat <<EOF
${bold}${status_count_total}${reset} targets, \
${bold}${status_count_managed}${reset} managed, \
${bold}${status_count_unmanaged}${reset} unmanaged, \
${bold}${status_count_nonexistent}${reset} non-existent
EOF
}
##### INSTALL #####
install_target_source() {
tilde_expanded_target=${1/\~/$HOME}
if [ -z "$install_done" ] && [ "$tilde_expanded_target" = "$install_target" ] ; then
# if no source is provided, select the first option, otherwise
# check for a match
if [ -z "$install_source" ] || [ "$install_source" = "$2" ] ; then
echo "${dim}$ ln -sf ${PWD}/$2 $tilde_expanded_target${reset}"
ln -sf "${PWD}/$2" "$tilde_expanded_target"
install_done="y"
fi
fi
}
install_finished() {
if [ -z $install_done ] ; then
echo "${bold}${red}error${reset}: no suitable target/source combination found."
exit 1
fi
}
##### PROCESS INTERFACE #####
# start processing a target
#
# expects 1 argument: target path
process_target_begin() {
case $mode in
("status")
status_target_begin $1
;;
("install")
true
;;
(*)
echo "Invalid mode: '$mode'"
exit 1
;;
esac
}
# stop processing a target
#
# expects 1 argument: target path
process_target_end() {
case $mode in
("status")
status_target_end "$1"
;;
("install")
true
;;
(*)
echo "Invalid mode: '$mode'"
exit 1
;;
esac
}
# process a target and source pair
#
# expects 2 argument: target path, source path
process_target_source() {
case $mode in
("status")
status_target_source "$1" "$2"
;;
("install")
install_target_source "$1" "$2"
;;
(*)
echo "Invalid mode: '$mode'"
exit 1
;;
esac
}
# finish processing
#
# expects no arguments
process_finished() {
case $mode in
("status")
status_finished
;;
("install")
install_finished
;;
(*)
echo "Invalid mode: '$mode'"
exit 1
;;
esac
}
##### MAIN #####
case $1 in
(""|"status")
mode="status"
;;
("install")
mode="install"
if [ -z "$2" ] || [ "$2" = "--help" ]; then
echo "usage: manage.sh install [target] (source)"
exit 1
fi
install_target=$2
install_source=$3
;;
("help"|"--help")
if [ -z $2 ] ; then
cat <<EOF
usage: manage.sh [command] [options]
Commands:
status View all dotfiles
install Install new dotfiles
EOF
exit 1
fi
;;
*)
echo "Unrecognized command: '$1'"
exit 1
;;
esac
bindings_file="bindings"
line_nr=1
target_file=""
while read -r line ; do
case $line in
("-> "*)
if [ -z "$target_file" ] ; then
echo "Parsing error: source given without target at line $line_nr"
exit 1
fi
source_file=${line#"-> "}
process_target_source "$target_file" "$source_file"
;;
("")
if [ ! -z "$target_file" ] ; then
process_target_end "$target_file"
fi
target_file=""
;;
(*)
target_file="$line"
process_target_begin "$target_file"
;;
esac
# echo $line
((line_nr=line_nr+1))
done < "$bindings_file"
# process last item
if [ ! -z "$target_file" ] ; then
process_target_end "$target_file"
fi
process_finished