about summary refs log tree commit diff stats
path: root/bootstrap.sh
blob: f9b14321888c6b8b4ec9b9d8f87c21a6319b7472 (plain)
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 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 "$@"