My dotfiles. Period.

mklinks 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. #!/bin/bash
  2. warn() {
  3. echo "$@" >&2
  4. }
  5. think() {
  6. $Verbose || return
  7. warn "$@"
  8. }
  9. die() {
  10. warn "$@"
  11. exit 3
  12. }
  13. usage() {
  14. warn "usage: $0 init VAULT SUBVAULT"
  15. warn "usage: $0 explore VAULT SUBVAULT"
  16. exit 2
  17. }
  18. debug() {
  19. $Debug || return
  20. local msg
  21. for msg in "$@";
  22. do
  23. warn "debug:$msg"
  24. done
  25. }
  26. debugv() {
  27. $Debug || return
  28. local vn
  29. local vd
  30. for vn in "$@";
  31. do
  32. if vd=$(declare -p "$vn" 2>/dev/null);
  33. then
  34. warn "debug:${vd#declare ?? }"
  35. else
  36. warn "debug:$vn #Unset"
  37. fi
  38. done
  39. }
  40. debugc() {
  41. $Debug || return
  42. debug "-- cmd begin: $*"
  43. Debug=false "$@" | sed -e 's/^/debug:/' >&2
  44. debug "-- cmd end --"
  45. }
  46. lssv() {
  47. #
  48. # List sub-vaults
  49. #
  50. # Sub-vault is a sub-path of vault directory that contains items
  51. # that should be linked separately, to a similar $HOME subpath,
  52. # rather than linking the whole sub-tree. This is typical for
  53. # .config directory specified by XDG standard.
  54. #
  55. # For example, imagine vault like this:
  56. #
  57. # dotfiles/
  58. # ├── config
  59. # │   ├── dunst
  60. # │   │   └── dunstrc
  61. # │   └── uzbl
  62. # │      ├── config
  63. # │      └── style.css
  64. # ├── i3
  65. # │   └── config
  66. # ├── i3status.conf
  67. # └── vimrc
  68. #
  69. # Without sub-vault, the "config" would be linked as "$HOME/.config":
  70. #
  71. # $HOME/.config -> dotfiles/.config
  72. # $HOME/.i3 -> dotfiles/i3
  73. # $HOME/.i3status.conf -> dotfiles/i3status.conf
  74. # $HOME/.vimrc -> dotfiles/vimrc
  75. #
  76. # This is impractical in most cases, as normally you want to select
  77. # applications to link--some dotfiles could be specific to some
  78. # hosts or even private.
  79. #
  80. # Setting 'config' as sub-vault will tell mklinks that you don't
  81. # want to link this item directly but rather each of its items:
  82. #
  83. # $HOME/.config/dunst -> dotfiles/config/dunst
  84. # $HOME/.config/uzbl -> dotfiles/config/uzbl
  85. # $HOME/.i3 -> dotfiles/i3
  86. # $HOME/.i3status.conf -> dotfiles/i3status.conf
  87. # $HOME/.vimrc -> dotfiles/vimrc
  88. #
  89. # This way, you can have other items under "$HOME/.config" that
  90. # are either purely local or linked to another vault.
  91. #
  92. # Note that the subvault can be deeper than one level, i.e.
  93. # subvault "local/share" also works:
  94. #
  95. # $HOME/.local/share/klavaro -> dotfiles/local/share/klavaro
  96. #
  97. local sv
  98. test -n "${SubVaults[0]}" && {
  99. think "using sub-vaults from command line"
  100. for sv in "${SubVaults[@]}";
  101. do
  102. echo "$sv"
  103. done
  104. return
  105. }
  106. test -f "$Vault/dotvault/subvaults" && {
  107. think "reading subvaults from file: $Vault/dotvault/subvaults"
  108. grep . "$Vault/dotvault/subvaults" \
  109. | grep -v "^#.*"
  110. return
  111. }
  112. think "using hard-coded list of subvaults"
  113. echo config
  114. }
  115. lsvault() {
  116. #
  117. # List all vaults and subvaults, depth-first
  118. #
  119. {
  120. echo "$Vault"
  121. lssv \
  122. | while IFS= read -r sv;
  123. do
  124. test -e "$Vault/$sv" || {
  125. think "ignoring non-existent sub-vault: $sv"
  126. continue
  127. }
  128. test -d "$Vault/$sv" || {
  129. warn "ignoring non-directory sub-vault: $sv"
  130. continue
  131. }
  132. echo "$Vault/$sv"
  133. done
  134. } \
  135. | LC_ALL=C sort \
  136. | tac
  137. }
  138. istarget() {
  139. #
  140. # True if item $1 is possible target
  141. #
  142. # Item is obviously not a possible target if it's
  143. # a (sub-)vault.
  144. #
  145. # A less obvious case is when item is an intermediate directory
  146. # on a path leading to a sub-vault. Such item cannot be a target!
  147. #
  148. # For example: there are sub-vaults:
  149. #
  150. # foo
  151. # foo/bar/baz
  152. #
  153. # and a tree:
  154. #
  155. # foo
  156. # ├── A
  157. # └── bar
  158. # └── baz
  159. # └── B
  160. #
  161. # Items A and B could both be targets, but baz could not be!
  162. #
  163. #TODO: write an explanation
  164. #
  165. local item=$1
  166. debugv item
  167. Verbose=false lsvault | grep -qxF "$item" && return 1
  168. Verbose=false lsvault | grep -qx "$item/.*" && return 1
  169. return 0
  170. }
  171. pfxpath() {
  172. #
  173. # Prefix paths with $1
  174. #
  175. # Just like `sed "s/^/$1/" but works with any characters
  176. # in $1 excepr newline, which is not allowed in paths..
  177. #
  178. local pfx="$1"
  179. local path
  180. while IFS= read -r path;
  181. do
  182. echo "$pfx$path"
  183. done
  184. }
  185. lstarget() {
  186. #
  187. # List potential items in vault $1
  188. #
  189. local vlt=$1
  190. debugv vlt
  191. local item
  192. find "$vlt" -mindepth 1 -maxdepth 1 -printf "%P\n" \
  193. | pfxpath "$vlt/" \
  194. | grep -v "$Vault/dotvault" \
  195. | while IFS= read -r item;
  196. do
  197. istarget "$item" && echo "$item"
  198. done
  199. }
  200. make_links() {
  201. #
  202. # Create all links
  203. #
  204. local slpath
  205. local target
  206. local vlt
  207. lsvault \
  208. | while IFS= read -r vlt;
  209. do
  210. lstarget "$vlt" \
  211. | while IFS= read -r target;
  212. do
  213. slpath=$(slpath "$target")
  214. if test -L "$slpath";
  215. then
  216. $ClobberLinks || {
  217. warn "skipping existing slpath: $slpath"
  218. continue
  219. }
  220. think "removing existing link: $slpath"
  221. maybe rm "$slpath"
  222. fi
  223. test -e "$slpath" && {
  224. warn "skipping existing non-link: $slpath"
  225. continue
  226. }
  227. maybe ln -sr "$target" "$slpath"
  228. done
  229. done
  230. }
  231. explore() {
  232. #
  233. # Show what would be done
  234. #
  235. local vlt
  236. local tgt
  237. local tgts
  238. lsvault \
  239. | sort \
  240. | while IFS= read -r vlt;
  241. do
  242. tgts=$(lstarget "$vlt")
  243. test -n "$tgts" || continue
  244. if test "$vlt" == "$Vault";
  245. then
  246. echo "VAULT:$vlt:"
  247. else
  248. echo
  249. echo "SUBVAULT:$vlt"
  250. fi
  251. sort <<<"$tgts" \
  252. | while IFS= read -r tgt;
  253. do
  254. echo "${tgt}:$(slpath "$tgt")"
  255. done \
  256. | sed -e "s|:$HOME|:=> ~|" \
  257. | column -ts: \
  258. | sed -e "s/^/ /"
  259. done
  260. }
  261. maybe() {
  262. #
  263. # Maybe do things $@, maybe not (if in debug mode)
  264. #
  265. $Debug && { debug "WOULD: $*"; return; }
  266. "$@"
  267. }
  268. slpath() {
  269. #
  270. # Print path where to create link for target $1
  271. #
  272. local item=$1
  273. echo "$HOME/.${item#$Vault/}"
  274. }
  275. route() {
  276. local Vault=$1; shift
  277. local SubVaults=()
  278. test -n "$Vault" || usage
  279. test -e "$Vault" || die "vault does not exist: $Vault"
  280. test -d "$Vault" || die "vault is not a directory: $Vault"
  281. SubVaults=("$@")
  282. case $Action in
  283. init) make_links ;;
  284. explore) explore ;;
  285. *) usage ;;
  286. esac
  287. }
  288. export LC_ALL=C
  289. main() {
  290. local Action
  291. local Debug=false
  292. local Verbose=false
  293. local ClobberLinks=false
  294. while true; do case $1 in
  295. -d) Debug=true; shift ;;
  296. -v) Verbose=true; shift ;;
  297. -f) ClobberLinks=true; shift ;;
  298. -*) usage ;;
  299. *) break ;;
  300. esac done
  301. Action=$1; shift
  302. route "$@"
  303. }
  304. main "$@"