#!/usr/bin/env bash set -ueo pipefail shopt -s nullglob declare -r TARGET_PATH="$HOME" _detect_current_script_real_directory() { realpath -e -- "$(dirname -- "$(readlink -e -- "${BASH_SOURCE[0]:-$0}")")" } declare DOTFILES_ROOT DOTFILES_ROOT="$(_detect_current_script_real_directory)" readonly DOTFILES_ROOT declare -xr SUB="${DOTFILES_ROOT}/home/user" source "${DOTFILES_ROOT}/TARGETS.sh" _die() { echo "${0}: ${1}" >&2 exit $2 } _link_files_in_sandbox() { local targetfile for targetfile in "$@" do echo "installing: ${targetfile}" if [[ "${targetfile::1}" = "%" ]]; then _link_files_in_sandbox ${TARGETS["${targetfile:1}"]} 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() { local comparisons comparisons="$(diff -rq "$SANDBOX_PATH" "$TARGET_PATH")" echo "$comparisons" | grep -vE "^Only in .+" || true } _merge_sandbox_to_home() { cp -RTnP "$SANDBOX_PATH" "$TARGET_PATH" || true } __install_from_sandbox() { local comparisons 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 local -r target="$1" local -r hook_name="$2" local hook_path="${DOTFILES_ROOT}/install-hooks/${target}/${hook_name}" if [[ -x "$hook_path" ]]; then echo "Executing ${hook_name} for target '${target}'" "$hook_path" fi } execute_pre_hook() { _execute_hook_if_executable "$1" "pre-install" } recursive_execute_pre_hooks() { local targetfile for targetfile in ${TARGETS["$1"]} do if [[ "${targetfile::1}" = "%" ]]; then recursive_execute_pre_hooks "${targetfile:1}" execute_pre_hook "${targetfile:1}" fi done } execute_post_hook() { _execute_hook_if_executable "$1" "post-install" } recursive_execute_post_hooks() { local targetfile for targetfile in ${TARGETS["$1"]} do if [[ "${targetfile::1}" = "%" ]]; then recursive_execute_post_hooks "${targetfile:1}" execute_post_hook "${targetfile:1}" fi done } install_target() { local -r target="$1" execute_pre_hook "$target" recursive_execute_pre_hooks "$target" _link_files_in_sandbox ${TARGETS["$target"]} __install_from_sandbox recursive_execute_post_hooks "$target" execute_post_hook "$target" } is_target_installed() { local not_fully_installed=false local targetfile for targetfile in ${TARGETS["$1"]} do if [[ "${targetfile::1}" = "%" ]]; then is_target_installed "${targetfile:1}" || not_fully_installed=true else if [[ ! -e "$TARGET_PATH/$targetfile" ]]; then echo "${targetfile} not linked" not_fully_installed=true fi fi done if $not_fully_installed; then echo "Target '${1}' not fully installed" echo return 1 fi return 0 } find_targets_that_depend_on() { local target for target in "${!TARGETS[@]}" do if [[ " ${TARGETS["$target"]} " =~ " %${1} " ]]; then echo "$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() { local target targetfile for target in "$@" do die_if_installed_targets_depend_on "$target" for targetfile in ${TARGETS["$target"]} do if [[ "${targetfile::1}" = "%" ]]; then continue fi if [[ -e "${TARGET_PATH}/${targetfile}" ]]; then unlink "${TARGET_PATH}/${targetfile}" fi done done } cmd_no_target() { _die "TARGET not exists" 1 } cmd_list() { echo "${!TARGETS[@]}" } target_exists() { local -r target="$1" [[ " ${!TARGETS[*]} " =~ " ${target} " ]] } cmd_install() { local target for target in "$@" do if target_exists "$target"; then SANDBOX_PATH="$(mktemp -td "${USER:-user}.dotfiles_XXXXXXX")" export SANDBOX_PATH install_target "$target" rm -rf "$SANDBOX_PATH" else cmd_no_target fi done } cmd_help() { echo "Dotfiles installation script: Usage: ./install TARGET... Usage: ./install unlink TARGET... Usage: ./install check TARGET Usage: ./install list" } unset executed_command 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