@renault-digital/bash-base 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +78 -10
- package/README.md +79 -6
- package/bin/bash-base +597 -313
- package/docs/bash-zsh-compatibility.md +202 -0
- package/docs/coverage.md +289 -0
- package/docs/design-principles.md +174 -0
- package/docs/references.md +123 -69
- package/man/bash-base.1 +72 -5
- package/package.json +5 -6
- package/scripts/test-shell-compat.sh +391 -0
- package/scripts/test.sh +82 -14
- package/spec/array.spec.sh +8 -2
- package/spec/coverage_edges.spec.sh +88 -0
- package/spec/coverage_zsh_array_map.spec.sh +24 -0
- package/spec/process.spec.sh +113 -16
- package/spec/reflection.spec.sh +1 -1
- package/spec/spec_helper.sh +17 -0
- package/spec/string.spec.sh +14 -17
- package/src/alias.sh +35 -4
- package/src/argument.sh +222 -124
- package/src/array.sh +96 -98
- package/src/constant.sh +1 -1
- package/src/document.sh +17 -11
- package/src/out.sh +26 -15
- package/src/process.sh +4 -4
- package/src/reflection.sh +76 -17
- package/src/string.sh +92 -27
- package/src/tool.sh +29 -13
package/bin/bash-base
CHANGED
|
@@ -1,11 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Backward-compatible command names. Use wrapper functions rather than aliases:
|
|
4
|
+
# aliases need different non-interactive expansion rules in Bash and zsh, while
|
|
5
|
+
# functions work identically when this library is sourced by either shell.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
# @NAME
|
|
8
|
+
# args_confirm -- show the name and value of variables, and continue execute if args_confirm by user, or exit if not
|
|
9
|
+
# @SYNOPSIS
|
|
10
|
+
# args_confirm variableName...
|
|
11
|
+
# @DESCRIPTION
|
|
12
|
+
# **variableName...** some existed variable names to show its value
|
|
13
|
+
# @EXAMPLES
|
|
14
|
+
# a="correct value"
|
|
15
|
+
# b="wrong value"
|
|
16
|
+
# args_confirm a b
|
|
17
|
+
# @SEE_ALSO
|
|
18
|
+
# args_print, stop_if_failed
|
|
19
|
+
args_confirm() {
|
|
20
|
+
confirm_to_continue "$@"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# @NAME
|
|
24
|
+
# args_print -- if LOG_LEVEL<=$LOG_LEVEL_WARN, show the name and value of variables
|
|
25
|
+
# @SYNOPSIS
|
|
26
|
+
# args_print variableName...
|
|
27
|
+
# @DESCRIPTION
|
|
28
|
+
# **variableName...** some existed variable names to show its value
|
|
29
|
+
# @EXAMPLES
|
|
30
|
+
# var1="value 1"
|
|
31
|
+
# var2="value 2"
|
|
32
|
+
# args_print var1 var2
|
|
33
|
+
# @SEE_ALSO
|
|
34
|
+
# print_header, print_error, print_success, print_warn, print_info, print_debug
|
|
35
|
+
args_print() {
|
|
36
|
+
print_args "$@"
|
|
37
|
+
}
|
|
7
38
|
|
|
8
|
-
|
|
39
|
+
if [[ -n "${ZSH_VERSION:-}" ]]; then
|
|
40
|
+
# zsh may temporarily replace $0 with the sourced filename when
|
|
41
|
+
# FUNCTION_ARGZERO is enabled. ZSH_ARGZERO always keeps the invocation name.
|
|
42
|
+
_bash_base_argzero="${ZSH_ARGZERO:-$0}"
|
|
43
|
+
else
|
|
44
|
+
_bash_base_argzero="$0"
|
|
45
|
+
fi
|
|
46
|
+
THIS_SCRIPT_PATH="$_bash_base_argzero" # script source path for args_valid_* lookup
|
|
47
|
+
THIS_SCRIPT_NAME="$(basename "$_bash_base_argzero")" # the main script name
|
|
48
|
+
unset _bash_base_argzero
|
|
9
49
|
|
|
10
50
|
SHORT_DESC="${THIS_SCRIPT_NAME%%.*}"
|
|
11
51
|
SHORT_DESC="${SHORT_DESC/[\-_]/ }" # redefine it to show your script short description in the 'NAME' field of generated -h response
|
|
@@ -20,9 +60,14 @@ USAGE='' # redefine it in your script only if the gene
|
|
|
20
60
|
# @EXAMPLES
|
|
21
61
|
# args_parse $# "$@" newVar1 newVar2 newVar3
|
|
22
62
|
# @SEE_ALSO
|
|
23
|
-
|
|
24
|
-
local nbArgValues nbPositionalVarNames option
|
|
25
|
-
local
|
|
63
|
+
args_parse() {
|
|
64
|
+
local nbArgValues nbPositionalVarNames option OPTARG OPTIND nbPositionalArgValues
|
|
65
|
+
local showUsage=false
|
|
66
|
+
local element validCommand description defaultUsage
|
|
67
|
+
local strPositionalVarNames="" strPositionalValuesExamples=""
|
|
68
|
+
local -a positionalArgValues positionalVarNames
|
|
69
|
+
# Bash legacy contract: prompt, possibleValues, i, descriptions and
|
|
70
|
+
# onlyPositionalVarNames were caller-visible, not function-local.
|
|
26
71
|
|
|
27
72
|
nbArgValues=$1
|
|
28
73
|
shift 1
|
|
@@ -45,79 +90,103 @@ function args_parse() {
|
|
|
45
90
|
shift $((OPTIND - 1))
|
|
46
91
|
|
|
47
92
|
nbPositionalArgValues=$((nbArgValues - OPTIND + 1))
|
|
48
|
-
positionalArgValues=(
|
|
49
|
-
|
|
93
|
+
positionalArgValues=()
|
|
94
|
+
# Split positional values from destination names without a new local
|
|
95
|
+
# counter; the historical Bash `i` global is handled below.
|
|
96
|
+
while (($# > nbPositionalVarNames)); do
|
|
97
|
+
positionalArgValues+=("$1")
|
|
98
|
+
shift
|
|
99
|
+
done
|
|
100
|
+
positionalVarNames=("$@")
|
|
50
101
|
|
|
51
102
|
onlyPositionalVarNames=()
|
|
52
103
|
descriptions=''
|
|
53
|
-
if ((nbPositionalVarNames > 0)); then
|
|
54
|
-
for i in $(seq 0 $((nbPositionalVarNames - 1))); do
|
|
55
|
-
element=${positionalVarNames[i]}
|
|
56
104
|
|
|
105
|
+
# Pair values with variable names without relying on Bash's 0-based array
|
|
106
|
+
# indexes (zsh uses a different default array index base).
|
|
107
|
+
set -- "${positionalArgValues[@]}"
|
|
108
|
+
for element in "${positionalVarNames[@]}"; do
|
|
109
|
+
# Do not introduce a local value variable: callers may legally name
|
|
110
|
+
# their destination positionalValue (or prompt). Save it only in $1.
|
|
111
|
+
|
|
112
|
+
# zsh FUNCTION_ARGZERO may change $0 to the current function name.
|
|
113
|
+
# Only scan a caller script when there is an actual script file.
|
|
114
|
+
validCommand=''
|
|
115
|
+
if [[ -f "${THIS_SCRIPT_PATH:-}" ]]; then
|
|
57
116
|
validCommand="$(
|
|
58
|
-
grep -E "
|
|
117
|
+
grep -E "^[[:space:]]*args_valid.*[[:space:]]+${element}[[:space:]]+" "$THIS_SCRIPT_PATH" | head -1 |
|
|
59
118
|
awk -F "'" -v OFS="'" '{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
119
|
+
for (i=2; i<=NF; i+=2) {
|
|
120
|
+
gsub(/ /, "_SPACE_", $i);
|
|
121
|
+
gsub(/\$/, "_DOLLAR_", $i);
|
|
122
|
+
gsub(/\(/, "_PARENTHESES_LEFT_", $i);
|
|
123
|
+
gsub(/\)/, "_PARENTHESES_RIGHT_", $i);
|
|
124
|
+
}
|
|
125
|
+
print
|
|
126
|
+
}' |
|
|
68
127
|
sed -e "s/\'//g"
|
|
69
128
|
)"
|
|
129
|
+
fi
|
|
70
130
|
|
|
71
|
-
|
|
72
|
-
|
|
131
|
+
if [[ -z $validCommand ]]; then
|
|
132
|
+
eval "${element}=\"\${1-}\""
|
|
133
|
+
onlyPositionalVarNames+=("${element}")
|
|
134
|
+
description="a valid value for ${element}"
|
|
135
|
+
else
|
|
136
|
+
if [[ "$validCommand" == *args_valid_or_select_pipe* ]]; then
|
|
137
|
+
eval "${element}=\"\${1-}\""
|
|
73
138
|
onlyPositionalVarNames+=("${element}")
|
|
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
|
-
prompt="optional, $(reflect_nth_arg 4 "${validCommand}"). should be set like '${element}=value' before ${THIS_SCRIPT_NAME} or export to OS environment"
|
|
101
|
-
if [[ -n $(reflect_nth_arg 5 "${validCommand}") ]]; then
|
|
102
|
-
prompt="${prompt}. if absent, fallback to: $(reflect_nth_arg 5 "${validCommand}")"
|
|
103
|
-
else
|
|
104
|
-
prompt="${prompt}, empty if absent"
|
|
105
|
-
fi
|
|
106
|
-
possibleValues=""
|
|
139
|
+
prompt="$(reflect_nth_arg 4 "$validCommand")"
|
|
140
|
+
possibleValues=", possible values: $(reflect_nth_arg 3 "$validCommand")"
|
|
141
|
+
elif [[ "$validCommand" == *args_valid_or_select_args* ]]; then
|
|
142
|
+
eval "${element}=\"\${1-}\""
|
|
143
|
+
onlyPositionalVarNames+=("${element}")
|
|
144
|
+
prompt="$(reflect_nth_arg 3 "$validCommand")"
|
|
145
|
+
possibleValues=", you can select one using wizard if you do not know which value is valid"
|
|
146
|
+
elif [[ "$validCommand" == *args_valid_or_select* ]]; then
|
|
147
|
+
eval "${element}=\"\${1-}\""
|
|
148
|
+
onlyPositionalVarNames+=("${element}")
|
|
149
|
+
prompt="$(reflect_nth_arg 4 "$validCommand")"
|
|
150
|
+
possibleValues=", you can select one using wizard if you do not know which value is valid"
|
|
151
|
+
elif [[ "$validCommand" == *args_valid_or_read* ]]; then
|
|
152
|
+
eval "${element}=\"\${1-}\""
|
|
153
|
+
onlyPositionalVarNames+=("${element}")
|
|
154
|
+
prompt="$(reflect_nth_arg 4 "$validCommand")"
|
|
155
|
+
if [[ -n $(reflect_nth_arg 5 "$validCommand") ]]; then
|
|
156
|
+
prompt="${prompt}, proposed value: $(reflect_nth_arg 5 "$validCommand")"
|
|
157
|
+
fi
|
|
158
|
+
possibleValues=""
|
|
159
|
+
elif [[ "$validCommand" == *args_valid_or_default* ]]; then
|
|
160
|
+
prompt="optional, $(reflect_nth_arg 4 "$validCommand"). should be set like ${element}=value before ${THIS_SCRIPT_NAME} or export to OS environment"
|
|
161
|
+
if [[ -n $(reflect_nth_arg 5 "$validCommand") ]]; then
|
|
162
|
+
prompt="${prompt}. if absent, fallback to: $(reflect_nth_arg 5 "$validCommand")"
|
|
163
|
+
else
|
|
164
|
+
prompt="${prompt}, empty if absent"
|
|
107
165
|
fi
|
|
166
|
+
possibleValues=""
|
|
167
|
+
fi
|
|
108
168
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
169
|
+
prompt="$(
|
|
170
|
+
printf '%s\n' "${prompt}" |
|
|
171
|
+
string_replace "_SPACE_" " " |
|
|
172
|
+
string_replace "_DOLLAR_" "$" |
|
|
173
|
+
string_replace "_PARENTHESES_LEFT_" "(" |
|
|
174
|
+
string_replace "_PARENTHESES_RIGHT_" ")"
|
|
175
|
+
)"
|
|
116
176
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
177
|
+
description="${prompt}${possibleValues}"
|
|
178
|
+
fi
|
|
179
|
+
descriptions+="$(printf "\n %-20s%s" "${element} " "${description}")"
|
|
180
|
+
if (($# > 0)); then
|
|
181
|
+
shift
|
|
182
|
+
fi
|
|
183
|
+
done
|
|
184
|
+
if [[ -n "${BASH_VERSION:-}" && $nbPositionalVarNames -gt 0 ]]; then
|
|
185
|
+
# The original Bash implementation used a caller-visible `i` as
|
|
186
|
+
# a loop counter over output names. Respect a destination named i.
|
|
187
|
+
if ! array_in i "${positionalVarNames[@]}"; then
|
|
188
|
+
i=$((nbPositionalVarNames - 1))
|
|
189
|
+
fi
|
|
121
190
|
fi
|
|
122
191
|
|
|
123
192
|
if [ ${#onlyPositionalVarNames[@]} -gt 0 ]; then
|
|
@@ -125,36 +194,36 @@ function args_parse() {
|
|
|
125
194
|
strPositionalValuesExamples=" \"$(array_join 'Value" "' onlyPositionalVarNames)Value\""
|
|
126
195
|
fi
|
|
127
196
|
|
|
128
|
-
declare_heredoc defaultUsage
|
|
129
|
-
|
|
130
|
-
|
|
197
|
+
declare_heredoc defaultUsage <<EOF
|
|
198
|
+
${COLOR_BOLD_YELLOW}NAME${COLOR_END}
|
|
199
|
+
${THIS_SCRIPT_NAME} -- ${SHORT_DESC}
|
|
131
200
|
|
|
132
|
-
|
|
133
|
-
|
|
201
|
+
${COLOR_BOLD_YELLOW}SYNOPSIS${COLOR_END}
|
|
202
|
+
[optionalVariable=value ...] ./${THIS_SCRIPT_NAME} [-qh]${strPositionalVarNames}
|
|
134
203
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
204
|
+
${COLOR_BOLD_YELLOW}DESCRIPTION${COLOR_END}
|
|
205
|
+
[-h] help, print the usage
|
|
206
|
+
[-q] optional, Run quietly, no confirmation
|
|
207
|
+
${descriptions}
|
|
139
208
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
209
|
+
${COLOR_BOLD_YELLOW}EXAMPLES${COLOR_END}
|
|
210
|
+
help, print the usage:
|
|
211
|
+
./${THIS_SCRIPT_NAME} -h
|
|
143
212
|
|
|
144
|
-
|
|
145
|
-
|
|
213
|
+
run with all params, if run in quiet mode with -q, be sure all the params are valid:
|
|
214
|
+
./${THIS_SCRIPT_NAME} [-q]${strPositionalValuesExamples}
|
|
146
215
|
|
|
147
|
-
|
|
148
|
-
|
|
216
|
+
run using wizard, input value for params step by step:
|
|
217
|
+
./${THIS_SCRIPT_NAME}
|
|
149
218
|
|
|
150
|
-
|
|
151
|
-
|
|
219
|
+
run with optional variables, example:
|
|
220
|
+
myOpt1=myValue1 myOpt2=myValue2 ./${THIS_SCRIPT_NAME}
|
|
152
221
|
|
|
153
|
-
|
|
154
|
-
|
|
222
|
+
or you can run with some params, and input value for other params using wizard.
|
|
223
|
+
EOF
|
|
155
224
|
|
|
156
|
-
if [ "$showUsage" == true ]; then
|
|
157
|
-
|
|
225
|
+
if [[ "$showUsage" == true ]]; then
|
|
226
|
+
printf '%b\n' "${USAGE:-$defaultUsage}"
|
|
158
227
|
exit 0
|
|
159
228
|
fi
|
|
160
229
|
}
|
|
@@ -175,26 +244,38 @@ function args_parse() {
|
|
|
175
244
|
# args_valid_or_select varEmpty arr "Which app"
|
|
176
245
|
# @SEE_ALSO
|
|
177
246
|
# args_valid_or_select_pipe, args_valid_or_read, args_valid_or_default
|
|
178
|
-
|
|
179
|
-
local valueVarName validValuesVarName prompt value
|
|
247
|
+
args_valid_or_select() {
|
|
248
|
+
local valueVarName validValuesVarName prompt value PS3
|
|
249
|
+
local -a validValues
|
|
250
|
+
|
|
180
251
|
valueVarName="${1}"
|
|
181
|
-
validValuesVarName
|
|
182
|
-
validValues="$2[@]"
|
|
252
|
+
validValuesVarName="$2"
|
|
183
253
|
prompt="${3}"
|
|
184
|
-
|
|
185
|
-
|
|
254
|
+
eval 'value="${'"$valueVarName"'-}"'
|
|
255
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
256
|
+
# Preserve Bash's historical echo/word-splitting normalization, without
|
|
257
|
+
# reparsing the value with eval (which would execute input as code).
|
|
258
|
+
# shellcheck disable=SC2086,SC2116
|
|
259
|
+
value="$(echo $value)"
|
|
260
|
+
fi
|
|
261
|
+
eval "validValues=( \"\${${validValuesVarName}[@]}\" )"
|
|
186
262
|
|
|
187
263
|
while ! array_contains "${validValuesVarName}" "${value}"; do
|
|
188
|
-
|
|
264
|
+
printf '\n%s ?\n' "$prompt"
|
|
189
265
|
[[ -n "${value}" ]] && print_error "the input '${value}' is not valid."
|
|
190
266
|
|
|
191
267
|
PS3="choose one by ${COLOR_BOLD_YELLOW}number${COLOR_END} [1|2|...] ? "
|
|
192
|
-
select value in "${
|
|
268
|
+
select value in "${validValues[@]}"; do
|
|
193
269
|
break
|
|
194
270
|
done
|
|
195
271
|
done
|
|
196
|
-
|
|
197
|
-
|
|
272
|
+
|
|
273
|
+
eval "${valueVarName}=\"\$value\""
|
|
274
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
275
|
+
# shellcheck disable=SC2086,SC2116
|
|
276
|
+
value="$(echo $value)"
|
|
277
|
+
fi
|
|
278
|
+
printf "Selected value: ${COLOR_BLUE}'%s'${COLOR_END}\n" "$value"
|
|
198
279
|
}
|
|
199
280
|
|
|
200
281
|
# @NAME
|
|
@@ -210,8 +291,9 @@ function args_valid_or_select() {
|
|
|
210
291
|
# args_valid_or_select_pipe sel "a|ab|d" "which value"
|
|
211
292
|
# @SEE_ALSO
|
|
212
293
|
# args_valid_or_select, args_valid_or_select_args, args_valid_or_read, args_valid_or_default
|
|
213
|
-
|
|
214
|
-
local valueVarName validValues prompt
|
|
294
|
+
args_valid_or_select_pipe() {
|
|
295
|
+
local valueVarName validValues prompt
|
|
296
|
+
local -a newArray
|
|
215
297
|
valueVarName="${1}"
|
|
216
298
|
validValues="${2}"
|
|
217
299
|
prompt="${3}"
|
|
@@ -233,15 +315,21 @@ function args_valid_or_select_pipe() {
|
|
|
233
315
|
# args_valid_or_select_args sel "which value" "a" "ab" "d"
|
|
234
316
|
# @SEE_ALSO
|
|
235
317
|
# args_valid_or_select, args_valid_or_select_pipe, args_valid_or_read, args_valid_or_default
|
|
236
|
-
|
|
318
|
+
args_valid_or_select_args() {
|
|
237
319
|
local valueVarName value prompt PS3
|
|
238
320
|
valueVarName="${1}"
|
|
239
|
-
|
|
321
|
+
eval 'value="${'"$valueVarName"'-}"'
|
|
322
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
323
|
+
# Preserve Bash's historical echo/word-splitting normalization, without
|
|
324
|
+
# reparsing the value with eval (which would execute input as code).
|
|
325
|
+
# shellcheck disable=SC2086,SC2116
|
|
326
|
+
value="$(echo $value)"
|
|
327
|
+
fi
|
|
240
328
|
prompt="${2}"
|
|
241
329
|
shift 2
|
|
242
330
|
|
|
243
331
|
while ! array_in "${value}" "$@"; do
|
|
244
|
-
|
|
332
|
+
printf '\n%s ?\n' "$prompt"
|
|
245
333
|
[[ -n "${value}" ]] && print_error "the input '${value}' is not valid."
|
|
246
334
|
|
|
247
335
|
PS3="choose one by ${COLOR_BOLD_YELLOW}number${COLOR_END} [1|2|...] ? "
|
|
@@ -249,8 +337,13 @@ function args_valid_or_select_args() {
|
|
|
249
337
|
break
|
|
250
338
|
done
|
|
251
339
|
done
|
|
252
|
-
|
|
253
|
-
|
|
340
|
+
|
|
341
|
+
eval "${valueVarName}=\"\$value\""
|
|
342
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
343
|
+
# shellcheck disable=SC2086,SC2116
|
|
344
|
+
value="$(echo $value)"
|
|
345
|
+
fi
|
|
346
|
+
printf "Selected value: ${COLOR_BLUE}'%s'${COLOR_END}\n" "$value"
|
|
254
347
|
}
|
|
255
348
|
|
|
256
349
|
# @NAME
|
|
@@ -268,28 +361,48 @@ function args_valid_or_select_args() {
|
|
|
268
361
|
# args_valid_or_read destRootPackage '^.+$' "Destination root package" "${defaultDestRootPackage}"
|
|
269
362
|
# @SEE_ALSO
|
|
270
363
|
# args_valid_or_select, args_valid_or_select_args, args_valid_or_select_pipe, args_valid_or_default
|
|
271
|
-
|
|
272
|
-
local
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
364
|
+
args_valid_or_read() {
|
|
365
|
+
local valueVarName="$1"
|
|
366
|
+
local regExp="${2}"
|
|
367
|
+
local prompt="${3}"
|
|
368
|
+
local proposedValue="${4-}"
|
|
369
|
+
local value
|
|
370
|
+
|
|
371
|
+
eval 'value="${'"$valueVarName"'-}"'
|
|
372
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
373
|
+
# Preserve Bash's historical echo/word-splitting normalization, without
|
|
374
|
+
# reparsing the value with eval (which would execute input as code).
|
|
375
|
+
# shellcheck disable=SC2086,SC2116
|
|
376
|
+
value="$(echo $value)"
|
|
377
|
+
fi
|
|
277
378
|
|
|
278
379
|
if [[ -n "${proposedValue}" ]]; then
|
|
279
380
|
prompt="${prompt} [${proposedValue}]"
|
|
280
|
-
if [[ -z "${value}" && "${modeQuiet}" == true ]]; then
|
|
381
|
+
if [[ -z "${value}" && "${modeQuiet:-false}" == true ]]; then
|
|
281
382
|
value="${proposedValue}"
|
|
282
383
|
fi
|
|
283
384
|
fi
|
|
284
|
-
|
|
385
|
+
|
|
386
|
+
while ! [[ $value =~ $regExp ]]; do
|
|
285
387
|
[[ -n "${value}" ]] && print_error "the input '${value}' is not valid, please input again."
|
|
286
|
-
|
|
388
|
+
if [[ -t 0 ]]; then
|
|
389
|
+
printf '%s: ' "$prompt" >&2
|
|
390
|
+
fi
|
|
391
|
+
read -r value || value=""
|
|
287
392
|
if [[ -z "${value}" ]]; then
|
|
288
393
|
value="${proposedValue}"
|
|
289
394
|
fi
|
|
290
395
|
done
|
|
291
|
-
|
|
292
|
-
|
|
396
|
+
|
|
397
|
+
eval "${valueVarName}=\"\$value\""
|
|
398
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
399
|
+
# Legacy Bash printed via `eval echo $name`: collapse display-only
|
|
400
|
+
# whitespace, but leave the actual assigned value unchanged.
|
|
401
|
+
# shellcheck disable=SC2086,SC2116
|
|
402
|
+
printf "Inputted value: ${COLOR_BLUE}'%s'${COLOR_END}\n" "$(echo $value)"
|
|
403
|
+
else
|
|
404
|
+
printf "Inputted value: ${COLOR_BLUE}'%s'${COLOR_END}\n" "$value"
|
|
405
|
+
fi
|
|
293
406
|
}
|
|
294
407
|
|
|
295
408
|
# @NAME
|
|
@@ -310,22 +423,38 @@ function args_valid_or_read() {
|
|
|
310
423
|
# args_valid_or_default destRootPackage '^.+$' "Destination root package" "${defaultDestRootPackage}"
|
|
311
424
|
# @SEE_ALSO
|
|
312
425
|
# args_valid_or_select, args_valid_or_select_args, args_valid_or_select_pipe, args_valid_or_read
|
|
313
|
-
|
|
314
|
-
local
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
426
|
+
args_valid_or_default() {
|
|
427
|
+
local valueVarName="$1"
|
|
428
|
+
local regExp="${2}"
|
|
429
|
+
local prompt="${3}"
|
|
430
|
+
local defaultValue="${4-}"
|
|
431
|
+
local value
|
|
432
|
+
|
|
433
|
+
eval 'value="${'"$valueVarName"'-}"'
|
|
434
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
435
|
+
# Preserve Bash's historical echo/word-splitting normalization, without
|
|
436
|
+
# reparsing the value with eval (which would execute input as code).
|
|
437
|
+
# shellcheck disable=SC2086,SC2116
|
|
438
|
+
value="$(echo $value)"
|
|
439
|
+
fi
|
|
319
440
|
|
|
320
|
-
if [[ -z "${value}" || ! $
|
|
321
|
-
prompt="for param ${
|
|
441
|
+
if [[ -z "${value}" || ! $value =~ $regExp ]]; then
|
|
442
|
+
prompt="for param ${valueVarName}:"
|
|
322
443
|
[[ -n "${value}" ]] && prompt+=" the input '${value}' is not valid" || prompt+=" no input"
|
|
323
444
|
[[ -n "${defaultValue}" ]] && prompt+=", fallback to default value '${defaultValue}'" || prompt+=", fallback to empty"
|
|
324
445
|
print_warn "${prompt}"
|
|
325
446
|
value="${defaultValue}"
|
|
326
447
|
fi
|
|
327
|
-
|
|
328
|
-
|
|
448
|
+
|
|
449
|
+
eval "${valueVarName}=\"\$value\""
|
|
450
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
451
|
+
# Legacy Bash printed via `eval echo $name`: collapse display-only
|
|
452
|
+
# whitespace, but leave the actual assigned value unchanged.
|
|
453
|
+
# shellcheck disable=SC2086,SC2116
|
|
454
|
+
printf "Inputted value: ${COLOR_BLUE}'%s'${COLOR_END}\n" "$(echo $value)"
|
|
455
|
+
else
|
|
456
|
+
printf "Inputted value: ${COLOR_BLUE}'%s'${COLOR_END}\n" "$value"
|
|
457
|
+
fi
|
|
329
458
|
}
|
|
330
459
|
|
|
331
460
|
# @NAME
|
|
@@ -340,25 +469,28 @@ function args_valid_or_default() {
|
|
|
340
469
|
# array_join '|' myArry ==> " a | b c "
|
|
341
470
|
# @SEE_ALSO
|
|
342
471
|
# string_split_to_array, array_describe, array_from_describe
|
|
343
|
-
|
|
472
|
+
array_join() {
|
|
344
473
|
local delimiter="$1"
|
|
345
|
-
local array="$2[@]"
|
|
474
|
+
local array="$2[@]" # Same local names as the pre-PR Bash implementation.
|
|
475
|
+
local element result="" delimiterLength
|
|
346
476
|
|
|
347
|
-
|
|
348
|
-
|
|
477
|
+
# Expand the named array directly into function positional parameters. This
|
|
478
|
+
# avoids introducing new local variables that shadow caller array names.
|
|
479
|
+
eval 'set -- "${'"$2"'[@]}"'
|
|
480
|
+
for element in "$@"; do
|
|
349
481
|
result="${result}${element}${delimiter}"
|
|
350
482
|
done
|
|
351
483
|
|
|
352
484
|
delimiterLength=$(string_length "${delimiter}")
|
|
353
485
|
if string_is_empty "${result}"; then
|
|
354
|
-
|
|
486
|
+
printf '\n'
|
|
355
487
|
else
|
|
356
488
|
string_sub 0 $((0 - delimiterLength)) "${result}"
|
|
357
489
|
fi
|
|
358
490
|
}
|
|
359
491
|
|
|
360
492
|
# @NAME
|
|
361
|
-
# array_describe -- convert the array to its string representation
|
|
493
|
+
# array_describe -- convert the array to its shell-native string representation
|
|
362
494
|
# @SYNOPSIS
|
|
363
495
|
# array_describe arrayVarName
|
|
364
496
|
# @DESCRIPTION
|
|
@@ -368,8 +500,20 @@ function array_join() {
|
|
|
368
500
|
# array_describe myArray ==> ([0]='a' [1]='b')
|
|
369
501
|
# @SEE_ALSO
|
|
370
502
|
# string_split_to_array, array_join, array_from_describe
|
|
371
|
-
|
|
372
|
-
|
|
503
|
+
array_describe() {
|
|
504
|
+
local _bash_base_declaration _bash_base_values
|
|
505
|
+
|
|
506
|
+
_bash_base_declaration="$(typeset -p "$1")" || return 1
|
|
507
|
+
_bash_base_values="${_bash_base_declaration#*=}"
|
|
508
|
+
|
|
509
|
+
# macOS Bash 3.2 wraps array assignments in one outer single-quoted
|
|
510
|
+
# word. Newer Bash prints the compound assignment directly. Decode
|
|
511
|
+
# the outer shell quoting before preserving the existing output format.
|
|
512
|
+
if [[ -n "${BASH_VERSION:-}" && "$_bash_base_values" == \'*\' ]]; then
|
|
513
|
+
eval "_bash_base_values=$_bash_base_values" || return 1
|
|
514
|
+
fi
|
|
515
|
+
|
|
516
|
+
printf '%s\n' "$_bash_base_values" | tr '"' "'"
|
|
373
517
|
}
|
|
374
518
|
|
|
375
519
|
# @NAME
|
|
@@ -384,7 +528,7 @@ function array_describe() {
|
|
|
384
528
|
# array_from_describe myNewArray < fileNameContentString
|
|
385
529
|
# @SEE_ALSO
|
|
386
530
|
# string_split_to_array, array_join, array_describe
|
|
387
|
-
|
|
531
|
+
array_from_describe() {
|
|
388
532
|
local newArrayVarName="$1"
|
|
389
533
|
local string="${2-$(cat)}"
|
|
390
534
|
|
|
@@ -405,11 +549,14 @@ function array_from_describe() {
|
|
|
405
549
|
# echo "ab" | array_contains arr
|
|
406
550
|
# @SEE_ALSO
|
|
407
551
|
# array_remove, array_in
|
|
408
|
-
|
|
552
|
+
array_contains() {
|
|
409
553
|
local array="$1[@]"
|
|
410
554
|
local seeking="${2-$(cat)}"
|
|
411
555
|
|
|
412
|
-
|
|
556
|
+
# Use the same locals as before; adding a `values` or `arrayVarName`
|
|
557
|
+
# local here would hide callers' arrays with those legitimate names.
|
|
558
|
+
eval 'set -- "${'"$1"'[@]}"'
|
|
559
|
+
array_in "${seeking}" "$@"
|
|
413
560
|
}
|
|
414
561
|
|
|
415
562
|
# @NAME
|
|
@@ -424,7 +571,7 @@ function array_contains() {
|
|
|
424
571
|
# array_in "a b" "a" "b" "c" "a b" "f" "g"
|
|
425
572
|
# @SEE_ALSO
|
|
426
573
|
# array_remove, array_contains
|
|
427
|
-
|
|
574
|
+
array_in() {
|
|
428
575
|
local seeking="$1"
|
|
429
576
|
shift
|
|
430
577
|
local element
|
|
@@ -446,16 +593,14 @@ function array_in() {
|
|
|
446
593
|
# array_sort myArray ==> ([0]='aa' [1]='aa' [2]='bb')
|
|
447
594
|
# @SEE_ALSO
|
|
448
595
|
# array_sort_distinct
|
|
449
|
-
|
|
596
|
+
array_sort() {
|
|
450
597
|
local arrayVarName="$1"
|
|
451
598
|
local strSorted arrSorted
|
|
452
599
|
|
|
453
600
|
strSorted="$(array_join $'\n' "${arrayVarName}" | sort)"
|
|
454
601
|
string_split_to_array $'\n' arrSorted "${strSorted}"
|
|
455
602
|
|
|
456
|
-
|
|
457
|
-
local command="${arrayVarName}=(\"${string}\")"
|
|
458
|
-
eval "${command}"
|
|
603
|
+
eval "${arrayVarName}=( \"\${arrSorted[@]}\" )"
|
|
459
604
|
}
|
|
460
605
|
|
|
461
606
|
# @NAME
|
|
@@ -469,16 +614,14 @@ function array_sort() {
|
|
|
469
614
|
# array_sort_distinct myArray ==> ([0]='aa' [1]='bb')
|
|
470
615
|
# @SEE_ALSO
|
|
471
616
|
# array_sort
|
|
472
|
-
|
|
617
|
+
array_sort_distinct() {
|
|
473
618
|
local arrayVarName="$1"
|
|
474
619
|
local strSorted arrSorted
|
|
475
620
|
|
|
476
621
|
strSorted="$(array_join $'\n' "${arrayVarName}" | sort -u)"
|
|
477
622
|
string_split_to_array $'\n' arrSorted "${strSorted}"
|
|
478
623
|
|
|
479
|
-
|
|
480
|
-
local command="${arrayVarName}=(\"${string}\")"
|
|
481
|
-
eval "${command}"
|
|
624
|
+
eval "${arrayVarName}=( \"\${arrSorted[@]}\" )"
|
|
482
625
|
}
|
|
483
626
|
|
|
484
627
|
# @NAME
|
|
@@ -491,38 +634,30 @@ function array_sort_distinct() {
|
|
|
491
634
|
# myArray=('aa' 'bb' 'aa')
|
|
492
635
|
# array_length myArray ==> 3
|
|
493
636
|
# @SEE_ALSO
|
|
494
|
-
|
|
637
|
+
array_length() {
|
|
495
638
|
local arrayVarName="$1"
|
|
496
|
-
local
|
|
497
|
-
|
|
498
|
-
eval "string='$'{${arrayVarName}[@]}"
|
|
499
|
-
command="tmp=(\"${string}\")"
|
|
500
|
-
eval "${command}"
|
|
639
|
+
local -a tmp
|
|
501
640
|
|
|
502
|
-
|
|
641
|
+
eval "tmp=( \"\${${arrayVarName}[@]}\" )"
|
|
642
|
+
printf '%s\n' "${#tmp[@]}"
|
|
503
643
|
}
|
|
504
644
|
|
|
505
645
|
# @NAME
|
|
506
|
-
# array_reset_index -- reset
|
|
646
|
+
# array_reset_index -- reset indexes to the shell-native contiguous sequence, save the result to original variable name
|
|
507
647
|
# @SYNOPSIS
|
|
508
648
|
# array_reset_index arrayVarName
|
|
509
649
|
# @DESCRIPTION
|
|
510
650
|
# **arrayVarName** the variable name of the array to be processed
|
|
511
651
|
# @EXAMPLES
|
|
512
|
-
# myArray=([2]='a' [5]='c' [11]='dd')
|
|
652
|
+
# Bash example: myArray=([2]='a' [5]='c' [11]='dd')
|
|
513
653
|
# array_reset_index myArray ==> ([0]='a' [1]='c' [2]='dd')
|
|
514
654
|
# @SEE_ALSO
|
|
515
|
-
|
|
655
|
+
array_reset_index() {
|
|
516
656
|
local arrayVarName="$1"
|
|
517
|
-
local
|
|
518
|
-
|
|
519
|
-
eval "string='$'{${arrayVarName}[@]}"
|
|
520
|
-
command="tmp=(\"${string}\")"
|
|
521
|
-
eval "${command}"
|
|
657
|
+
local -a tmp
|
|
522
658
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
eval "${command}"
|
|
659
|
+
eval "tmp=( \"\${${arrayVarName}[@]}\" )"
|
|
660
|
+
eval "${arrayVarName}=( \"\${tmp[@]}\" )"
|
|
526
661
|
}
|
|
527
662
|
|
|
528
663
|
# @NAME
|
|
@@ -540,7 +675,7 @@ function array_reset_index() {
|
|
|
540
675
|
# array_equals myArray1 myArray2 false && echo Y || echo N ==> N
|
|
541
676
|
# array_equals myArray1 myArray2 true && echo Y || echo N ==> Y
|
|
542
677
|
# @SEE_ALSO
|
|
543
|
-
|
|
678
|
+
array_equals() {
|
|
544
679
|
local arrayVarName1="$1"
|
|
545
680
|
local arrayVarName2="$2"
|
|
546
681
|
local ignoreOrder=${3:-true}
|
|
@@ -563,7 +698,7 @@ function array_equals() {
|
|
|
563
698
|
array_reset_index tmp2
|
|
564
699
|
fi
|
|
565
700
|
|
|
566
|
-
[ "$(array_describe tmp1)" == "$(array_describe tmp2)" ]
|
|
701
|
+
[[ "$(array_describe tmp1)" == "$(array_describe tmp2)" ]]
|
|
567
702
|
}
|
|
568
703
|
|
|
569
704
|
# @NAME
|
|
@@ -582,25 +717,23 @@ function array_equals() {
|
|
|
582
717
|
# array_intersection myArray1 myArray2 newArray false
|
|
583
718
|
# @SEE_ALSO
|
|
584
719
|
# array_subtract, array_union
|
|
585
|
-
|
|
720
|
+
array_intersection() {
|
|
586
721
|
local array1="$1[@]"
|
|
587
722
|
local arrayVarName2="$2"
|
|
588
723
|
local newArrayVarName="$3"
|
|
589
724
|
local ignoreOrderAndDuplicated=${4:-true}
|
|
590
|
-
|
|
591
725
|
local tmp element2 string command
|
|
726
|
+
|
|
727
|
+
eval 'set -- "${'"$1"'[@]}"'
|
|
592
728
|
tmp=()
|
|
593
|
-
for element2 in "
|
|
729
|
+
for element2 in "$@"; do
|
|
594
730
|
array_contains "$arrayVarName2" "$element2" && array_append tmp "$element2"
|
|
595
731
|
done
|
|
596
732
|
|
|
597
733
|
if [ "${ignoreOrderAndDuplicated}" = true ]; then
|
|
598
734
|
array_sort_distinct tmp
|
|
599
735
|
fi
|
|
600
|
-
|
|
601
|
-
string="\${tmp[@]}"
|
|
602
|
-
command="${newArrayVarName}=(\"${string}\")"
|
|
603
|
-
eval "${command}"
|
|
736
|
+
eval "${newArrayVarName}=( \"\${tmp[@]}\" )"
|
|
604
737
|
}
|
|
605
738
|
|
|
606
739
|
# @NAME
|
|
@@ -619,25 +752,23 @@ function array_intersection() {
|
|
|
619
752
|
# array_subtract myArray1 myArray2 newArray false
|
|
620
753
|
# @SEE_ALSO
|
|
621
754
|
# array_intersection, array_union
|
|
622
|
-
|
|
755
|
+
array_subtract() {
|
|
623
756
|
local array1="$1[@]"
|
|
624
757
|
local arrayVarName2="$2"
|
|
625
758
|
local newArrayVarName="$3"
|
|
626
759
|
local ignoreOrderAndDuplicated=${4:-true}
|
|
627
|
-
|
|
628
760
|
local tmp element2 string command
|
|
761
|
+
|
|
762
|
+
eval 'set -- "${'"$1"'[@]}"'
|
|
629
763
|
tmp=()
|
|
630
|
-
for element2 in "
|
|
764
|
+
for element2 in "$@"; do
|
|
631
765
|
array_contains "$arrayVarName2" "$element2" || array_append tmp "$element2"
|
|
632
766
|
done
|
|
633
767
|
|
|
634
768
|
if [ "${ignoreOrderAndDuplicated}" = true ]; then
|
|
635
769
|
array_sort_distinct tmp
|
|
636
770
|
fi
|
|
637
|
-
|
|
638
|
-
string="\${tmp[@]}"
|
|
639
|
-
command="${newArrayVarName}=(\"${string}\")"
|
|
640
|
-
eval "${command}"
|
|
771
|
+
eval "${newArrayVarName}=( \"\${tmp[@]}\" )"
|
|
641
772
|
}
|
|
642
773
|
|
|
643
774
|
# @NAME
|
|
@@ -656,28 +787,25 @@ function array_subtract() {
|
|
|
656
787
|
# array_union myArray1 myArray2 newArray false
|
|
657
788
|
# @SEE_ALSO
|
|
658
789
|
# array_intersection, array_union
|
|
659
|
-
|
|
790
|
+
array_union() {
|
|
660
791
|
local array1="$1[@]"
|
|
661
792
|
local array2="$2[@]"
|
|
662
793
|
local newArrayVarName="$3"
|
|
663
794
|
local ignoreOrderAndDuplicated=${4:-true}
|
|
664
|
-
|
|
665
795
|
local tmp element2 string command
|
|
796
|
+
|
|
797
|
+
# Union consumes both arrays as a single ordered sequence. Both are
|
|
798
|
+
# expanded before positional arguments are replaced.
|
|
799
|
+
eval 'set -- "${'"$1"'[@]}" "${'"$2"'[@]}"'
|
|
666
800
|
tmp=()
|
|
667
|
-
for element2 in "
|
|
668
|
-
array_append tmp "$element2"
|
|
669
|
-
done
|
|
670
|
-
for element2 in "${!array2}"; do
|
|
801
|
+
for element2 in "$@"; do
|
|
671
802
|
array_append tmp "$element2"
|
|
672
803
|
done
|
|
673
804
|
|
|
674
805
|
if [ "${ignoreOrderAndDuplicated}" = true ]; then
|
|
675
806
|
array_sort_distinct tmp
|
|
676
807
|
fi
|
|
677
|
-
|
|
678
|
-
string="\${tmp[@]}"
|
|
679
|
-
command="${newArrayVarName}=(\"${string}\")"
|
|
680
|
-
eval "${command}"
|
|
808
|
+
eval "${newArrayVarName}=( \"\${tmp[@]}\" )"
|
|
681
809
|
}
|
|
682
810
|
|
|
683
811
|
# @NAME
|
|
@@ -692,14 +820,15 @@ function array_union() {
|
|
|
692
820
|
# array_append myArray "ele ment1" "ele ment2"
|
|
693
821
|
# @SEE_ALSO
|
|
694
822
|
# array_remove
|
|
695
|
-
|
|
823
|
+
array_append() {
|
|
696
824
|
local arrayVarName="$1"
|
|
697
825
|
shift
|
|
698
826
|
|
|
699
|
-
local elementToAppend
|
|
827
|
+
local elementToAppend
|
|
700
828
|
for elementToAppend in "$@"; do
|
|
701
|
-
|
|
702
|
-
|
|
829
|
+
# Keep the value out of the eval program text; expand it only after eval
|
|
830
|
+
# has selected the destination array name.
|
|
831
|
+
eval "${arrayVarName}+=( \"\$elementToAppend\" )"
|
|
703
832
|
done
|
|
704
833
|
}
|
|
705
834
|
|
|
@@ -715,24 +844,19 @@ function array_append() {
|
|
|
715
844
|
# array_remove arr "ab"
|
|
716
845
|
# @SEE_ALSO
|
|
717
846
|
# array_contains, array_append
|
|
718
|
-
|
|
847
|
+
array_remove() {
|
|
719
848
|
local arrayVarName="$1"
|
|
720
849
|
local element="$2"
|
|
721
|
-
|
|
722
850
|
local string command tmp index
|
|
723
|
-
eval "string='$'{${arrayVarName}[@]}"
|
|
724
|
-
command="tmp=(\"${string}\")"
|
|
725
|
-
eval "${command}"
|
|
726
851
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
852
|
+
eval 'set -- "${'"$arrayVarName"'[@]}"'
|
|
853
|
+
tmp=()
|
|
854
|
+
for index in "$@"; do
|
|
855
|
+
if [[ "$index" != "$element" ]]; then
|
|
856
|
+
tmp+=("$index")
|
|
730
857
|
fi
|
|
731
858
|
done
|
|
732
|
-
|
|
733
|
-
string="\${tmp[@]}"
|
|
734
|
-
command="${arrayVarName}=(\"${string}\")"
|
|
735
|
-
eval "${command}"
|
|
859
|
+
eval "${arrayVarName}=( \"\${tmp[@]}\" )"
|
|
736
860
|
}
|
|
737
861
|
|
|
738
862
|
# @NAME
|
|
@@ -746,7 +870,7 @@ function array_remove() {
|
|
|
746
870
|
# arr=(" a " " b c ")
|
|
747
871
|
# array_clone arr newArray
|
|
748
872
|
# @SEE_ALSO
|
|
749
|
-
|
|
873
|
+
array_clone() {
|
|
750
874
|
local arrayVarName="$1"
|
|
751
875
|
local arrayVarName2="$2"
|
|
752
876
|
|
|
@@ -765,23 +889,27 @@ function array_clone() {
|
|
|
765
889
|
# arr=(" a " " b c ")
|
|
766
890
|
# array_map arr "string_trim | wc -m | string_trim" newArray
|
|
767
891
|
# @SEE_ALSO
|
|
768
|
-
|
|
892
|
+
array_map() {
|
|
769
893
|
local array="$1[@]"
|
|
770
894
|
local pipedOperators="$2"
|
|
771
|
-
local newArrayVarName="$3"
|
|
772
|
-
|
|
895
|
+
local newArrayVarName="${3-}"
|
|
773
896
|
local tmp element mapped_value string command
|
|
897
|
+
|
|
898
|
+
eval 'set -- "${'"$1"'[@]}"'
|
|
774
899
|
tmp=()
|
|
775
|
-
for element in "
|
|
776
|
-
|
|
777
|
-
|
|
900
|
+
for element in "$@"; do
|
|
901
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
902
|
+
# Retain the original Bash echo semantics for values such as -n,
|
|
903
|
+
# but never evaluate an input element as shell source.
|
|
904
|
+
mapped_value="$(echo "$element" | eval "$pipedOperators")"
|
|
905
|
+
else
|
|
906
|
+
mapped_value="$(printf '%s\n' "$element" | eval "$pipedOperators")"
|
|
907
|
+
fi
|
|
778
908
|
tmp+=("${mapped_value}")
|
|
779
909
|
done
|
|
780
910
|
|
|
781
911
|
if [[ -n "${newArrayVarName}" ]]; then
|
|
782
|
-
|
|
783
|
-
command="${newArrayVarName}=(\"${string}\")"
|
|
784
|
-
eval "${command}"
|
|
912
|
+
eval "${newArrayVarName}=( \"\${tmp[@]}\" )"
|
|
785
913
|
else
|
|
786
914
|
array_join $'\n' tmp
|
|
787
915
|
fi
|
|
@@ -799,29 +927,28 @@ function array_map() {
|
|
|
799
927
|
# arr=("NAME A" "NAME B" "OTHER")
|
|
800
928
|
# array_filter arr 'NAME' newArray
|
|
801
929
|
# @SEE_ALSO
|
|
802
|
-
|
|
930
|
+
array_filter() {
|
|
803
931
|
local array="$1[@]"
|
|
804
932
|
local regExp="$2"
|
|
805
|
-
local newArrayVarName="$3"
|
|
806
|
-
|
|
933
|
+
local newArrayVarName="${3-}"
|
|
807
934
|
local tmp element string command
|
|
935
|
+
|
|
936
|
+
eval 'set -- "${'"$1"'[@]}"'
|
|
808
937
|
tmp=()
|
|
809
|
-
for element in "
|
|
810
|
-
if [[ $
|
|
938
|
+
for element in "$@"; do
|
|
939
|
+
if [[ $element =~ $regExp ]]; then
|
|
811
940
|
array_append tmp "${element}"
|
|
812
941
|
fi
|
|
813
942
|
done
|
|
814
943
|
|
|
815
944
|
if [[ -n "${newArrayVarName}" ]]; then
|
|
816
|
-
|
|
817
|
-
command="${newArrayVarName}=(\"${string}\")"
|
|
818
|
-
eval "${command}"
|
|
945
|
+
eval "${newArrayVarName}=( \"\${tmp[@]}\" )"
|
|
819
946
|
else
|
|
820
947
|
array_join $'\n' tmp
|
|
821
948
|
fi
|
|
822
949
|
}
|
|
823
950
|
|
|
824
|
-
NEW_LINE_SED="\\$(
|
|
951
|
+
NEW_LINE_SED="\\$(printf '\r\n')" # Constant: the return and new line character, used with sed
|
|
825
952
|
export NEW_LINE_SED
|
|
826
953
|
|
|
827
954
|
COLOR_BOLD_MAGENTA=$'\e[1;35m' # Constant: color for printing Header
|
|
@@ -853,9 +980,10 @@ export LOG_LEVEL_DEBUG LOG_LEVEL_INFO LOG_LEVEL_WARN LOG_LEVEL_ERROR
|
|
|
853
980
|
# doc_lint_script_comment "${shellScriptFile}"
|
|
854
981
|
# @SEE_ALSO
|
|
855
982
|
# doc_comment_to_markdown
|
|
856
|
-
|
|
983
|
+
doc_lint_script_comment() {
|
|
857
984
|
local shellScriptFile="$1"
|
|
858
|
-
local element strAllFunctionsAndTheirTags arrAllFunctionsAndTheirTags manTags strFunctionAndItsTags arrFunctionAndItsTags intersection
|
|
985
|
+
local element strAllFunctionsAndTheirTags arrAllFunctionsAndTheirTags manTags strFunctionAndItsTags arrFunctionAndItsTags intersection
|
|
986
|
+
local counter=0 lastFunction=""
|
|
859
987
|
|
|
860
988
|
# format the comment
|
|
861
989
|
sed -E -i'.bk' \
|
|
@@ -863,22 +991,27 @@ function doc_lint_script_comment() {
|
|
|
863
991
|
-e "s/^#/# /g" \
|
|
864
992
|
-e "s/^#[[:space:]]+$/#/g" \
|
|
865
993
|
-e "s/^#[[:space:]]*!/#!/g" \
|
|
866
|
-
-e "s/^#[[:space:]]*(@|-|#+)/#
|
|
994
|
+
-e "s/^#[[:space:]]*(@|-|#+)/# \\1/g" \
|
|
867
995
|
"${shellScriptFile}"
|
|
868
996
|
rm -fr "${shellScriptFile}.bk"
|
|
869
997
|
|
|
870
|
-
#
|
|
871
|
-
|
|
998
|
+
# Accept both historical `function name()` declarations and the shared
|
|
999
|
+
# Bash/zsh `name()` form.
|
|
1000
|
+
strAllFunctionsAndTheirTags=$(grep -E -e '^# @' -e '^[[:space:]]*(function[[:space:]]+)?[0-9A-Za-z_-]+[[:space:]]*\(\)[[:space:]]*\{' "${shellScriptFile}" | string_replace_regex '\(\)|#' '' | string_trim)
|
|
872
1001
|
string_split_to_array "{" arrAllFunctionsAndTheirTags "${strAllFunctionsAndTheirTags}"
|
|
873
|
-
|
|
1002
|
+
manTags=('@NAME' '@SYNOPSIS' '@DESCRIPTION' '@EXAMPLES' '@SEE_ALSO')
|
|
874
1003
|
for strFunctionAndItsTags in "${arrAllFunctionsAndTheirTags[@]}"; do
|
|
875
1004
|
string_split_to_array $'\n' arrFunctionAndItsTags "${strFunctionAndItsTags}"
|
|
876
1005
|
array_intersection manTags arrFunctionAndItsTags intersection false
|
|
877
1006
|
array_equals manTags intersection false
|
|
878
1007
|
if [[ $? -ne 0 ]]; then
|
|
879
|
-
((counter
|
|
880
|
-
|
|
881
|
-
|
|
1008
|
+
counter=$((counter + 1))
|
|
1009
|
+
typeset -p manTags intersection
|
|
1010
|
+
lastFunction=""
|
|
1011
|
+
for element in "${arrFunctionAndItsTags[@]}"; do
|
|
1012
|
+
lastFunction="$element"
|
|
1013
|
+
done
|
|
1014
|
+
print_error "the comments is not the same as template for ${lastFunction}"
|
|
882
1015
|
fi
|
|
883
1016
|
done
|
|
884
1017
|
|
|
@@ -899,7 +1032,7 @@ function doc_lint_script_comment() {
|
|
|
899
1032
|
# doc_comment_to_markdown src/reflection.sh docs/references.md
|
|
900
1033
|
# @SEE_ALSO
|
|
901
1034
|
# doc_lint_script_comment
|
|
902
|
-
|
|
1035
|
+
doc_comment_to_markdown() {
|
|
903
1036
|
local fromShellFile="$1"
|
|
904
1037
|
local toMarkdownFile="$2"
|
|
905
1038
|
|
|
@@ -918,7 +1051,7 @@ function doc_comment_to_markdown() {
|
|
|
918
1051
|
string_replace_regex '^(\*\*)' "- \1"
|
|
919
1052
|
)"
|
|
920
1053
|
|
|
921
|
-
|
|
1054
|
+
printf '%b\n' "@NAME\n${md//${NEW_LINE_SED}/\\n}" | # NEW_LINE_SED is not compatible by pandoc
|
|
922
1055
|
string_replace_regex '@([A-Z]+)' "##### \1" >"${toMarkdownFile}"
|
|
923
1056
|
}
|
|
924
1057
|
|
|
@@ -934,9 +1067,9 @@ LOG_LEVEL=${LOG_LEVEL:-$LOG_LEVEL_INFO} # the default value INFO will be used if
|
|
|
934
1067
|
# print_info "my message"
|
|
935
1068
|
# @SEE_ALSO
|
|
936
1069
|
# print_header, print_error, print_success, print_warn, print_args, print_info
|
|
937
|
-
|
|
1070
|
+
print_debug() {
|
|
938
1071
|
if [[ $LOG_LEVEL -le $LOG_LEVEL_DEBUG ]]; then
|
|
939
|
-
|
|
1072
|
+
printf '%b\n' "${COLOR_GRAY}DEBUG: ${1-$(cat)}${COLOR_END}"
|
|
940
1073
|
fi
|
|
941
1074
|
}
|
|
942
1075
|
|
|
@@ -950,9 +1083,9 @@ function print_debug() {
|
|
|
950
1083
|
# print_info "my message"
|
|
951
1084
|
# @SEE_ALSO
|
|
952
1085
|
# print_header, print_error, print_success, print_warn, print_args, print_debug
|
|
953
|
-
|
|
1086
|
+
print_info() {
|
|
954
1087
|
if [[ $LOG_LEVEL -le $LOG_LEVEL_INFO ]]; then
|
|
955
|
-
|
|
1088
|
+
printf '%b\n' "${COLOR_END}INFO: ${1-$(cat)}"
|
|
956
1089
|
fi
|
|
957
1090
|
}
|
|
958
1091
|
|
|
@@ -966,9 +1099,9 @@ function print_info() {
|
|
|
966
1099
|
# print_warn "my message"
|
|
967
1100
|
# @SEE_ALSO
|
|
968
1101
|
# print_header, print_error, print_success, print_info, print_args, print_debug
|
|
969
|
-
|
|
1102
|
+
print_warn() {
|
|
970
1103
|
if [[ $LOG_LEVEL -le $LOG_LEVEL_WARN ]]; then
|
|
971
|
-
|
|
1104
|
+
printf '%b\n' "${COLOR_BOLD_YELLOW}WARN: ${1-$(cat)} ${COLOR_END}"
|
|
972
1105
|
fi
|
|
973
1106
|
}
|
|
974
1107
|
|
|
@@ -982,9 +1115,9 @@ function print_warn() {
|
|
|
982
1115
|
# print_error "my error message"
|
|
983
1116
|
# @SEE_ALSO
|
|
984
1117
|
# print_header, print_success, print_warn, print_info, print_args, print_debug
|
|
985
|
-
|
|
1118
|
+
print_error() {
|
|
986
1119
|
if [[ $LOG_LEVEL -le $LOG_LEVEL_ERROR ]]; then
|
|
987
|
-
|
|
1120
|
+
printf '%b\n' "${COLOR_BOLD_RED}ERROR: ${1-$(cat)} ${COLOR_END}"
|
|
988
1121
|
fi
|
|
989
1122
|
}
|
|
990
1123
|
|
|
@@ -998,9 +1131,9 @@ function print_error() {
|
|
|
998
1131
|
# print_success "my message"
|
|
999
1132
|
# @SEE_ALSO
|
|
1000
1133
|
# print_header, print_error, print_warn, print_info, print_args, print_debug
|
|
1001
|
-
|
|
1134
|
+
print_success() {
|
|
1002
1135
|
if [[ $LOG_LEVEL -le $LOG_LEVEL_WARN ]]; then
|
|
1003
|
-
|
|
1136
|
+
printf '%b\n' "${COLOR_BOLD_GREEN}OK: ${1-$(cat)} ${COLOR_END}"
|
|
1004
1137
|
fi
|
|
1005
1138
|
}
|
|
1006
1139
|
|
|
@@ -1016,12 +1149,23 @@ function print_success() {
|
|
|
1016
1149
|
# print_args var1 var2
|
|
1017
1150
|
# @SEE_ALSO
|
|
1018
1151
|
# print_header, print_error, print_success, print_warn, print_info, print_debug
|
|
1019
|
-
|
|
1152
|
+
print_args() {
|
|
1020
1153
|
if [[ $LOG_LEVEL -le $LOG_LEVEL_WARN ]]; then
|
|
1021
1154
|
local varName varValue varValueOutput
|
|
1022
1155
|
for varName in "$@"; do
|
|
1023
|
-
|
|
1024
|
-
|
|
1156
|
+
eval 'varValue="${'"$varName"'-}"'
|
|
1157
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
1158
|
+
# Historical Bash print_args used `eval echo $name`, which
|
|
1159
|
+
# normalized whitespace and echo options in displayed values.
|
|
1160
|
+
# Echo expanded DATA, never eval it as new shell source.
|
|
1161
|
+
# shellcheck disable=SC2086,SC2116
|
|
1162
|
+
varValue="$(echo $varValue)"
|
|
1163
|
+
fi
|
|
1164
|
+
if [[ -z "$varValue" ]]; then
|
|
1165
|
+
varValueOutput="<EMPTY>"
|
|
1166
|
+
else
|
|
1167
|
+
varValueOutput="${COLOR_BLUE}${varValue}${COLOR_END}"
|
|
1168
|
+
fi
|
|
1025
1169
|
printf "%-30.30s%s\n" "${varName}:" "${varValueOutput}"
|
|
1026
1170
|
done
|
|
1027
1171
|
fi
|
|
@@ -1037,9 +1181,9 @@ function print_args() {
|
|
|
1037
1181
|
# print_header "My header1"
|
|
1038
1182
|
# @SEE_ALSO
|
|
1039
1183
|
# print_error, print_success, print_warn, print_info, print_args, print_debug
|
|
1040
|
-
|
|
1184
|
+
print_header() {
|
|
1041
1185
|
if [[ $LOG_LEVEL -le $LOG_LEVEL_ERROR ]]; then
|
|
1042
|
-
|
|
1186
|
+
printf '%b\n' "${COLOR_BOLD_MAGENTA}\n### ${1-$(cat)} ${COLOR_END}"
|
|
1043
1187
|
fi
|
|
1044
1188
|
}
|
|
1045
1189
|
|
|
@@ -1053,7 +1197,7 @@ function print_header() {
|
|
|
1053
1197
|
# prc_filter_by_port 9090
|
|
1054
1198
|
# @SEE_ALSO
|
|
1055
1199
|
# prc_kill_by_port
|
|
1056
|
-
|
|
1200
|
+
prc_filter_by_port() {
|
|
1057
1201
|
if [ $# -eq 0 ]; then
|
|
1058
1202
|
lsof -iTCP -sTCP:LISTEN -n -P
|
|
1059
1203
|
elif [ $# -eq 1 ]; then
|
|
@@ -1072,7 +1216,7 @@ function prc_filter_by_port() {
|
|
|
1072
1216
|
# prc_kill_by_port 9090
|
|
1073
1217
|
# @SEE_ALSO
|
|
1074
1218
|
# prc_filter_by_port
|
|
1075
|
-
|
|
1219
|
+
prc_kill_by_port() {
|
|
1076
1220
|
local port="$1"
|
|
1077
1221
|
local signal="${2-15}"
|
|
1078
1222
|
|
|
@@ -1095,7 +1239,7 @@ function prc_kill_by_port() {
|
|
|
1095
1239
|
# prc_filter_by_cmd node
|
|
1096
1240
|
# @SEE_ALSO
|
|
1097
1241
|
# prc_kill_by_cmd
|
|
1098
|
-
|
|
1242
|
+
prc_filter_by_cmd() {
|
|
1099
1243
|
if [ $# -eq 0 ]; then
|
|
1100
1244
|
ps
|
|
1101
1245
|
elif [ $# -eq 1 ]; then
|
|
@@ -1114,7 +1258,7 @@ function prc_filter_by_cmd() {
|
|
|
1114
1258
|
# prc_kill_by_cmd my-app
|
|
1115
1259
|
# @SEE_ALSO
|
|
1116
1260
|
# prc_filter_by_cmd
|
|
1117
|
-
|
|
1261
|
+
prc_kill_by_cmd() {
|
|
1118
1262
|
local cmd="$1"
|
|
1119
1263
|
local signal="${2-15}"
|
|
1120
1264
|
|
|
@@ -1140,15 +1284,50 @@ function prc_kill_by_cmd() {
|
|
|
1140
1284
|
# string="args_valid_or_read myVar '^[0-9a-z]{3,3}$' \"SIA\""
|
|
1141
1285
|
# reflect_nth_arg 4 $string ==> "SIA"
|
|
1142
1286
|
# @SEE_ALSO
|
|
1143
|
-
|
|
1144
|
-
local index string
|
|
1287
|
+
reflect_nth_arg() {
|
|
1288
|
+
local index string word position
|
|
1145
1289
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1290
|
+
if [[ -n "${ZSH_VERSION:-}" ]]; then
|
|
1291
|
+
# zsh's lexer splits shell-quoted words without executing their contents.
|
|
1292
|
+
# Keep this expansion in eval so Bash can still parse the source file.
|
|
1293
|
+
string="$*"
|
|
1294
|
+
local -a __bash_base_reflect_words
|
|
1295
|
+
eval '__bash_base_reflect_words=( "${(@Q)${(z)string}}" )' || return 1
|
|
1296
|
+
set -- "${__bash_base_reflect_words[@]}"
|
|
1297
|
+
else
|
|
1298
|
+
string="$(printf '%s\n' "$*" | string_replace_regex '(\\|\||\{|>|<|&)' '\\\1')"
|
|
1299
|
+
|
|
1300
|
+
# Parse the same shell-like argument string as the historical implementation,
|
|
1301
|
+
# but use positional parameters instead of an indexed array so Bash/zsh array
|
|
1302
|
+
# index bases cannot change the result.
|
|
1303
|
+
eval "set -- ${string}"
|
|
1304
|
+
fi
|
|
1305
|
+
index="${1-}"
|
|
1306
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
1307
|
+
# Bash legacy indexing addressed the entire parsed list, including
|
|
1308
|
+
# the index argument at [0]; missing indexes printed a blank line.
|
|
1309
|
+
# Keep that behavior, including Bash's native negative indexes.
|
|
1310
|
+
local -a __bash_base_legacy_reflect_words
|
|
1311
|
+
__bash_base_legacy_reflect_words=("$@")
|
|
1312
|
+
echo "${__bash_base_legacy_reflect_words[$index]}"
|
|
1313
|
+
return 0
|
|
1314
|
+
fi
|
|
1315
|
+
shift || true
|
|
1316
|
+
|
|
1317
|
+
if ! [[ "$index" =~ ^[0-9]+$ ]] || ((index < 1)); then
|
|
1318
|
+
return 1
|
|
1319
|
+
fi
|
|
1320
|
+
|
|
1321
|
+
position=1
|
|
1322
|
+
for word in "$@"; do
|
|
1323
|
+
if ((position == index)); then
|
|
1324
|
+
printf '%s\n' "$word"
|
|
1325
|
+
return 0
|
|
1326
|
+
fi
|
|
1327
|
+
position=$((position + 1))
|
|
1328
|
+
done
|
|
1149
1329
|
|
|
1150
|
-
|
|
1151
|
-
echo "${args[$index]}"
|
|
1330
|
+
return 1
|
|
1152
1331
|
}
|
|
1153
1332
|
|
|
1154
1333
|
# @NAME
|
|
@@ -1161,9 +1340,9 @@ function reflect_nth_arg() {
|
|
|
1161
1340
|
# reflect_get_function_definition confirm_to_continue
|
|
1162
1341
|
# @SEE_ALSO
|
|
1163
1342
|
# reflect_function_names_of_file
|
|
1164
|
-
|
|
1343
|
+
reflect_get_function_definition() {
|
|
1165
1344
|
local functionName="$1"
|
|
1166
|
-
|
|
1345
|
+
typeset -f "$functionName"
|
|
1167
1346
|
}
|
|
1168
1347
|
|
|
1169
1348
|
# @NAME
|
|
@@ -1177,9 +1356,14 @@ function reflect_get_function_definition() {
|
|
|
1177
1356
|
# reflect_function_names_of_file scripts/my_script.sh
|
|
1178
1357
|
# @SEE_ALSO
|
|
1179
1358
|
# reflect_get_function_definition
|
|
1180
|
-
|
|
1359
|
+
reflect_function_names_of_file() {
|
|
1181
1360
|
local shellScriptFile="$1"
|
|
1182
|
-
|
|
1361
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
1362
|
+
# Existing Bash callers may rely on the original output spelling.
|
|
1363
|
+
grep -E '^[[:space:]]*(function)?[[:space:]]*[0-9A-Za-z_\-]+[[:space:]]*\(\)[[:space:]]*\{?' "$shellScriptFile" | cut -d'(' -f1 | sed -e 's/function//'
|
|
1364
|
+
return
|
|
1365
|
+
fi
|
|
1366
|
+
grep -E '^[[:space:]]*(function[[:space:]]+)?[0-9A-Za-z_-]+[[:space:]]*\(\)[[:space:]]*\{?' "$shellScriptFile" | sed -E -e 's/^[[:space:]]*function[[:space:]]+//' -e 's/[[:space:]]*\(\).*//' -e 's/[[:space:]]+$//'
|
|
1183
1367
|
}
|
|
1184
1368
|
|
|
1185
1369
|
# @NAME
|
|
@@ -1193,9 +1377,13 @@ function reflect_function_names_of_file() {
|
|
|
1193
1377
|
# reflect_function_definitions_of_file scripts/my_script.sh
|
|
1194
1378
|
# @SEE_ALSO
|
|
1195
1379
|
# reflect_get_function_definition
|
|
1196
|
-
|
|
1380
|
+
reflect_function_definitions_of_file() {
|
|
1197
1381
|
local shellScriptFile="$1"
|
|
1198
|
-
|
|
1382
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
1383
|
+
sed -E -n '/^[[:space:]]*(function)?[[:space:]]*[0-9A-Za-z_\-]+[[:space:]]*\(\)[[:space:]]*\{?/,/^[[:space:]]*}/p' "$shellScriptFile"
|
|
1384
|
+
return
|
|
1385
|
+
fi
|
|
1386
|
+
sed -E -n '/^[[:space:]]*(function[[:space:]]+)?[0-9A-Za-z_-]+[[:space:]]*\(\)[[:space:]]*\{?/,/^[[:space:]]*}/p' "$shellScriptFile"
|
|
1199
1387
|
}
|
|
1200
1388
|
|
|
1201
1389
|
# @NAME
|
|
@@ -1209,9 +1397,17 @@ function reflect_function_definitions_of_file() {
|
|
|
1209
1397
|
# reflect_search_function '^args_.*'
|
|
1210
1398
|
# @SEE_ALSO
|
|
1211
1399
|
# reflect_search_variable
|
|
1212
|
-
|
|
1400
|
+
reflect_search_function() {
|
|
1213
1401
|
local functionNamePattern="$1"
|
|
1214
|
-
|
|
1402
|
+
|
|
1403
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
1404
|
+
# Exact Bash legacy extraction/output (no new sorting or dedupe).
|
|
1405
|
+
typeset -f | grep -E '\s+\(\)\s+' | sed -E 's/[(){ ]//g' | grep -Ei "$functionNamePattern"
|
|
1406
|
+
elif [[ -n "${ZSH_VERSION:-}" ]]; then
|
|
1407
|
+
# Hide zsh-only parameter-expansion flags inside eval so the file still
|
|
1408
|
+
# parses in Bash.
|
|
1409
|
+
eval 'printf "%s\\n" "${(k)functions[@]}"' | LC_ALL=C sort -u | grep -Ei "$functionNamePattern"
|
|
1410
|
+
fi
|
|
1215
1411
|
}
|
|
1216
1412
|
|
|
1217
1413
|
# @NAME
|
|
@@ -1225,9 +1421,16 @@ function reflect_search_function() {
|
|
|
1225
1421
|
# reflect_search_variable '^COLOR'
|
|
1226
1422
|
# @SEE_ALSO
|
|
1227
1423
|
# reflect_search_function
|
|
1228
|
-
|
|
1424
|
+
reflect_search_variable() {
|
|
1229
1425
|
local variableNamePattern="$1"
|
|
1230
|
-
|
|
1426
|
+
|
|
1427
|
+
if [[ -n "${BASH_VERSION:-}" ]]; then
|
|
1428
|
+
# Enumerate Bash variables directly. Parsing typeset -p using \s/\w
|
|
1429
|
+
# depends on grep extensions unavailable on some macOS versions.
|
|
1430
|
+
compgen -A variable | grep -Ei "$variableNamePattern"
|
|
1431
|
+
elif [[ -n "${ZSH_VERSION:-}" ]]; then
|
|
1432
|
+
eval 'printf "%s\\n" "${(k)parameters[@]}"' | LC_ALL=C sort -u | grep -Ei "$variableNamePattern"
|
|
1433
|
+
fi
|
|
1231
1434
|
}
|
|
1232
1435
|
|
|
1233
1436
|
# @NAME
|
|
@@ -1241,7 +1444,7 @@ function reflect_search_variable() {
|
|
|
1241
1444
|
# string_trim < logfile
|
|
1242
1445
|
# echo " add " | string_trim
|
|
1243
1446
|
# @SEE_ALSO
|
|
1244
|
-
|
|
1447
|
+
string_trim() {
|
|
1245
1448
|
local string="${1-$(cat)}"
|
|
1246
1449
|
echo "${string}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' #trim ' '
|
|
1247
1450
|
}
|
|
@@ -1257,7 +1460,7 @@ function string_trim() {
|
|
|
1257
1460
|
# string_repeat 'abc' 5
|
|
1258
1461
|
# echo 5 | string_repeat 'abc'
|
|
1259
1462
|
# @SEE_ALSO
|
|
1260
|
-
|
|
1463
|
+
string_repeat() {
|
|
1261
1464
|
local string="$1"
|
|
1262
1465
|
local nbTimes=${2-$(cat)}
|
|
1263
1466
|
printf "${string}%.0s" $(seq 1 "$nbTimes")
|
|
@@ -1274,7 +1477,7 @@ function string_repeat() {
|
|
|
1274
1477
|
# string_length < logfile
|
|
1275
1478
|
# echo " add " | string_length
|
|
1276
1479
|
# @SEE_ALSO
|
|
1277
|
-
|
|
1480
|
+
string_length() {
|
|
1278
1481
|
local string index
|
|
1279
1482
|
string="${1-$(cat)}"
|
|
1280
1483
|
|
|
@@ -1294,7 +1497,7 @@ function string_length() {
|
|
|
1294
1497
|
# echo " add " | string_is_empty
|
|
1295
1498
|
# @SEE_ALSO
|
|
1296
1499
|
# string_length
|
|
1297
|
-
|
|
1500
|
+
string_is_empty() {
|
|
1298
1501
|
local string="${1-$(cat)}"
|
|
1299
1502
|
|
|
1300
1503
|
[[ $(string_length "$string") -eq 0 ]]
|
|
@@ -1310,7 +1513,7 @@ function string_is_empty() {
|
|
|
1310
1513
|
# string_revert 'aBc'
|
|
1311
1514
|
# echo 'aBc' | string_revert
|
|
1312
1515
|
# @SEE_ALSO
|
|
1313
|
-
|
|
1516
|
+
string_revert() {
|
|
1314
1517
|
local string="${1-$(cat)}"
|
|
1315
1518
|
echo "${string}" | rev
|
|
1316
1519
|
}
|
|
@@ -1326,9 +1529,16 @@ function string_revert() {
|
|
|
1326
1529
|
# echo 'abc' | string_upper
|
|
1327
1530
|
# @SEE_ALSO
|
|
1328
1531
|
# string_upper_first, string_lower
|
|
1329
|
-
|
|
1532
|
+
string_upper() {
|
|
1330
1533
|
local string=${1-$(cat)}
|
|
1331
|
-
|
|
1534
|
+
if [[ -n "${BASH_VERSION:-}" ]] && ((BASH_VERSINFO[0] >= 4)); then
|
|
1535
|
+
# Bash's case expansion is locale-aware (unlike many tr versions).
|
|
1536
|
+
# Keep the Bash 4+/5+ echo behavior verbatim. Construct the expression
|
|
1537
|
+
# at runtime so Bash 3.2 can source this file without evaluating it.
|
|
1538
|
+
eval 'echo "${string'"^^"'}"'
|
|
1539
|
+
return
|
|
1540
|
+
fi
|
|
1541
|
+
printf '%s\n' "$string" | tr '[:lower:]' '[:upper:]'
|
|
1332
1542
|
}
|
|
1333
1543
|
|
|
1334
1544
|
# @NAME
|
|
@@ -1342,9 +1552,13 @@ function string_upper() {
|
|
|
1342
1552
|
# echo 'aBc' | string_lower
|
|
1343
1553
|
# @SEE_ALSO
|
|
1344
1554
|
# string_upper, string_upper_first
|
|
1345
|
-
|
|
1555
|
+
string_lower() {
|
|
1346
1556
|
local string=${1-$(cat)}
|
|
1347
|
-
|
|
1557
|
+
if [[ -n "${BASH_VERSION:-}" ]] && ((BASH_VERSINFO[0] >= 4)); then
|
|
1558
|
+
eval 'echo "${string'",,"'}"'
|
|
1559
|
+
return
|
|
1560
|
+
fi
|
|
1561
|
+
printf '%s\n' "$string" | tr '[:upper:]' '[:lower:]'
|
|
1348
1562
|
}
|
|
1349
1563
|
|
|
1350
1564
|
# @NAME
|
|
@@ -1358,10 +1572,29 @@ function string_lower() {
|
|
|
1358
1572
|
# echo 'aBc' | string_upper_first
|
|
1359
1573
|
# @SEE_ALSO
|
|
1360
1574
|
# string_lower, string_upper
|
|
1361
|
-
|
|
1575
|
+
string_upper_first() {
|
|
1362
1576
|
local string=${1-$(cat)}
|
|
1363
|
-
local lower
|
|
1364
|
-
|
|
1577
|
+
local lower first rest
|
|
1578
|
+
|
|
1579
|
+
if [[ -n "${BASH_VERSION:-}" ]] && ((BASH_VERSINFO[0] >= 4)); then
|
|
1580
|
+
# Do not round-trip through command substitution: it strips the
|
|
1581
|
+
# trailing newlines that the old Bash parameter expansion preserved.
|
|
1582
|
+
eval 'lower="${string'",,"'}"'
|
|
1583
|
+
eval 'echo "${lower'"^"'}"'
|
|
1584
|
+
return
|
|
1585
|
+
fi
|
|
1586
|
+
|
|
1587
|
+
lower="$(string_lower "$string")"
|
|
1588
|
+
[[ -n "$lower" ]] || {
|
|
1589
|
+
printf '\n'
|
|
1590
|
+
return 0
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
# `${var^^}` / `${var,,}` / `${var^}` are Bash-specific. Parameter
|
|
1594
|
+
# prefix/suffix removal is shared by Bash and zsh.
|
|
1595
|
+
rest="${lower#?}"
|
|
1596
|
+
first="${lower%"$rest"}"
|
|
1597
|
+
printf '%s%s\n' "$(string_upper "$first")" "$rest"
|
|
1365
1598
|
}
|
|
1366
1599
|
|
|
1367
1600
|
# @NAME
|
|
@@ -1377,11 +1610,37 @@ function string_upper_first() {
|
|
|
1377
1610
|
# string_sub 3 5 < temp_file.txt
|
|
1378
1611
|
# echo ' as fd ' | string_sub 2 4
|
|
1379
1612
|
# @SEE_ALSO
|
|
1380
|
-
|
|
1613
|
+
string_sub() {
|
|
1381
1614
|
local startIndex=$1
|
|
1382
1615
|
local subStringLength=$2
|
|
1383
1616
|
local string="${3-$(cat)}"
|
|
1384
|
-
|
|
1617
|
+
if [[ -n "${BASH_VERSION:-}" ]] && ((BASH_VERSINFO[0] >= 4)); then
|
|
1618
|
+
# Keep Bash 4+/5+ substring and error behavior exactly as before.
|
|
1619
|
+
# Bash 3.2 continues to use the portable negative-length fallback.
|
|
1620
|
+
echo "${string:$startIndex:$subStringLength}"
|
|
1621
|
+
return
|
|
1622
|
+
fi
|
|
1623
|
+
local totalLength=${#string}
|
|
1624
|
+
local actualStart=$startIndex
|
|
1625
|
+
local actualLength=$subStringLength
|
|
1626
|
+
|
|
1627
|
+
# Bash 3.2 cannot use a negative substring length. Convert the
|
|
1628
|
+
# documented negative start/end offsets to nonnegative bounds first.
|
|
1629
|
+
if ((actualStart < 0)); then
|
|
1630
|
+
actualStart=$((totalLength + actualStart))
|
|
1631
|
+
fi
|
|
1632
|
+
if ((actualLength < 0)); then
|
|
1633
|
+
actualLength=$((totalLength + actualLength - actualStart))
|
|
1634
|
+
fi
|
|
1635
|
+
# Out-of-range starts produce an empty result (as in modern Bash).
|
|
1636
|
+
if ((actualStart < 0 || actualStart > totalLength)); then
|
|
1637
|
+
printf '\n'
|
|
1638
|
+
return 0
|
|
1639
|
+
fi
|
|
1640
|
+
if ((actualLength < 0)); then
|
|
1641
|
+
return 1
|
|
1642
|
+
fi
|
|
1643
|
+
printf '%s\n' "${string:$actualStart:$actualLength}"
|
|
1385
1644
|
}
|
|
1386
1645
|
|
|
1387
1646
|
# @NAME
|
|
@@ -1395,7 +1654,7 @@ function string_sub() {
|
|
|
1395
1654
|
# string_match 'name;+' "name;name;"
|
|
1396
1655
|
# @SEE_ALSO
|
|
1397
1656
|
# string_index_first
|
|
1398
|
-
|
|
1657
|
+
string_match() {
|
|
1399
1658
|
local value regExp
|
|
1400
1659
|
regExp=${1}
|
|
1401
1660
|
string="${2-$(cat)}"
|
|
@@ -1413,10 +1672,14 @@ function string_match() {
|
|
|
1413
1672
|
# escape_sed 'a$'
|
|
1414
1673
|
# @SEE_ALSO
|
|
1415
1674
|
# string_replace
|
|
1416
|
-
|
|
1417
|
-
|
|
1675
|
+
escape_sed() {
|
|
1676
|
+
if [[ -n "${ZSH_VERSION:-}" ]]; then
|
|
1677
|
+
printf '%s\n' "$1"
|
|
1678
|
+
else
|
|
1679
|
+
# Preserve historical Bash echo semantics.
|
|
1680
|
+
echo "$1"
|
|
1681
|
+
fi | sed -e 's/\//\\\//g' -e 's/\&/\\\&/g' -e 's/\./\\\./g' -e 's/\^/\\\^/g' -e 's/\[/\\\[/g' -e 's/\$/\\\$/g'
|
|
1418
1682
|
}
|
|
1419
|
-
export -f escape_sed >/dev/null
|
|
1420
1683
|
|
|
1421
1684
|
# @NAME
|
|
1422
1685
|
# string_replace -- replace literally the token string to new string, not support regular expression
|
|
@@ -1432,11 +1695,16 @@ export -f escape_sed >/dev/null
|
|
|
1432
1695
|
# string_replace '\*' 'b' 'a*a' ==> 'aba'
|
|
1433
1696
|
# @SEE_ALSO
|
|
1434
1697
|
# escape_sed, string_replace_regex
|
|
1435
|
-
|
|
1698
|
+
string_replace() {
|
|
1436
1699
|
local tokenString newString
|
|
1437
1700
|
tokenString=$(escape_sed "${1}")
|
|
1438
1701
|
newString=$(escape_sed "${2}")
|
|
1439
|
-
|
|
1702
|
+
if [[ -n "${ZSH_VERSION:-}" ]]; then
|
|
1703
|
+
printf '%s\n' "${3-$(cat)}" | sed -e "s/${tokenString}/${newString}/g"
|
|
1704
|
+
else
|
|
1705
|
+
# Preserve historical Bash echo semantics.
|
|
1706
|
+
echo "${3-$(cat)}" | sed -e "s/${tokenString}/${newString}/g"
|
|
1707
|
+
fi
|
|
1440
1708
|
}
|
|
1441
1709
|
|
|
1442
1710
|
# @NAME
|
|
@@ -1453,7 +1721,7 @@ function string_replace() {
|
|
|
1453
1721
|
# string_replace_regex '*' 'b' 'a*a' ==> 'aba'
|
|
1454
1722
|
# @SEE_ALSO
|
|
1455
1723
|
# string_replace
|
|
1456
|
-
|
|
1724
|
+
string_replace_regex() {
|
|
1457
1725
|
echo "${3-$(cat)}" | sed -E -e "s/$1/$2/g"
|
|
1458
1726
|
}
|
|
1459
1727
|
|
|
@@ -1470,11 +1738,11 @@ function string_replace_regex() {
|
|
|
1470
1738
|
# echo " add " | string_index_first "token"
|
|
1471
1739
|
# @SEE_ALSO
|
|
1472
1740
|
# string_before_first, string_after_first
|
|
1473
|
-
|
|
1741
|
+
string_index_first() {
|
|
1474
1742
|
local tokenString=$1
|
|
1475
1743
|
local string="${2-$(cat)}"
|
|
1476
1744
|
local prefix="${string%%${tokenString}*}"
|
|
1477
|
-
[ "${string}" == "${prefix}" ] &&
|
|
1745
|
+
[[ "${string}" == "${prefix}" ]] && printf '%s\n' -1 || printf '%s\n' "${#prefix}"
|
|
1478
1746
|
}
|
|
1479
1747
|
|
|
1480
1748
|
# @NAME
|
|
@@ -1490,7 +1758,7 @@ function string_index_first() {
|
|
|
1490
1758
|
# echo " add " | string_before_first "dd"
|
|
1491
1759
|
# @SEE_ALSO
|
|
1492
1760
|
# string_index_first, string_after_first
|
|
1493
|
-
|
|
1761
|
+
string_before_first() {
|
|
1494
1762
|
local tokenString=$1
|
|
1495
1763
|
local string="${2-$(cat)}"
|
|
1496
1764
|
echo "${string%%${tokenString}*}" # Remove the first - and everything following it
|
|
@@ -1509,7 +1777,7 @@ function string_before_first() {
|
|
|
1509
1777
|
# echo " add " | string_after_first "dd"
|
|
1510
1778
|
# @SEE_ALSO
|
|
1511
1779
|
# string_index_first, string_before_first
|
|
1512
|
-
|
|
1780
|
+
string_after_first() {
|
|
1513
1781
|
local tokenString=$1
|
|
1514
1782
|
local string="${2-$(cat)}"
|
|
1515
1783
|
echo "${string#*${tokenString}}" # Remove everything up to and including first -
|
|
@@ -1531,7 +1799,7 @@ function string_after_first() {
|
|
|
1531
1799
|
# string_split_to_array $'\n' branchesToSelectArray "${branchesToSelectString}"
|
|
1532
1800
|
# @SEE_ALSO
|
|
1533
1801
|
# array_join, array_describe, array_from_describe, string_pick_to_array
|
|
1534
|
-
|
|
1802
|
+
string_split_to_array() {
|
|
1535
1803
|
local tokenString="$1"
|
|
1536
1804
|
local newArrayVarName="$2"
|
|
1537
1805
|
local string="${3-$(cat)}"
|
|
@@ -1569,7 +1837,7 @@ function string_split_to_array() {
|
|
|
1569
1837
|
# string_pick_to_array '{age:' '}' newArray "$str"
|
|
1570
1838
|
# @SEE_ALSO
|
|
1571
1839
|
# array_join, array_describe, array_from_describe, string_split_to_array
|
|
1572
|
-
|
|
1840
|
+
string_pick_to_array() {
|
|
1573
1841
|
local startTokenString="$1"
|
|
1574
1842
|
local endTokenString="$2"
|
|
1575
1843
|
local newArrayVarName="$3"
|
|
@@ -1605,7 +1873,7 @@ function string_pick_to_array() {
|
|
|
1605
1873
|
# stop_if_failed "ERROR: can't delete the directory '${destProjectPath}' !"
|
|
1606
1874
|
# @SEE_ALSO
|
|
1607
1875
|
# confirm_to_continue
|
|
1608
|
-
|
|
1876
|
+
stop_if_failed() {
|
|
1609
1877
|
if [[ $? -ne 0 ]]; then
|
|
1610
1878
|
print_error "$*"
|
|
1611
1879
|
exit 1
|
|
@@ -1624,19 +1892,25 @@ function stop_if_failed() {
|
|
|
1624
1892
|
# confirm_to_continue a b
|
|
1625
1893
|
# @SEE_ALSO
|
|
1626
1894
|
# print_args, stop_if_failed
|
|
1627
|
-
|
|
1895
|
+
confirm_to_continue() {
|
|
1628
1896
|
local response
|
|
1629
1897
|
print_args "$@"
|
|
1630
|
-
if ! [ "${modeQuiet}" == true ]; then
|
|
1631
|
-
read -r -p "Continue ? [y/N] " response
|
|
1632
1898
|
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1899
|
+
if [[ "${modeQuiet:-false}" != true ]]; then
|
|
1900
|
+
# Bash `read -p` and zsh `read "name?prompt"` are shell-specific.
|
|
1901
|
+
# Match Bash read -p behavior by showing the prompt only for a TTY.
|
|
1902
|
+
if [[ -t 0 ]]; then
|
|
1903
|
+
printf '%s' 'Continue ? [y/N] ' >&2
|
|
1904
|
+
fi
|
|
1905
|
+
read -r response || response=""
|
|
1906
|
+
|
|
1907
|
+
case "$response" in
|
|
1908
|
+
[yY] | [yY][eE][sS])
|
|
1909
|
+
printf '%s\n' 'Continue...'
|
|
1910
|
+
sleep 1
|
|
1637
1911
|
;;
|
|
1638
1912
|
*)
|
|
1639
|
-
|
|
1913
|
+
printf '%s\n' 'Exiting...'
|
|
1640
1914
|
exit 1
|
|
1641
1915
|
;;
|
|
1642
1916
|
esac
|
|
@@ -1654,8 +1928,8 @@ function confirm_to_continue() {
|
|
|
1654
1928
|
# @EXAMPLES
|
|
1655
1929
|
# wait_for 'test -f /tmp/output.txt' 'file existed' 3
|
|
1656
1930
|
# @SEE_ALSO
|
|
1657
|
-
# confirm_to_continue, stop_if_failed
|
|
1658
|
-
|
|
1931
|
+
# confirm_to_continue, stop_if_failed
|
|
1932
|
+
wait_for() {
|
|
1659
1933
|
local predicate="${1}"
|
|
1660
1934
|
local subject="${2-${predicate}}"
|
|
1661
1935
|
local interval=${3-3}
|
|
@@ -1681,6 +1955,16 @@ function wait_for() {
|
|
|
1681
1955
|
# record2
|
|
1682
1956
|
# EOF
|
|
1683
1957
|
# @SEE_ALSO
|
|
1684
|
-
|
|
1685
|
-
|
|
1958
|
+
declare_heredoc() {
|
|
1959
|
+
local _bash_base_heredoc_target="${1-}"
|
|
1960
|
+
local _bash_base_heredoc_content
|
|
1961
|
+
|
|
1962
|
+
# Only identifier names may appear as the left-hand side of an eval.
|
|
1963
|
+
case "$_bash_base_heredoc_target" in
|
|
1964
|
+
'' | [!a-zA-Z_]* | *[!a-zA-Z0-9_]*) return 2 ;;
|
|
1965
|
+
esac
|
|
1966
|
+
|
|
1967
|
+
_bash_base_heredoc_content="$(cat)"
|
|
1968
|
+
# Expand the content as a variable value, never parse it as shell code.
|
|
1969
|
+
eval "${_bash_base_heredoc_target}=\"\$_bash_base_heredoc_content\""
|
|
1686
1970
|
}
|