diff --git a/install b/install index 2add000..14e9cc9 100755 --- a/install +++ b/install @@ -1,60 +1,50 @@ -#!/bin/bash +#!/usr/bin/env bash -SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && - pwd)" -PROFILES_DIR="${SCRIPT_DIR}/profiles" -STOW_ARGS=( - "--no-folding" - "--adopt" - "--dir" "${PROFILES_DIR}" - "--target" "${HOME}" +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" ) -SCRIPT_NAME="${0##*/}" - -readonly SCRIPT_DIR -readonly PROFILES_DIR -readonly -a STOW_ARGS -readonly SCRIPT_NAME - -print_opt() { - local -r option="$1" - local -r description="$2" - printf " %-22s %s\n" "${option}" "${description}" -} +readonly script_name="${0##*/}" shell_quote() { - local -r string="$1" - printf "'%s'" "${string//'/'\\''/}" + 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 + 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 + 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." + 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 </dev/null 2>&1; then + cat >&2 <&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 - ;; - -P | --prune-symlinks) - prune_symlinks="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 + while (($# > 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 "${prune_symlinks}" || -n "${verbose}" || -n "${profile}" ]]; then - die "options '--list-profiles' and others cannot be used together" - fi + 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 + if [[ -n "$profile" ]] && + ! array_contains "$profile" "${PROFILES[@]}"; then + die "profile $(shell_quote "$profile") cannot be found" + fi } show_general_help() { - echo "Usage: ${SCRIPT_NAME} [options]" - echo " ${SCRIPT_NAME} -l | --list-profiles" - echo " ${SCRIPT_NAME} --help[=TOPIC]" - echo - echo "Options:" - print_opt "-h, --help[=TOPIC]" \ - "show general help or help for a specific topic" - print_opt "-p, --profile PROFILE" "select profile" - print_opt "-v, --verbose" "increase verbosity" - print_opt "-P, --prune-symlinks" \ - "remove dangling symlinks in home directory" - print_opt "-l, --list-profiles" \ - "list available profiles (cannot be combined with other options)" - echo - echo "Topics:" - print_opt "profile" "explanation of profile structure and usage" - echo - echo "Profile:" - echo " Directory containing dotfiles plus optional parent and hooks/." - echo " Used by the installer to group and deploy related configurations." + 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 + pushd "${HOME}" >/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" + 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 + 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}" + 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 - echo "${parent}" - fi } build_deptree() { - local -r target_profile="$1" - fetch_parents "${target_profile}" - echo "${target_profile}" + 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}" - [[ -n "${prune_symlinks}" ]] && prune_symlinks "${target_profile}" - stow_profile "${target_profile}" - run_post_hooks "${target_profile}" - done + 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 + 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 "$@"