#!/usr/bin/env bash script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" readonly script_dir readonly profiles_dir="$script_dir/profiles" readonly -a stow_args=( "--no-folding" "--adopt" "--dir" "$profiles_dir" "--target" "$HOME" ) readonly script_name="${0##*/}" shell_quote() { local -r string="$1" printf "'%s'" "${string//'/'\\''/}" } array_contains() { local -r target_item="$1" shift local -a -r array=("$@") local item for item in "${array[@]}"; do if [[ ${item} == "$target_item" ]]; then return 0 fi done return 1 } die() { local -r message="$1" printf "%s: %b\n" "$script_name" "$message" >&2 exit 1 } invalid_option() { local -r option="$1" die "invalid option $(shell_quote "$option") Try '$script_name --help' for usage." } check_deps() { if ! command -v stow >/dev/null 2>&1; then cat >&2 <&2 < 0)); do case $1 in -h) show_general_help exit 0 ;; --help) if (($# >= 2)) && [[ $2 ]] && [[ $2 != -* ]]; then show_help_topic "$2" else show_general_help fi exit 0 ;; --help=*) local -r topic="${1#*=}" [[ -z $topic ]] && show_general_help && exit 0 show_help_topic "$topic" exit 0 ;; -l | --list-profiles) list_profiles="true" shift ;; -v | --verbose) verbose="true" shift ;; -p | --profile) ( (($# < 2)) || [[ -z "$2" ]]) && die "--profile requires an argument" profile="$2" shift 2 ;; *) invalid_option "$1" ;; esac done } validate_args() { if [[ -n "$list_profiles" ]] && [[ -n "$verbose" || -n "$profile" ]]; then die "options '--list-profiles' and others cannot be used together" fi if [[ -n "$profile" ]] && ! array_contains "$profile" "${PROFILES[@]}"; then die "profile $(shell_quote "$profile") cannot be found" fi } show_general_help() { cat </dev/null || exit 1 local common_files local -r target_profile="$1" mapfile -t common_files < <(comm -12 <(find "${HOME}" -type l \ -printf '%P\n' 2>/dev/null | sort) \ <(find "$profiles_dir/$target_profile" -type f -printf '%P\n' | sort)) local target_file for target_file in "${common_files[@]}"; do local -a rm_args=("--force") [[ ${verbose} ]] && rm_args+=("--verbose") rm "${rm_args[@]}" -- "$target_file" done popd >/dev/null || exit 1 echo "OK" } stow_profile() { local -r target_profile="$1" if stow "${stow_args[@]}" "$target_profile" 2>&1; then echo "Profile $target_profile has been stowed" else echo "Failed to create symlinks pointing to dotfiles" \ "in profile $target_profile" fi } fetch_parents() { local -r target_profile="$1" if [[ -s "$profiles_dir/$target_profile/parent" ]]; then local parent parent="$(cat "$profiles_dir/$target_profile/parent")" if [[ -s "$profiles_dir/$parent/parent" ]]; then fetch_parents "$parent" fi echo "$parent" fi } build_deptree() { local -r target_profile="$1" fetch_parents "$target_profile" echo "$target_profile" } install_dotfiles() { local target_profile for target_profile in $(build_deptree "$profile"); do run_pre_hooks "${target_profile}" prune_symlinks "${target_profile}" stow_profile "${target_profile}" run_post_hooks "${target_profile}" done } main() { check_deps parse_args "$@" require_profiles_dir (($# == 0)) && show_general_help && exit 1 validate_args [[ -n "$list_profiles" ]] && echo "${PROFILES[@]}" && exit 0 install_dotfiles } main "$@"