1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
#!/bin/sh
# Bootstrap file for XDG_CONFIG_HOME
# by Case Duckworth <acdw@acdw.net>
### License:
# Everyone is permitted to do whatever with this software, without
# limitation. This software comes without any warranty whatsoever,
# but with two pieces of advice:
# - Don't hurt yourself.
# - Make good choices.
### Commentary:
# For some time now, there has been a standard place to put configuration files
# for various programs: $XDG_CONFIG_HOME. Slowly but surely, programs have
# been adapting to that standard for their configuration files, but there have
# been some stubborn holdouts. Examples include shells such as sh, bash, and
# zsh.
# Luckily, the filesystem can help us. This script takes input from a file in
# its $PWD, bootstrap.manifest, and reads linking directions from tab-delimited
# lines in that file. Then, it creates links from within the cozy confines of
# $XDG_CONFIG_HOME to the wild west of ~, so that everyone is happy: you have
# your zen garden of configuration, and misbehaving programs have their bazaar.
### Code:
### Main entry point
main() {
## Sanity checking
# Since bootstrap.sh does some naive path-mangling, let's show an error
# if it's not run correctly. Yes, there are other ways to run a
# script. But this script should ideally be run, like, one time. Also
# you can obviously comment this out or change it if you know what
# you're doing!
case "$0" in
./*) ;; # this is the way bootstrap.sh /should/ be run.
*)
printf >&2 'Weird invocation! %s\n' "$*"
printf >&2 'Try: cd <bootstrap-dir>; ./bootstrap.sh\n'
exit 127
;;
esac
## Variables
# option: -d/--dry-run
: "${BOOTSTRAP_ACTION:=link}"
# option: -v/--verbose
: "${BOOTSTRAP_DEBUG:=false}"
# option: -k/--keep-going
: "${BOOTSTRAP_QUIT_ON_ERROR:=true}"
# option: -m/--manifest FILE
: "${BOOTSTRAP_MANIFEST_FILE:=bootstrap.manifest}"
# option: -- (rest are passed to ln)
: "${BOOTSTRAP_LN_ARGS:=-s}"
## Handle command-line flags
# Basically an easier way of setting the above variables.
while [ -n "$1" ]; do
case "$1" in
-h|--help)
cat >&2 <<END_HELP
Usage: ./bootstrap.sh [-d] [-v] [-k] [-m FILE] [-f] [-- LN_OPTS]
OPTIONS:
-d, --dry-run
Only print what would happen.
-v, --verbose
Be more verbose about things.
-k, --keep-going
Keep going after an error.
-f, --force
Force linking. Passes -f to ln.
-m FILE, --manifest FILE
Use FILE as manifest.
Default: bootstrap.manifest.
-- Signify end of options. The rest are passed to ln.
END_HELP
exit
;;
-d|--dry-run)
BOOTSTRAP_ACTION=print
shift 1
;;
-v|--verbose)
BOOTSTRAP_DEBUG=true
shift 1
;;
-k|--keep-going)
BOOTSTRAP_QUIT_ON_ERROR=false
shift 1
;;
-m|--manifest)
case "$2" in
''|-*)
printf >&2 "Bad argument: '$2'"
exit 129
;;
esac
BOOTSTRAP_MANIFEST_FILE="$2"
shift 2
;;
-f|--force)
BOOTSTRAP_LN_ARGS="$BOOTSTRAP_LN_ARGS -f"
shift 1
;;
--)
shift 1
BOOTSTRAP_LN_ARGS="$@"
break
;;
esac
done
## Main loop
while IFS=' ' read -r source destination; do
# Ignore lines beginning with '#'
case "$source" in
'#'*)
if "$BOOTSTRAP_DEBUG"; then
printf >&2 '%s %s\n' \
"$source" "$destination"
fi
continue
;;
esac
# Ignore empty lines, or lines with only SOURCE or DESTINATION
if [ -z "$source" ] || [ -z "$destination" ]; then
if "$BOOTSTRAP_DEBUG"; then
printf >&2 'Skipping line: %s\t%s\n' \
"$source" "$destination"
fi
continue
fi
# Do the thing
if ! dispatch "$source" "$destination"; then
printf >&2 'ERROR: %s -> %s\n' \
"$source" "$destination"
if "$BOOTSTRAP_QUIT_ON_ERROR"; then
exit "$dispatch_error"
fi
fi
done < "$BOOTSTRAP_MANIFEST_FILE"
}
### Functions
dispatch() { # dispatch SOURCE DESTINATION
# Depending on environment variables, do the linking or displaying or
# whatever of a source and a destination.
## Variables
src="$1"
dest="$2"
dispatch_error=0 # success
## Sanitize pathnames
# If the SOURCE starts with ~, /, or $, keep it as-is; otherwise,
# prepend "$PWD/".
case "$src" in
'/'* | '~'* | '$'* ) ;;
*) src="$PWD/$src" ;;
esac
# Convert ~ to $HOME in SOURCE and DESTINATION, to get around shell
# quoting rules.
src="$(printf '%s\n' "$src" | sed "s#^~#$HOME#")"
dest="$(printf '%s\n' "$dest" | sed "s#^~#$HOME#")"
## Do the thing
# /Always/ tell the user what we're doing.
if [ -f "$dest" ]; then
printf >&2 'mv %s %s.old\n' "$dest" "$dest"
fi
printf >&2 "ln %s %s %s\n" "$BOOTSTRAP_LN_ARGS" "$src" "$dest"
case "$BOOTSTRAP_ACTION" in
link) # actually ... do the links
# if DESTINATION exists, move it to DESTINATION.old
if [ -f "$dest" ]; then
mv "$dest" "$dest.old"
fi
ln $BOOTSTRAP_LN_ARGS "$src" "$dest" ||
dispatch_error="$?"
;;
print) ;; # already printed.
esac
return "$dispatch_error"
}
### Do the thing
main "$@"
|