-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path.bashrc_easystow
232 lines (196 loc) · 9.74 KB
/
.bashrc_easystow
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
########################################################################################################################
# GNU stow helpers
# GNU stow operates on packages - minimal working units that represent collections of files and directories to back up:
# https://www.gnu.org/software/stow/manual/stow.html#Terminology
#
# E.g. in your $HOME/backup/dotfiles you have directory 'homeroot' where you plan to store
# packages that represent config files stored in your home directory.
#
# Then you might have sub-dirs homeroot/bash and homeroot/zsh, each storing a set of config files for respective shell.
# Here 'bash' and 'zsh' are packages.
# While 'homeroot' is a kind of category for convenient labeling of miscellaneous configurations.
#
# Current implementation defines three categories:
# - homeroot - for storing configurations from $HOME directory
# - dotconfig - for storing configurations from $HOME/.config directory
# - other - for storing configurations from other places
#
# So, the backup storage is located in $HOME/backup/dotfiles and uses following structure:
# dotfiles
# ├── dotconfig
# ├── homeroot
# └── other
# dotfiles/secrets
# ├── dotconfig
# ├── homeroot
# └── other
#
# Secrets (located in a 'secrets' sub-dir) is a sub-storage for backing up anything that contains
# passwords, auth keys, and any other kinds of sensitive information.
#
# Categories are stored in __DF_* variables defined below, as well as the root storage path and path to 'secrets'.
# These are configurable things.
# Just:
# - add/remove/change your categories
# - change paths if needed
# - update __DF_CATEGORIES variable with appropriate categories
# Next aliases and functions are at your disposal:
# stow-ls-structure - for listing storage structure (tree-view)
# stow-ls-packages - for listing all stowed packages (tree-view)
#
# stow-simulate - for running stow packaging in simulation mode (stow option -n)
# stow-do - for stowing packages
# stow-force - for stowing packages in forced mode (stow option --adopt)
# stow-unstow - for un-stowing packages (stow option -D)
# stow-restow - for re-stowing packages (stow option -R)
#
# 'DF' stands for 'DOTFILES'
__DF_STOW_ROOT_DIR=$HOME/backup/dotfiles # root storage directory
__DF_TARGET_DIR=$HOME # target directory
__DF_SECRETS=secrets # sub-directory name for packages with sensitive info (passwords, auth tokens, etc.)
__DF_SECRETS_ROOT_DIR=$__DF_STOW_ROOT_DIR/$__DF_SECRETS # root secrets sub-directory
__DF_CATEGORIES="dotconfig homeroot other" # all categories
alias stow-ls-structure="env tree -dL 1 -I $__DF_SECRETS $__DF_STOW_ROOT_DIR --noreport &&
env tree -dL 1 $__DF_SECRETS_ROOT_DIR --noreport"
alias stow-ls-packages="env tree -dL 2 -I $__DF_SECRETS $__DF_STOW_ROOT_DIR --noreport &&
env tree -dL 2 $__DF_SECRETS_ROOT_DIR --noreport"
# General autocompletion function for using in specific functions.
# Usage examples for function 'foo' bound to use completions:
# foo <TAB><TAB> -> should output "dotconfig homeroot other secrets"
# foo sec<TAB><TAB> -> should output "secrets"
# foo secrets <TAB><TAB> -> should output "dotconfig homeroot"
# foo dotconfig <TAB><TAB> -> should output packages in category 'dotconfig'
# foo secrets homeroot <TAB><TAB> -> should output packages in category 'homeroot' of 'secrets' sub-storage
function __stow_package_completion(){
latest="${COMP_WORDS[$COMP_CWORD]}"
categories_array=($__DF_CATEGORIES)
arg1="${COMP_WORDS[1]}"
arg2="${COMP_WORDS[2]}"
arg1_regex_exact="\<${arg1}\>" # will test for exact value, not regex part
arg2_regex_exact="\<${arg2}\>" # will test for exact value, not regex part
completions=""
if [ $COMP_CWORD -eq 1 ]; then
# no args specified yet - choose category or 'secrets'
# autocomplete with actual (existing) categories in root storage directory + 'secrets' directory
root_categories=$(cd $__DF_STOW_ROOT_DIR && env ls -d $__DF_CATEGORIES 2>/dev/null)
completions="$root_categories $__DF_SECRETS"
elif [ $arg1 == $__DF_SECRETS ] && [ $COMP_CWORD -eq 2 ]; then
# first arg is 'secrets', no 2nd arg specified yet - choose category under 'secrets'
# autocomplete with actual (existing) categories in 'secrets' sub-directory
completions="$(cd $__DF_SECRETS_ROOT_DIR && env ls -d $__DF_CATEGORIES 2>/dev/null)"
elif [[ ${categories_array[@]} =~ $arg1_regex_exact ]]; then
# first arg is a package category - choose package
completions=$(env ls $__DF_STOW_ROOT_DIR/$arg1 $(__build_comp_exclusions ${COMP_WORDS[@]}) 2>/dev/null)
elif [ $arg1 == $__DF_SECRETS ] && [[ ${categories_array[@]} =~ $arg2_regex_exact ]]; then
# first arg is 'secrets'
# second arg is a package category - choose package
completions=$(env ls $__DF_STOW_ROOT_DIR/$arg1/$arg2 $(__build_comp_exclusions ${COMP_WORDS[@]}) 2>/dev/null)
fi
COMPREPLY=($(compgen -W "$completions" -- $latest))
return 0
}
# Build exclusions for completions we already selected, echo result (as bash cannot return strings).
# This result will be evaluated by calling function.
function __build_comp_exclusions() {
exclusions="" # -I option for ls command excludes files enumerated after it from displaying
for package in $@; do
exclusions+=" -I $package"
done
echo $exclusions
}
# Build arguments for stow command from autocomplete args returned by caller function.
# Args that are category directories or 'secrets' become parts of storage path.
# Remainder args will be treaded as stow packages.
function __build_stow_args() {
arg_num=1
directories=($__DF_CATEGORIES $__DF_SECRETS)
path_to_packages=$__DF_STOW_ROOT_DIR
for arg in $@; do
arg_regex_exact="\<${arg}\>" # will test for exact value, not regex part
if [[ ${directories[@]} =~ $arg_regex_exact ]]; then
path_to_packages+="/$arg"
((arg_num++))
else
break
fi
done
# check whether any category selected
roots=($__DF_STOW_ROOT_DIR $__DF_SECRETS_ROOT_DIR)
if [[ ${roots[@]} =~ $path_to_packages ]]; then
return 1 # no packages can be listed in stow root or 'secrets'
fi
stow_packages=""
stow_options=""
# remainder args - treat them either as stow options (when arg begins with '-') or as stow packages
for arg in ${@:$arg_num}; do
if [[ $arg == -* ]]; then
stow_options+=" $arg"
else
stow_packages+=" $arg"
fi
done
# some category selected
if [ -z "$stow_packages" ]; then
# no packages specified - treat it as 'all packages in category'
stow_packages=$(env ls $path_to_packages 2>/dev/null)
if [ -z "$stow_packages" ]; then
return 1 # no packages were listed in selected category
else
if [ ${@:((arg_num-2)):1} == $__DF_SECRETS ]; then
echo 1>&2 "Processing *all* packages in '${@:((arg_num-1)):1}' category of '$__DF_SECRETS' ..."
else
echo 1>&2 "Processing *all* packages in '${@:((arg_num-1)):1}' category ..."
fi
fi
else
if [ ${@:((arg_num-2)):1} == $__DF_SECRETS ]; then
echo 1>&2 "Processing packages in '${@:((arg_num-1)):1}' category of '$__DF_SECRETS':$stow_packages"
else
echo 1>&2 "Processing packages in '${@:((arg_num-1)):1}' category:$stow_packages"
fi
fi
stow_args="-v -d $path_to_packages -t $__DF_TARGET_DIR $stow_packages $stow_options"
echo $stow_args # result will be evaluated by calling function
}
# Make an actual execute of stow command and process passed arguments.
# If last arg starts with '-' then extract it; from remainder args build stow command arguments (path/packages).
function __stow_execute() {
stow_exec_option=""
last_arg="${@: -1}"
if [[ $last_arg == -* ]]; then # last arg starts with '-' (like '-n' in 'stow-simulate')
stow_exec_option+=$last_arg # treat this arg as an option to the 'stow' command
stow_args="$(__build_stow_args ${@:1:$#-1})" # from remainder args build stow arguments (path/packages)
else
stow_args="$(__build_stow_args $@)" # last arg does not start with '-', build stow arguments from all args
fi
# check return value of previous action (execution of __build_stow_args)
retval=$?
if [ $retval -ne 0 ]; then
# if return value is not 0, then no packages were specified - notify user
echo "Please specify either category and at least one package or just category"
else
# run stow command passing possible options and built arguments
stow $stow_exec_option $stow_args
fi
}
# stow in simulation mode
function stow-simulate() { __stow_execute $@ -n ; }
# actual stow packaging
function stow-do() { __stow_execute $@ ; }
# stow packaging with --adopt flag (kind of force - use it with caution as it will modify already stored content)
function stow-force() { __stow_execute $@ --adopt ; }
# un-stow
function stow-unstow() { __stow_execute $@ -D ; }
# re-stow
function stow-restow() { __stow_execute $@ -R ; }
# enable autocompletion for custom stow functions
complete -F __stow_package_completion "stow-simulate"
complete -F __stow_package_completion "stow-do"
complete -F __stow_package_completion "stow-force"
complete -F __stow_package_completion "stow-unstow"
complete -F __stow_package_completion "stow-restow"
### Inspirations:
###
### https://www.baeldung.com/linux/check-bash-array-contains-value
### https://www.baeldung.com/linux/shell-auto-completion
### https://unix.stackexchange.com/questions/564759/can-a-bash-script-include-its-own-auto-completions