-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathlib.sh
More file actions
executable file
·206 lines (189 loc) · 6.23 KB
/
Copy pathlib.sh
File metadata and controls
executable file
·206 lines (189 loc) · 6.23 KB
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
#!/usr/bin/env bash
# Shared helpers for the ./run command scripts.
#
# Sourced as a library by the command scripts in this directory, or executed
# directly to exercise a single helper:
# bash build_scripts/run_scripts/lib.sh require git python3
# Status/progress chatter, on stderr so stdout stays clean for command output.
info() { printf '%s\n' "$*" >&2; }
# Error message with a uniform prefix, on stderr.
error() { printf 'ERROR: %s\n' "$*" >&2; }
# Print a command line without running it, bold (like just's recipe echo)
# with a "$ " prefix. Goes to stderr so a command's stdout stays clean for
# its own output; escape codes only when stderr is a terminal and NO_COLOR
# is unset, so redirected output stays plain. Use directly when the command
# itself runs indirected (e.g. backgrounded with captured output).
log_cmd() {
if [ -t 2 ] && [ -z "${NO_COLOR:-}" ]; then
printf '\033[1m$ %s\033[0m\n' "$*" >&2
else
printf '$ %s\n' "$*" >&2
fi
}
# Print a command then execute it.
log_and_run() {
log_cmd "$@"
"$@"
}
run_var_name() {
# Print the RUN_<NAME> variable name for a tool (uppercased, dashes to
# underscores: uvx -> RUN_UVX). The single source of the require naming
# convention. LC_ALL=C pins ASCII range semantics regardless of locale.
printf 'RUN_%s' "$(printf '%s' "$1" | LC_ALL=C tr 'a-z-' 'A-Z_')"
}
require() {
# Check the given tools and resolve which executable to use for each.
# Takes bare tool names, not paths. A pre-set RUN_<NAME> env var (name
# uppercased, dashes to underscores: RUN_UVX for uvx, RUN_SHELLCHECK
# for shellcheck) is a user override and is the only candidate checked
# otherwise the tool must be on PATH. The winner is exported as
# RUN_<NAME> for callers to invoke and for delegated scripts to
# inherit. Failures are reported all at once.
# The name `python` is special: it delegates to _require_python, which
# version-gates the interpreter and reports its own detail.
local missing=() cmd var val failed=0
for cmd in "$@"; do
if [ "$cmd" = "python" ]; then
_require_python || failed=1
continue
fi
var="$(run_var_name "$cmd")"
val="${!var:-$cmd}"
if command -v "$val" >/dev/null 2>&1; then
export "$var=$val"
elif [ "$val" = "$cmd" ]; then
missing+=("$cmd")
else
missing+=("$cmd ($var=$val not found)")
fi
done
if [ "${#missing[@]}" -gt 0 ]; then
error 'missing required command(s):'
for cmd in "${missing[@]}"; do
info " $cmd"
done
info 'See build_scripts/run_scripts/README.md for installation instructions.'
return 1
fi
return "$failed"
}
_python_ok() {
# True if the given interpreter exists and is Python >= 3.11 (the
# version floor for the repo's build tooling, matching CI).
command -v "$1" >/dev/null 2>&1 &&
"$1" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' >/dev/null 2>&1
}
_require_python() {
# Locate a Python >= 3.11 and export it as RUN_PYTHON for commands to
# invoke. When RUN_PYTHON is already set (contributor override) it is
# the ONLY candidate checked, so a broken override fails loudly instead
# of silently falling back. Otherwise probe python3, python, then the
# Windows launcher (py -3, resolved to its underlying executable so
# RUN_PYTHON stays a single quotable word). On failure, reports the
# version of every candidate that was present.
local cand tried=""
if [ -n "${RUN_PYTHON:-}" ]; then
if _python_ok "$RUN_PYTHON"; then
export RUN_PYTHON
return 0
fi
error "RUN_PYTHON=$RUN_PYTHON is not a Python >= 3.11."
if command -v "$RUN_PYTHON" >/dev/null 2>&1; then
info " found: $("$RUN_PYTHON" --version 2>&1 || true)"
fi
info 'See build_scripts/run_scripts/README.md for installation instructions.'
return 1
fi
for cand in python3 python; do
if _python_ok "$cand"; then
RUN_PYTHON="$cand"
export RUN_PYTHON
return 0
fi
if command -v "$cand" >/dev/null 2>&1; then
tried="$tried
$cand: $("$cand" --version 2>&1 || true)"
fi
done
if command -v py >/dev/null 2>&1; then
cand="$(py -3 -c 'import sys; print(sys.executable)' 2>/dev/null || true)"
if [ -n "$cand" ] && _python_ok "$cand"; then
RUN_PYTHON="$cand"
export RUN_PYTHON
return 0
fi
tried="$tried
py -3: $(py -3 --version 2>&1 || true)"
fi
error "Python >= 3.11 is required and was not found."
if [ -n "$tried" ]; then
info "Candidates checked:$tried"
fi
info 'Set RUN_PYTHON to a suitable interpreter, or see build_scripts/run_scripts/README.md for installation instructions.'
return 1
}
print_resolved() {
# Print each tool name with the executable path its RUN_<TOOL> variable
# resolved to. Call require on the tools first.
local tool var
for tool in "$@"; do
var="$(run_var_name "$tool")"
printf ' %-11s %s\n' "$tool" "$(command -v "${!var}")"
done
}
dispatch() {
# dispatch <default_fn> [args...]: call the function named by the first
# remaining arg, or default_fn when none is given. Unknown names print
# the calling script's help and fail with a usage error.
local fn="${2:-$1}"
shift
if [ "$#" -gt 0 ]; then
shift
fi
if declare -F "$fn" >/dev/null; then
"$fn" "$@"
else
error "unknown command: $fn"
if declare -F help >/dev/null; then
help
fi
return 2
fi
}
banner_title() {
# Print only the first line of the #### banner block of the given script
# (used by ./run for its one-line-per-command index).
awk '
/^#{10,}/ { if (inIntro) exit; inIntro=1; next }
inIntro && /^#/ { gsub(/^# ?/, ""); print; exit }
' "$1"
}
banner_help() {
# Print the #### banner block at the top of the given script, unprefixed.
awk '
BEGIN { inIntro=0; introDone=0 }
/^#{10,}/ && introDone==0 && inIntro==0 { inIntro=1; next }
/^#{10,}/ && inIntro==1 { inIntro=0; introDone=1; print ""; next }
inIntro==1 { gsub(/^# ?/, ""); print }
' "$1"
}
command_help() {
# Print each function in the given script with its leading comment lines.
# Underscore-prefixed functions are internal and excluded.
printf 'Commands:\n'
awk '
/^[a-z][a-z_]* *\(\) *\{/ { fn=$1; sub(/\(\)$/, "", fn); inCmd=1; next }
inCmd==1 && /^[ \t]+#/ {
if (fn != "") { print " " fn; fn="" }
gsub(/^[ \t]+# ?/, " ")
print
next
}
inCmd==1 { inCmd=0 }
' "$1"
}
# Dispatch only when executed directly; stay silent when sourced.
if [[ ${BASH_SOURCE[0]} == "$0" ]]; then
set -euo pipefail
"$@"
fi