#!/usr/bin/env bash set -Eeuo pipefail readonly APP_PATH="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" readonly VIM_PATH="${VIM_PATH:-$HOME/.vim}" readonly MANAGED_START='# >>> dotfiles >>>' readonly MANAGED_END='# <<< dotfiles <<<' info() { printf '\033[34m[•]\033[0m %s\n' "$*"; } success() { printf '\033[32m[✔]\033[0m %s\n' "$*"; } warn() { printf '\033[33m[!]\033[0m %s\n' "$*" >&2; } die() { printf '\033[31m[✘]\033[0m %s\n' "$*" >&2; exit 1; } usage() { cat <<'USAGE' Usage: ./install.sh [all|vim|shell|tmux|uninstall]... With no arguments, installs every component. Components can be combined, for example: ./install.sh vim tmux USAGE } require() { command -v "$1" >/dev/null 2>&1 || die "Required program not found: $1" } backup_path() { local target=$1 if [[ -e "$target" || -L "$target" ]]; then local backup="${target}.backup.$(date +%Y%m%d_%H%M%S)" suffix=0 while [[ -e "$backup" || -L "$backup" ]]; do ((suffix += 1)) backup="${target}.backup.$(date +%Y%m%d_%H%M%S).$suffix" done mv -- "$target" "$backup" warn "Backed up $target to $backup" fi } link_config() { local source=$1 target=$2 if [[ -L "$target" && "$(readlink "$target" || true)" == "$source" ]]; then return fi backup_path "$target" ln -s "$source" "$target" success "Linked $target -> $source" } remove_managed_link() { local target=$1 expected=$2 if [[ -L "$target" && "$(readlink "$target")" == "$expected" ]]; then rm -- "$target" info "Removed $target" fi } remove_managed_block() { local file=$1 temp [[ -f "$file" ]] || return 0 temp=$(mktemp) awk -v start="$MANAGED_START" -v end="$MANAGED_END" ' $0 == start { managed = 1; next } $0 == end { managed = 0; next } !managed { print } ' "$file" > "$temp" cat "$temp" > "$file" rm -f "$temp" } install_vim() { require vim require git if ! vim --clean -Nu NONE -n -es \ '+if v:version < 900 | cquit | endif' '+qall'; then die 'Vim 9 or newer is required.' fi mkdir -p "$VIM_PATH/autoload" local temp temp=$(mktemp -d) git clone --depth 1 --quiet https://github.com/junegunn/vim-plug "$temp" cp "$temp/plug.vim" "$VIM_PATH/autoload/plug.vim" rm -rf "$temp" link_config "$APP_PATH/.vimrc" "$HOME/.vimrc" vim -Nu "$APP_PATH/.vimrc" -n -es \ --cmd 'set nomore' '+PlugInstall --sync' '+qall' success 'Installed Vim configuration and plugins.' } install_shell() { local bashrc="$HOME/.bashrc" quoted_source touch "$bashrc" remove_managed_block "$bashrc" printf -v quoted_source '%q' "$APP_PATH/bash_tools.sh" { printf '\n%s\n' "$MANAGED_START" printf '[ -r %s ] && source %s\n' "$quoted_source" "$quoted_source" printf 'command -v zoxide >/dev/null 2>&1 && eval "$(zoxide init bash)"\n' printf '%s\n' "$MANAGED_END" } >> "$bashrc" success "Installed managed Bash block in $bashrc" } install_tmux() { require tmux link_config "$APP_PATH/.tmux.conf" "$HOME/.tmux.conf" success 'Installed tmux configuration.' } uninstall() { remove_managed_link "$HOME/.vimrc" "$APP_PATH/.vimrc" remove_managed_link "$HOME/.tmux.conf" "$APP_PATH/.tmux.conf" remove_managed_block "$HOME/.bashrc" success 'Removed dotfile links and the managed Bash block.' info 'The repository, Vim data/plugins, and backups were retained.' } main() { case "${1:-}" in -h|--help) usage; return ;; esac local components=("$@") component ((${#components[@]})) || components=(all) for component in "${components[@]}"; do case "$component" in all) install_vim install_shell install_tmux ;; vim) install_vim ;; shell) install_shell ;; tmux) install_tmux ;; uninstall) uninstall ;; *) usage; die "Unknown component: $component" ;; esac done } main "$@"