dotfiles

My personal shell configs and stuff
git clone git://git.alex.balgavy.eu/dotfiles.git
Log | Files | Refs | Submodules | README | LICENSE

commit 60355e3febbcc6dd1b6d76436d951b5b9f7c33ed
parent 026b7d9f94b0e601fe18f902af1f7c86721af295
Author: Alex Balgavy <a.balgavy@gmail.com>
Date:   Wed, 11 Dec 2019 12:42:52 -0500

conf: implement link checking

Former-commit-id: 3c4861249d3187c8f8d68234cb4f2dc3ee4e58b6
Diffstat:
Mdot.map | 4++--
Mscripts/conf | 286++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
2 files changed, 175 insertions(+), 115 deletions(-)

diff --git a/dot.map b/dot.map @@ -88,8 +88,8 @@ Xresources: ~/.Xresources # Emacs config emacs: -- emacs.d: ~/.emacs.d -- emacs: ~/emacs +- emacs.d/themes: ~/.emacs.d/themes +- emacs: ~/.emacs # All shell configs shell: diff --git a/scripts/conf b/scripts/conf @@ -1,95 +1,93 @@ #!/usr/bin/env bash -# TODO: check if linked, fix linking e.g. "emacs/emacs" vs "emacs/emacs.d" vs "emacs", +# TODO: fix linking e.g. "emacs/emacs" vs "emacs/emacs.d" vs "emacs", # convert to posix shell # Set the dir for your dotfiles, mine comes from the environment -DOTFILES="${DOTFILES}" +DOTFILES="${DOTFILES}"; # Set the name of the mapfile, located in the root folder of your dotfiles -mapfile="dot.map" +mapfile="dot.map"; + +# Begin script +declare -A mappings; + +pp_mappings() { + echo "{" + for f in "${!mappings[@]}"; do + echo " ${f} => ${mappings[${f}]}"; + done + echo "}" +} die() { - echo "$1" >&2 - exit 1 + echo "$1" >&2; + exit 1; } + get_bash_version() { - echo "${BASH_VERSION}" | cut -d'.' -f1 + echo "${BASH_VERSION}" | cut -d'.' -f1; } -[ "$(get_bash_version)" -ge 4 ] || die "Requires Bash >= 4" - -# Don't want $DOTFILES expansion in the message -# shellcheck disable=SC2016 -[ -z "${DOTFILES}" ] && die '$DOTFILES variable not set.' -cd "${DOTFILES}" || die "Can't read ${DOTFILES}" -[ -f "${mapfile}" ] || die "Mapfile ${mapfile} does not exist in $(pwd)" - +preliminary_checks() { + [ "$(get_bash_version)" -ge 4 ] || die "Requires Bash >= 4"; - -do_link() { - if [ -e "$2" ]; then - if [ -L "$2" ] && [ "$(realpath "$2")" == "$(pwd)/$1" ]; then - echo "$2 is already linked to $1." - return - else - echo "$2 already exists, renaming to $2.bak" - mv "$2" "$2.bak" - fi - fi - mkdir -vp "${2%/*}" - ln -svf "$(pwd)/$1" "$2" + # Don't want $DOTFILES expansion in the message + # shellcheck disable=SC2016 + [ -z "${DOTFILES}" ] && die '$DOTFILES variable not set.'; + cd "${DOTFILES}" || die "Can't read ${DOTFILES}"; + [ -f "${mapfile}" ] || die "Mapfile ${mapfile} does not exist in $(pwd)"; } parse_mapfile() { - local nestdir="" - local lineno=1 - local nestlevel=0 - homedir="$(echo -n "${HOME}" | tr -d '\n\r')" + local nestdir=""; + local lineno=1; + local nestlevel=0; + homedir="$(echo -n "${HOME}" | tr -d '\n\r')"; while read -r map; do if [ -n "${map}" ] && [[ ! "${map}" == "#"* ]]; then - IFS=" " read -r -a mapping <<< "$(echo -n "${map}" | sed -e 's/^ *-* /-/' -e "s:~:${homedir}:" | awk -F ': ' '{ print $1 " " $2 }')" + IFS=" " read -r -a mapping <<< "$(echo -n "${map}" | sed -e 's/^ *-* /-/' -e "s:~:${homedir}:" | awk -F ': ' '{ print $1 " " $2 }')"; # Top level items if [[ ! "${mapping[0]}" == "-"* ]]; then - nestdir="" - nestlevel=0 + nestdir=""; + nestlevel=0; if [ -z "${mapping[1]}" ]; then # nestdir - nestdir="${mapping[0]/://}" + nestdir="${mapping[0]/://}"; else # one-off mapping if [ ! -e "${nestdir}${mapping[0]}" ]; then - die "error in mapfile: ${nestdir}${mapping[0]} does not exist (line ${lineno})" + die "error in mapfile: ${nestdir}${mapping[0]} does not exist (line ${lineno})"; fi - mappings["${nestdir}${mapping[0]}"]="${mapping[1]}" + mappings["${nestdir}${mapping[0]}"]="${mapping[1]}"; fi else - levels="$(count_nestlevels "${mapping[0]}")" - pth="$(echo "${mapping[0]}" | sed -e 's/^-*//g' -e 's/://')" + levels="$(count_nestlevels "${mapping[0]}")"; + pth="$(echo "${mapping[0]}" | sed -e 's/^-*//g' -e 's/://')"; if [ "${levels}" -lt "${nestlevel}" ]; then while [ "${levels}" -lt "${nestlevel}" ]; do - nestdir="${nestdir%/*/}/" - ((nestlevel--)) + nestdir="${nestdir%/*/}/"; + ((nestlevel--)); done - [ "${nestdir}" = "/" ] && nestdir="" + [ "${nestdir}" = "/" ] && nestdir=""; elif [ "${levels}" -gt "${nestlevel}" ]; then while [ "${levels}" -gt "${nestlevel}" ]; do - ((nestlevel++)) + ((nestlevel++)); done fi if [ -z "${mapping[1]}" ]; then - nestdir="${nestdir}${pth}/" + nestdir="${nestdir}${pth}/"; else if [ ! -e "${nestdir}${mapping[0]/-/}" ]; then - die "error in mapfile: ${nestdir}${pth} does not exist (line ${lineno})" + die "error in mapfile: ${nestdir}${pth} does not exist (line ${lineno})"; fi - mappings["${nestdir}${pth}"]="${mapping[1]}" + mappings["${nestdir}${pth}"]="${mapping[1]}"; fi fi fi - ((lineno++)) - done < <(cat "$1") + ((lineno++)); + done < <(cat "$1"); } link_all() { @@ -97,125 +95,180 @@ link_all() { do_link "${f}" "${mappings[${f}]}"; done } + unlink_all() { for f in "${!mappings[@]}"; do - do_unlink "${mappings[${f}]}" + do_unlink "${mappings[${f}]}"; done } + +find_mapping() { + ( IFS=$'\n'; echo "${!mappings[*]}" ) | grep "^$1"; +} + link_specific() { - grep_result="$( ( IFS=$'\n'; echo "${!mappings[*]}" ) | grep "^$1")" - if [ -n "${grep_result}" ]; then + if [ -n "$(find_mapping "$1")" ]; then for f in "${!mappings[@]}"; do if [[ "${f%%/*}" == "$1" ]] || [ "${f}" = "$1" ]; then - do_link "${f}" "${mappings[${f}]}" + do_link "${f}" "${mappings[${f}]}"; fi done else - die "Error: $1 not present in mapfile, don't know how to link." - fi -} -do_unlink() { - if [ ! -e "$1" ]; then - echo "$1 does not exist." - elif [ ! -L "$1" ]; then - echo "$1 is not a link, not removing." - else - echo -n "Removing link: " - rm -v "$1" + die "Error: $1 not present in mapfile, don't know how to link."; fi } + unlink_specific(){ - grep_result="$( ( IFS=$'\n'; echo "${!mappings[*]}" ) | grep "^$1")" - if [ -n "${grep_result}" ]; then + if [ -n "$(find_mapping "$1")" ]; then for f in "${!mappings[@]}"; do if [[ "${f}" == "${i}"* ]]; then - do_unlink "${mappings[${f}]}" + do_unlink "${mappings[${f}]}"; fi done else - die "Error: ${i} not present in mapfile, can't unlink." + die "Error: $1 not present in mapfile, can't unlink."; + fi +} + +do_link() { + if [ -e "$2" ]; then + if [ -L "$2" ] && [ "$(realpath "$2")" == "$(pwd)/$1" ]; then + echo "$2 is already linked to $1."; + return + else + echo "$2 already exists, renaming to $2.bak"; + mv "$2" "$2.bak"; + fi + fi + mkdir -vp "${2%/*}"; + ln -svf "$(pwd)/$1" "$2"; +} + +do_unlink() { + if [ ! -e "$1" ]; then + echo "$1 does not exist."; + elif [ ! -L "$1" ]; then + echo "$1 is not a link, not removing."; + else + echo -n "Removing link: "; + rm -v "$1"; fi } + list_mappings() { - mapstr="" + mapstr=""; for f in "${!mappings[@]}"; do mapstr="${mapstr}${f} => ${mappings[${f}]}\n"; done - echo -e "${mapstr}" | sort + echo -e "${mapstr}" | sort; } + count_nestlevels() { - echo -n "$1" | sed 's/^\([-]*\).*/\1/' | tr -d '\n' | wc -m | tr -d ' ' + echo -n "$1" | sed 's/^\([-]*\).*/\1/' | tr -d '\n' | wc -m | tr -d ' '; } -declare -A mappings +check_link() { + if [ -n "$(find_mapping "$1")" ]; then + for f in "${!mappings[@]}"; do + if [[ "${f%%/*}" == "$1" ]] || [ "${f}" = "$1" ]; then + src="$f"; + tgt="${mappings[${f}]}"; + if [ -e "$tgt" ]; then + if [ -L "$tgt" ] && [ "$(realpath "$tgt")" == "$(pwd)/$src" ]; then + echo -e "[ OK ] $src is linked at $tgt."; + elif [ -L "$tgt" ]; then + echo -e "[ XX ] $tgt is a link but does not point to $src."; + else + echo -e "[ XX ] $tgt exists but does not point to $src."; + fi + else + echo -e "[ XX ] $src is not linked."; + fi + fi + done + else + die "$1 does not map to anything."; + fi +} -PARAMS="" -link_mode=0 -unlink_mode=0 +check_all() { + for f in "$@"; do + check_link "$f" + done +} + +preliminary_checks; + +PARAMS=""; +mode=""; while (( "$#" )); do case "$1" in -l|--list) - parse_mapfile "./${mapfile}" - echo "Mappings (from $(realpath ${mapfile})):" - echo "(format: source => name_of_symlink)" - list_mappings + parse_mapfile "./${mapfile}"; + echo "Mappings (from $(realpath ${mapfile})):"; + echo "(format: source => name_of_symlink)"; + list_mappings; exit 0 ;; -h|--help) - echo "Loading configuration from: $(realpath ${mapfile})" - echo - echo "Usage:" - echo "conf [options] (link|unlink) [entry1 [entry2...]]" - echo - echo "Options:" - echo " -l, --list only list maps" - echo " -e, --edit edit map file" - echo - echo "link [entry1 [entry2...]] Link entries according to the map file." - echo " With no arguments, links all entries." - echo - echo "unlink [entry1 [entry2...]] Unlink entries according to the map file." - echo " With no arguments, unlinks all entries." + echo "Loading configuration from: $(realpath ${mapfile})"; + echo; + echo "Usage:"; + echo "conf [options] (link|unlink) [entry1 [entry2...]]"; + echo; + echo "Options:"; + echo " -l, --list only list maps"; + echo " -e, --edit edit map file"; + echo; + echo "link [entry1 [entry2...]] Link entries according to the map file."; + echo " With no arguments, links all entries."; + echo; + echo "unlink [entry1 [entry2...]] Unlink entries according to the map file."; + echo " With no arguments, unlinks all entries."; exit 0 ;; -e|--edit) # $EDITOR is an environment variable # shellcheck disable=SC2154 - echo "Opening ${mapfile} with ${EDITOR}" - "${EDITOR}" "${DOTFILES}/${mapfile}" + echo "Opening ${mapfile} with ${EDITOR}"; + "${EDITOR}" "${DOTFILES}/${mapfile}"; exit 0 ;; --) # end arg parsing - shift + shift; break ;; -*) # unsupported flags - echo "Unsupported flag $1" >&2 + echo "Unsupported flag $1" >&2; # Don't want the command to expand so # shellcheck disable=SC2016 - echo 'Run `conf -h` to show usage.' + echo 'Run `conf -h` to show usage.'; exit 1 ;; *) # preserve positional arguments - PARAMS="${PARAMS} $1" + PARAMS="${PARAMS} $1"; shift ;; esac done -eval set -- "${PARAMS}" +eval set -- "${PARAMS}"; case "$1" in "link") - link_mode=1 + mode="link"; shift ;; "unlink") - unlink_mode=1 + mode="unlink"; + shift + ;; + "check") + mode="check"; shift ;; *) @@ -224,15 +277,15 @@ esac # Don't want the command to expand so # shellcheck disable=SC2016 -[ "${link_mode}" -eq 0 ] && [ "${unlink_mode}" -eq 0 ] && die 'Arguments required, run `conf -h` to show usage.' +[ -z "$mode" ] && die 'Arguments required, run `conf -h` to show usage.'; -parse_mapfile "./${mapfile}" -if [ "${link_mode}" -eq 1 ]; then +parse_mapfile "./${mapfile}"; +if [ "$mode" = "link" ]; then if [ $# -eq 0 ]; then - read -srp "Link all dotfiles? [Y/n]" -n 1 -s conf + read -srp "Link all dotfiles? [Y/n]" -n 1 -s conf; case "${conf}" in Y|y) - echo + echo; link_all ;; *) @@ -240,26 +293,33 @@ if [ "${link_mode}" -eq 1 ]; then esac else for i in "$@"; do - link_specific "${i}" + link_specific "${i}"; done fi -elif [ "${unlink_mode}" -eq 1 ]; then +elif [ "$mode" = "unlink" ]; then if [ $# -eq 0 ]; then - read -srp "Unlink all dotfiles? [Y/n]" -n 1 -s conf + read -srp "Unlink all dotfiles? [Y/n]" -n 1 -s conf; case "${conf}" in Y|y) echo - unlink_all + unlink_all; ;; *) ;; esac else for i in "$@"; do - unlink_specific "${i}" + unlink_specific "${i}"; + done + fi +elif [ "$mode" = "check" ]; then + if [ $# -eq 0 ]; then + check_all "${!mappings[@]}"; + else + for i in "$@"; do + check_link "${i}"; done fi else - die "Neither link nor unlink mode specified." + die "Neither mode not specified."; fi -