-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmakelinks.sh
executable file
·276 lines (245 loc) · 7.55 KB
/
makelinks.sh
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#!/bin/bash
# Symlink all of these git commands to somewhere in $PATH.
# -Christopher Welborn 01-31-2016
appname="git-commands: makelinks"
appversion="0.0.1"
apppath="$(readlink -f "${BASH_SOURCE[0]}")"
appscript="${apppath##*/}"
appdir="${apppath%/*}"
shopt -s nullglob
function confirm {
# Confirm a user's answer to a yes/no quesion.
[[ -n "$1" ]] && echo -e "\n$1"
echo -e -n "\nContinue? (y/N): "
read -r ans
yespat='^[Yy]([Ee][Ss])?$'
[[ "$ans" =~ $yespat ]] || return 1
return 0
}
function echo_err {
# Echo to stderr.
echo -e "$@" 1>&2
}
function fail {
# Print a message to stderr and exit with an error status code.
echo_err "$@"
exit 1
}
function fail_usage {
# Print a usage failure message, and exit with an error status code.
print_usage "$@"
exit 1
}
function get_install_dir {
# Get installation directory, make sure it's valid.
if ! bin_dir="$(select_path "Choose the installation path:")"; then
echo_err "Quitting."
return 2
fi
if [[ -z "$bin_dir" ]]; then
fail "Failed to set installation directory!"
elif [[ ! -e "$bin_dir" ]]; then
fail "Missing installation directory: $bin_dir"
elif [[ ! -w "$bin_dir" ]]; then
fail "\nNo permissions to write to: $bin_dir\n Do you need sudo?"
fi
printf "%s" "$bin_dir"
return 0
}
function install {
local gitcmds bin_dir new_links
local gitcmd gitcmdbase gitcmdlnk
local destname
local errs existing created plural
local errfmt linkfmt
local output
gitcmds=("$appdir"/git-*.{sh,py})
(( ${#gitcmds[@]} > 0 )) || fail "No scripts found!: $appdir"
# Get installation path.
bin_dir="$(get_install_dir)" || return 2
# Get new links to make, or report any errors that would prevent creating them.
declare -A new_links
let errs=0
let existing=0
for gitcmd in "${gitcmds[@]}"; do
gitcmdbase="${gitcmd##*/}"
gitcmdlnk="${gitcmdbase%%.*}"
destname="$bin_dir/$gitcmdlnk"
if [[ -z "$destname" ]]; then
printf_err "%15s %s\n" "Name failure:" "$gitcmd"
let errs+=1
elif [[ -e "$destname" ]]; then
printf_err "%15s %s\n" "Already exists:" "$destname"
let errs+=1
let existing+=1
else
new_links["$gitcmd"]="$destname"
fi
done
plural="items"
((existing == 1)) && plural="item"
((existing)) && echo -e "\n$existing existing $plural will not be overwritten."
if ((dryrun)); then
echo -e "\nDry run, not creating anything...\n"
else
plural="symlinks"
((${#new_links[@]} == 1)) && plural="symlink"
if ! confirm "This will install ${#new_links[@]} $plural in: $bin_dir"; then
fail "\nCancelling installation."
fi
echo -e "\nInstalling ${#new_links[@]} $plural...\n"
fi
# Create symlinks, or report any errors that would prevent creating them.
let created=0
for gitcmd in "${!new_links[@]}"; do
destname="${new_links[$gitcmd]}"
if ((dryrun)); then
printf "%15s %s\n" "Dry run:" "ln -s $gitcmd $destname"
else
if output="$(ln -s "$gitcmd" "$destname" 2>&1)"; then
let created+=1
printf "%15s %s\n" "Created:" "$destname"
else
printf_err "%15s %s\n%19s %s\n" "Failed:" "$destname" "Message:" "${output:-<none>}"
let errs+=1
fi
fi
done
# Print a final status message.
errfmt="\nThere were %s errors.\n"
(( errs == 1 )) && errfmt="\nThere was %s error.\n"
printf_err "$errfmt" "$errs"
linkfmt="%s links were created.\n"
(( created == 1 )) && linkfmt="%s link was created.\n"
# shellcheck disable=SC2059
printf "$linkfmt" "$created"
((! errs)) && echo "Success!"
((existing)) && echo_err "You will need to remove any existing items."
return $errs
}
function print_usage {
# Show usage reason if first arg is available.
[[ -n "$1" ]] && echo_err "\n$1\n"
echo "$appname v. $appversion
Usage:
$appscript -h | -v
$appscript (-r | -u)
Options:
-d,--dryrun : Don't install anything, just print the actions.
-h,--help : Show this message.
-r,--remove : Remove any links that were installed.
-u,--uninstall : Same as --remove.
-v,--version : Show $appname version and exit.
With no arguments, any non-existing links will be installed.
"
}
function printf_err {
# printf to stderr.
# shellcheck disable=SC2059
printf "$@" 1>&2
}
function select_path {
# BASH function to make the user select a directory in $PATH.
# Outputs the path on success, returns 1 on error, and 2 when
# no path is selected.
# Arguments:
# $1 : Prompt for the select menu.
# Default: "Choose the installation path:"
# Example usage:
# if mypath="$(select_path)"; then
# echo "Success: $mypath"
# else
# echo "No path selected."
local pathdirs=($(printf "%s" "${PATH//:/$'\n'}" | sort))
((${#pathdirs} > 0)) || {
printf "Failed to find \$PATH directories!\n" 1>&2
return 1
}
PS3=$'\n'"Type \`c\` to cancel."$'\n'"${1:-Choose the installation path:} "
local usepath
select usepath in "${pathdirs[@]}"; do
case "${#usepath}" in
0 )
return 2
;;
* )
printf "%s" "$usepath"
break
;;
esac
done
}
function uninstall {
local gitcmds gitcmdbase gitcmdlnk names found_links
local dirpath possible name link errs
declare -a names gitcmds found_links
gitcmds=("$appdir"/git-*.{sh,py})
(( ${#gitcmds[@]} > 0 )) || fail "No scripts found!: $appdir"
for gitcmd in "${gitcmds[@]}"; do
gitcmdbase="${gitcmd##*/}"
gitcmdlnk="${gitcmdbase%%.*}"
names+=("$gitcmdlnk")
done
while IFS=$'\n' read -r dirpath; do
[[ -d "$dirpath" ]] || continue
while IFS=$'\n' read -r possible; do
for name in "${names[@]}"; do
[[ "$possible" == *"$name" ]] && {
found_links+=("$possible")
break
}
done
done < <(find "$dirpath" -type l -name "*git-*")
done < <(echo "$PATH" | tr ':' '\n')
local plural
plural="symlinks"
((${#found_links[@]} == 1)) && plural="symlink"
echo "Found ${#found_links[@]} $plural:"
printf " %s\n" "${found_links[@]}"
confirm "\nThis will remove ${#found_links[@]} $plural." || fail "\nCancelling removal."
let errs=0
for link in "${found_links[@]}"; do
((dryrun)) && {
printf "%15s %s\n" "Dry run:" "rm $link"
continue
}
rm "$link" || {
let errs+=1
continue
}
printf "Removed: %s\n" "$link"
done
return $errs
}
declare -a nonflags
dryrun=0
do_uninstall=0
for arg; do
case "$arg" in
"-d"|"--dryrun" )
dryrun=1
;;
"-h"|"--help" )
print_usage ""
exit 0
;;
"-u"|"--uninstall"|"-r"|"--remove")
do_uninstall=1
;;
"-v"|"--version" )
echo -e "$appname v. $appversion\n"
exit 0
;;
-*)
fail_usage "Unknown flag argument: $arg"
;;
*)
nonflags=("${nonflags[@]}" "$arg")
esac
done
if ((do_uninstall)); then
uninstall
else
install
fi
exit