# shellcheck shell=bash # shellcheck disable=SC2034 # the paths below are for whichever script sources this # # Shared by link.sh and the link/*.sh parts: the dry-run flag, colours, link and # link_all, and the paths they all use. Source it with the script's own # arguments: # # source "$(dirname "$0")/lib.sh" "$@" # # Dry run unless the first argument is `yes`. # # Other repos' link.sh source this file instead of keeping copies, finding it # through $DOT_LINK_LIB, which link.sh exports for them. Keep these names and # the `yes` convention stable, or change them there as well. is_dry_run="y" if [[ "${1:-}" == "yes" ]] then is_dry_run="" fi color() { color_code="$1" shift # Black 0;30 Dark Gray 1;30 # Red 0;31 Light Red 1;31 # Green 0;32 Light Green 1;32 # Brown/Orange 0;33 Yellow 1;33 # Blue 0;34 Light Blue 1;34 # Purple 0;35 Light Purple 1;35 # Cyan 0;36 Light Cyan 1;36 # Light Gray 0;37 White 1;37 yes_color="\033[${color_code}m" no_color='\033[0m' echo -e "${yes_color}$*${no_color}" } green() { color '0;32' "$@" } yellow() { color '1;33' "$@" } red() { color '0;31' "$@" } # Link up dotfiles link() { local target="$1" local link="$2" if [[ -z "$1" ]] then >&2 echo "Error: need valid target" return 1 fi if [[ -z "$2" ]] then >&2 echo "Error: need valid link" return 1 fi target="$(realpath "${target}")" if [[ -n "${is_dry_run}" ]] then local actual actual="$(readlink "$link" || true)" if [[ -n "${actual}" ]] ; then actual="$(realpath "$actual" || true)" fi if [[ -L "$link" && "$target" == "$actual" ]] then green "$link already exists and is correct" return elif [[ -e "$link" ]] then if diff "$link" "$target" > /dev/null then yellow "$link already exists and will be deleted first but contents are the same. $target != $actual" else red "$link WILL CHANGE" fi else yellow "$link will be created" fi else mkdir -p "$(dirname "$2")" rm -rf "$2" ln -sf "$1" "$2" fi } link_all() { from="$1" to="$2" # Process substitution rather than a pipe, so `link` runs in this shell and # not a subshell; NUL-delimited so names with spaces survive (SC2044/SC2086). while IFS= read -r -d '' f do link "$f" "$to/$(basename "$f")" done < <(find "$from" -mindepth 1 -maxdepth 1 -print0) } # Run another script with this interface, dry or not as this one is. run_link() { if [[ -n "${is_dry_run}" ]] then "$1" else "$1" yes fi } dotfiles_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" config_dir="$dotfiles_dir/config" xdg_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}" # iCloud Drive, on a Mac signed into it; absent everywhere else. icloud_dir="$HOME/Library/Mobile Documents/com~apple~CloudDocs" # Machine-local opt-out: the name of a part, one per line, in # ~/.config/dot/skip. For a machine where a part would do the wrong thing -- # one whose ~/.claude/settings.json is its owner's rather than this account's, # say -- without editing this repo, which every machine shares. Hand-written # and in no repo, like ~/.localenv. skip_file="$xdg_config_dir/dot/skip" part_skipped() { # Whole-line match, so a blank or commented line cannot name a part. [[ -r "$skip_file" ]] && grep -qxF "$1" "$skip_file" } # A part skips itself here rather than in link.sh's loop, so that running one # on its own (`link/claude.sh yes`) obeys the file too. Parts only: link.sh and # other repos' link.sh source this as well, and sit outside link/, so their own # names can never be read as a part's. if [[ "$(basename "$(dirname "$(realpath "$0")")")" == "link" ]] then part_name="$(basename "$0" .sh)" if part_skipped "$part_name" then yellow "link/$part_name.sh skipped by $skip_file" exit 0 fi fi