123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- #!/bin/bash
-
- warn() {
- echo "$@" >&2
- }
-
- think() {
- $Verbose || return
- warn "$@"
- }
-
- die() {
- warn "$@"
- exit 3
- }
-
- usage() {
- warn "usage: $0 init VAULT SUBVAULT"
- warn "usage: $0 explore VAULT SUBVAULT"
- exit 2
- }
-
- debug() {
- $Debug || return
- local msg
- for msg in "$@";
- do
- warn "debug:$msg"
- done
- }
-
- debugv() {
- $Debug || return
- local vn
- local vd
- for vn in "$@";
- do
- if vd=$(declare -p "$vn" 2>/dev/null);
- then
- warn "debug:${vd#declare ?? }"
- else
- warn "debug:$vn #Unset"
- fi
- done
- }
-
- debugc() {
- $Debug || return
- debug "-- cmd begin: $*"
- Debug=false "$@" | sed -e 's/^/debug:/' >&2
- debug "-- cmd end --"
- }
-
- lssv() {
- #
- # List sub-vaults
- #
- # Sub-vault is a sub-path of vault directory that contains items
- # that should be linked separately, to a similar $HOME subpath,
- # rather than linking the whole sub-tree. This is typical for
- # .config directory specified by XDG standard.
- #
- # For example, imagine vault like this:
- #
- # dotfiles/
- # ├── config
- # │ ├── dunst
- # │ │ └── dunstrc
- # │ └── uzbl
- # │ ├── config
- # │ └── style.css
- # ├── i3
- # │ └── config
- # ├── i3status.conf
- # └── vimrc
- #
- # Without sub-vault, the "config" would be linked as "$HOME/.config":
- #
- # $HOME/.config -> dotfiles/.config
- # $HOME/.i3 -> dotfiles/i3
- # $HOME/.i3status.conf -> dotfiles/i3status.conf
- # $HOME/.vimrc -> dotfiles/vimrc
- #
- # This is impractical in most cases, as normally you want to select
- # applications to link--some dotfiles could be specific to some
- # hosts or even private.
- #
- # Setting 'config' as sub-vault will tell mklinks that you don't
- # want to link this item directly but rather each of its items:
- #
- # $HOME/.config/dunst -> dotfiles/config/dunst
- # $HOME/.config/uzbl -> dotfiles/config/uzbl
- # $HOME/.i3 -> dotfiles/i3
- # $HOME/.i3status.conf -> dotfiles/i3status.conf
- # $HOME/.vimrc -> dotfiles/vimrc
- #
- # This way, you can have other items under "$HOME/.config" that
- # are either purely local or linked to another vault.
- #
- # Note that the subvault can be deeper than one level, i.e.
- # subvault "local/share" also works:
- #
- # $HOME/.local/share/klavaro -> dotfiles/local/share/klavaro
- #
- local sv
- test -n "${SubVaults[0]}" && {
- think "using sub-vaults from command line"
- for sv in "${SubVaults[@]}";
- do
- echo "$sv"
- done
- return
- }
- test -f "$Vault/dotvault/subvaults" && {
- think "reading subvaults from file: $Vault/dotvault/subvaults"
- grep . "$Vault/dotvault/subvaults" \
- | grep -v "^#.*"
- return
- }
- think "using hard-coded list of subvaults"
- echo config
- }
-
- lsvault() {
- #
- # List all vaults and subvaults, depth-first
- #
- {
- echo "$Vault"
- lssv \
- | while IFS= read -r sv;
- do
- test -e "$Vault/$sv" || {
- think "ignoring non-existent sub-vault: $sv"
- continue
- }
- test -d "$Vault/$sv" || {
- warn "ignoring non-directory sub-vault: $sv"
- continue
- }
- echo "$Vault/$sv"
- done
- } \
- | LC_ALL=C sort \
- | tac
- }
-
- istarget() {
- #
- # True if item $1 is possible target
- #
- # Item is obviously not a possible target if it's
- # a (sub-)vault.
- #
- # A less obvious case is when item is an intermediate directory
- # on a path leading to a sub-vault. Such item cannot be a target!
- #
- # For example: there are sub-vaults:
- #
- # foo
- # foo/bar/baz
- #
- # and a tree:
- #
- # foo
- # ├── A
- # └── bar
- # └── baz
- # └── B
- #
- # Items A and B could both be targets, but baz could not be!
- #
- #TODO: write an explanation
- #
- local item=$1
- debugv item
- Verbose=false lsvault | grep -qxF "$item" && return 1
- Verbose=false lsvault | grep -qx "$item/.*" && return 1
- return 0
- }
-
- pfxpath() {
- #
- # Prefix paths with $1
- #
- # Just like `sed "s/^/$1/" but works with any characters
- # in $1 excepr newline, which is not allowed in paths..
- #
- local pfx="$1"
- local path
- while IFS= read -r path;
- do
- echo "$pfx$path"
- done
- }
-
- lstarget() {
- #
- # List potential items in vault $1
- #
- local vlt=$1
- debugv vlt
- local item
- find "$vlt" -mindepth 1 -maxdepth 1 -printf "%P\n" \
- | pfxpath "$vlt/" \
- | grep -v "$Vault/dotvault" \
- | while IFS= read -r item;
- do
- istarget "$item" && echo "$item"
- done
- }
-
- make_links() {
- #
- # Create all links
- #
- local slpath
- local target
- local vlt
- lsvault \
- | while IFS= read -r vlt;
- do
- lstarget "$vlt" \
- | while IFS= read -r target;
- do
- slpath=$(slpath "$target")
- if test -L "$slpath";
- then
- $ClobberLinks || {
- warn "skipping existing slpath: $slpath"
- continue
- }
- think "removing existing link: $slpath"
- maybe rm "$slpath"
- fi
- test -e "$slpath" && {
- warn "skipping existing non-link: $slpath"
- continue
- }
- maybe ln -sr "$target" "$slpath"
- done
- done
- }
-
- explore() {
- #
- # Show what would be done
- #
- local vlt
- local tgt
- local tgts
- lsvault \
- | sort \
- | while IFS= read -r vlt;
- do
- tgts=$(lstarget "$vlt")
- test -n "$tgts" || continue
- if test "$vlt" == "$Vault";
- then
- echo "VAULT:$vlt:"
- else
- echo
- echo "SUBVAULT:$vlt"
- fi
- sort <<<"$tgts" \
- | while IFS= read -r tgt;
- do
- echo "${tgt}:$(slpath "$tgt")"
- done \
- | sed -e "s|:$HOME|:=> ~|" \
- | column -ts: \
- | sed -e "s/^/ /"
- done
- }
-
- maybe() {
- #
- # Maybe do things $@, maybe not (if in debug mode)
- #
- $Debug && { debug "WOULD: $*"; return; }
- "$@"
- }
-
- slpath() {
- #
- # Print path where to create link for target $1
- #
- local item=$1
- echo "$HOME/.${item#$Vault/}"
- }
-
- route() {
- local Vault=$1; shift
- local SubVaults=()
- test -n "$Vault" || usage
- test -e "$Vault" || die "vault does not exist: $Vault"
- test -d "$Vault" || die "vault is not a directory: $Vault"
- SubVaults=("$@")
- case $Action in
- init) make_links ;;
- explore) explore ;;
- *) usage ;;
- esac
- }
-
- export LC_ALL=C
-
- main() {
- local Action
- local Debug=false
- local Verbose=false
- local ClobberLinks=false
- while true; do case $1 in
- -d) Debug=true; shift ;;
- -v) Verbose=true; shift ;;
- -f) ClobberLinks=true; shift ;;
- -*) usage ;;
- *) break ;;
- esac done
- Action=$1; shift
- route "$@"
- }
-
- main "$@"
|