Summary Files Commits Branches @commit @diff @stats
path: root / make
blob: 9731008330aa6ccf29654a046829c791ab72dc7b (plain) (blame)
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
#!/bin/sh
# IMPORTANT NOTE: The "local" keyword is not defined in POSIX, so I've decided to work without it.
#                 For this reason, I do "$X/$f" instead of assigning local variables.
#                 Another thing to consider is that in recursive functions, backtracking logic
#                 CANNOT depend on variables, which are changed somewhere in the body before
#                 the recursive step.

cd "${0%/*}"

[ -z "$HOSTNAME" ] && HOSTNAME="$(hostname)"
if [ -z "$HOSTNAME" ]
then
	echo "Set HOSTNAME variable to the system's name!" >/dev/stderr
	exit 1
fi
if [ ! -f "./SYSTEM/$HOSTNAME" ]
then
	echo "No configuration in ./SYSTEM for '$HOSTNAME'!" >/dev/stderr
	exit 1
fi

# __merge_link <.FILES_DIR> <HOME_DIR>
__merge_link() {
	while read -r f
	do
		# Output file doesn't exist -> Create link
		# This is also true on broken symlinks
		if [ ! -e "$2/$f" ]
		then
			if [ -h "$2/$f" ]
			then
				echo "Warn: '$2/$f' is a broken link to '$(readlink "$2/$f")', updating!"
				rm "$2/$f"
			fi
			ln -s "$1/$f" "$2/$f"
		# Output file is already linked to current -> Do nothing
		elif [ -h "$2/$f" -a "$(readlink $2/$f)" = "$1/$f" ]
		then
			:
		# Current file is a file (and output exists) -> Create link but also update current
		elif [ -f "$1/$f" ]
		then
			if [ -f "$2/$f" ]
			then
				# When output is NOT a link inside .files,
				# update current file with the contents of the output
				# Otherwise,
				# replace output file with (link to) current
				if ! cmp -s "$1/$f" "$2/$f"
				then
					if [ -h "$2/$f" ]
					then
						real="$(readlink "$2/$f")"
						if [ "${real#$PWD}" = "$real" ]
						then
							cp "$real" "$1/$f" 2>/dev/null
							echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr
						fi
					else
						cp "$2/$f" "$1/$f"
						echo "Warn: '$2/$f' already existed, '$1/$f' updated with its contents!" >/dev/stderr
					fi
				fi
				rm "$2/$f"

				ln -s "$1/$f" "$2/$f"
			else
				echo "Error: '$2/$f' already exists and is not a regular file!"
			fi
		# Current file is a directory (and output exists) -> Link current's files & dirs in output directory
		elif [ -d "$1/$f" ]
		then
			if [ -d "$2/$f" ]
			then
				# If output is a link to directory in .files, continuing with merge
				# will place links inside a directory in .files, which is bad.
				# Instead, we'll make the link to directory become a directory with
				# links to all inner files.
				if [ -h "$2/$f" ]
				then
					real="$(realpath "$2/$f")"
					if [ "${real#$PWD}" != "$real" ]
					then
						rm "$2/$f"
						mkdir "$2/$f"
						while read -r ff
						do
							ln -s "$real/$ff" "$2/$f/$ff"
						done <<FILES
$(ls -A1 "$real")
FILES
					fi
				fi
				__merge_link "$1/$f" "$2/$f"
			else
				echo "Error: '$2/$f' already exists and is not a directory!"
			fi
		else
			echo "Error: '$1/$f' is neither a file or directory!"
		fi
	done <<FILES
$(ls -A1 "$1")
FILES
}

link() {
	while read -r dir
	do
		dir="${dir%%*/}"
		if [ ! -d "./$dir" ]
		then
			echo "Unknown directory '$dir'!" >/dev/stderr
			continue
		fi

		__merge_link "$PWD/$dir" "$HOME"
		echo "Linked $dir"
	done <"./SYSTEM/$HOSTNAME"
}

# __remove_link <.FILES_DIR> <HOME_DIR>
__remove_link() {
	while read -r f
	do
		if [ -h "$2/$f" ]
		then
			real="$(realpath "$2/$f")"
			# Output file exists and is a link to current -> Remove output
			if [ "$real" = "$1/$f" ]
			then
				rm "$2/$f"
				continue
			# Output file exists and is a link in .files -> Do nothing (do not remove from .files)
			# This is possible in the case when we have .files/X/A and .files/Y/A, and we linked
			# .files/X but are now unlinking .files/Y
			elif [ "${real#$PWD}" != "$real" ]
			then
				continue
			fi
		fi
		if [ -d "$1/$f" -a -d "$2/$f" ]
		then
			__remove_link "$1/$f" "$2/$f"
		fi
	done <<FILES
$(ls -A1 "$1")
FILES
	# See IMPORTANT NOTE, since we're aiming for full POSIX compatibility,
	# the variable f will be overwritten by recursive calls, so this loop
	# cannot be merged with the loop above (remove dirs on backtracking)
	while read -r f
	do
		# Output file exists and is an empty directory -> Remove it
		# We're assuming this directory was created by the script and/or the user
		# will not care for empty dirs
		if [ -d "$2/$f" -a -z "$(ls -A "$2/$f" 2>/dev/null)" ]
		then
			rm -r "$2/$f"
		fi
	done <<FILES
$(ls -A1 "$1")
FILES
}

unlink() {
	while read -r dir
	do
		dir="${dir%%*/}"
		if [ ! -d "./$dir" ]
		then
			echo "Unknown directory '$dir'!" >/dev/stderr
			continue
		fi

		__remove_link "$PWD/$dir" "$HOME"
		echo "Unlinked $dir"
	done <"./SYSTEM/$HOSTNAME"
}

case "$1" in
	(unlink) unlink ;;
	(link|*) link ;;
esac