dotfiles/install
2025-03-25 11:25:05 +03:00

259 lines
6.2 KiB
Bash
Executable File

#!/bin/sh
set -ue
readonly TARGET_PATH="${HOME}"
_detect_current_script_real_directory() {
realpath -e -- "$(dirname -- "$(readlink -e -- "${0}")")"
}
DOTFILES_ROOT="$(_detect_current_script_real_directory)"
readonly DOTFILES_ROOT
readonly SUB="${DOTFILES_ROOT}/home/user"
export SUB
. "${DOTFILES_ROOT}/TARGETS.sh"
_die() {
printf '%s: %s\n' "$(basename "${0}")" "${1}" >&2
exit "${2}"
}
string_get_first_char() (
printf '%.1s' "${1}"
)
string_exclude_first_char() (
echo "${1}" | tail -c+2
)
map_get_value() (
map="${1}"
key="${2}"
echo "${map}" | grep "^${key}:" | cut -d ':' -f2
)
map_get_keys() (
map="${1}"
echo "${map}" | cut -d ':' -f1
)
map_key_exists() (
map="${1}"
key="${2}"
map_get_keys "${map}" | grep "^${2}" 1>/dev/null
)
_link_files_in_sandbox() (
for targetfile in "$@"
do
printf 'installing: %s\n' "${targetfile}"
if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
files="$(map_get_value "${TARGETS}" "$(string_exclude_first_char "${targetfile}")")"
#shellcheck disable=SC2086
_link_files_in_sandbox ${files}
else
if [ ! "$(dirname "${targetfile}")" = "." ]; then
mkdir -p "${SANDBOX_PATH}/$(dirname "${targetfile}")"
fi
ln -sT "${SUB}/${targetfile}" "${SANDBOX_PATH}/${targetfile}"
fi
done
)
_compare_sandbox_to_home() (
comparisons="$(diff -rq "${SANDBOX_PATH}" "${TARGET_PATH}")" || true
echo "${comparisons}" | grep -vE "^Only in .+" || true
)
_merge_sandbox_to_home() (
cp -RTnP "${SANDBOX_PATH}" "${TARGET_PATH}" || true
)
__install_from_sandbox() (
comparisons="$(_compare_sandbox_to_home)"
if [ -n "${comparisons}" ]; then
echo "${comparisons}" >&2
_die "Found conflicting files. Exiting" 1
fi
echo "Merging to home..."
_merge_sandbox_to_home
echo "Successfully installed"
)
_execute_hook_if_executable() (
# all hooks gets SUB and SANDBOX_PATH env variables
target="${1}"
hook_name="${2}"
hook_path="${DOTFILES_ROOT}/install-hooks/${target}/${hook_name}"
if [ -x "${hook_path}" ]; then
printf 'Executing %s for target "%s"\n' "${hook_name}" "${target}"
"${hook_path}"
fi
)
execute_pre_hook() (
_execute_hook_if_executable "${1}" "pre-install"
)
recursive_execute_pre_hooks() (
files="$(map_get_value "${TARGETS}" "${1}")"
for targetfile in ${files}
do
if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
recursive_execute_pre_hooks "$(string_exclude_first_char "${targetfile}")"
execute_pre_hook "$(string_exclude_first_char "${targetfile}")"
fi
done
)
execute_post_hook() (
_execute_hook_if_executable "${1}" "post-install"
)
recursive_execute_post_hooks() (
files="$(map_get_value "${TARGETS}" "${1}")"
for targetfile in ${files}
do
if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
recursive_execute_post_hooks "$(string_exclude_first_char "${targetfile}")"
execute_post_hook "$(string_exclude_first_char "${targetfile}")"
fi
done
)
install_target() (
target="${1}"
files="$(map_get_value "${TARGETS}" "${target}")"
execute_pre_hook "${target}"
recursive_execute_pre_hooks "${target}"
#shellcheck disable=SC2086
_link_files_in_sandbox ${files}
__install_from_sandbox
recursive_execute_post_hooks "${target}"
execute_post_hook "${target}"
)
is_target_installed() (
target="${1}"
not_fully_installed=false
files="$(map_get_value "${TARGETS}" "${target}")"
for targetfile in ${files}
do
if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
is_target_installed "$(string_exclude_first_char "${targetfile}")" || not_fully_installed=true
else
if [ ! -e "${TARGET_PATH}/${targetfile}" ]; then
printf '%s not linked\n' "${targetfile}"
not_fully_installed=true
fi
fi
done
if ${not_fully_installed}; then
printf 'Target "%s" not fully installed\n\n' "${target}"
return 1
fi
return 0
)
find_targets_that_depend_on() (
for target in $(map_get_keys "${TARGETS}")
do
files="$(map_get_value "${TARGETS}" "${target}")"
if map_key_exists "${TARGETS}" "%${1}"; then
printf '%s\n' "${target}"
fi
done
)
die_if_installed_targets_depend_on() (
for reverse_dependecy in $(find_targets_that_depend_on "${1}")
do
if is_target_installed "${reverse_dependecy}" >/dev/null; then
_die "target '${reverse_dependecy}' is depends on installed target '${1}'. Exiting..." 1
fi
done
)
cmd_unlink() (
for target in "$@"
do
die_if_installed_targets_depend_on "${target}"
files="$(map_get_value "${TARGETS}" "${target}")"
for targetfile in ${files}
do
if [ "$(string_get_first_char "${targetfile}")" = "%" ]; then
continue
fi
if [ -e "${TARGET_PATH}/${targetfile}" ]; then
unlink "${TARGET_PATH}/${targetfile}"
fi
done
done
)
cmd_no_target() (
_die "TARGET '${1}' not exists" 1
)
cmd_list() (
map_get_keys "${TARGETS}"
)
target_exists() (
target="${1}"
map_key_exists "${TARGETS}" "${target}"
)
cmd_install() (
for target in "$@"
do
if ! target_exists "${target}"; then
cmd_no_target "${target}"
fi
SANDBOX_PATH="$(mktemp -td "${USER:-user}.dotfiles_XXXXXXX")"
export SANDBOX_PATH
install_target "${target}"
rm -rf "${SANDBOX_PATH}"
done
)
cmd_help() (
echo "Dotfiles installation script:
Usage: ./install TARGET...
Usage: ./install unlink TARGET...
Usage: ./install check TARGET
Usage: ./install list"
)
readonly executed_command="${1}"
case "${executed_command}" in
unlink) shift; cmd_unlink "$@" ;;
check) shift; is_target_installed "$@" ;;
list) shift; cmd_list "$@" ;;
help) shift; cmd_help "$@" ;;
*) shift; cmd_install "${executed_command}" "$@" ;;
esac
exit 0