#!/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() { echo "$(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 echo "installing: ${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 echo "Executing ${hook_name} for target '${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 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() ( for target in $(map_get_keys "${TARGETS}") do files="$(map_get_value "${TARGETS}" "${target}")" if map_key_exists "${TARGETS}" "%${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() ( 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