valise-sh 0.2.0 → 0.3.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +152 -2
  2. package/README.md +99 -146
  3. package/bin/valise +57 -5376
  4. package/docs/architecture.md +77 -121
  5. package/docs/contrib-modules.md +172 -218
  6. package/docs/installation.md +137 -83
  7. package/docs/module-abi.md +125 -126
  8. package/docs/projects.md +249 -0
  9. package/docs/release-policy.md +68 -93
  10. package/docs/shared-modules.md +40 -39
  11. package/examples/README.md +12 -11
  12. package/examples/e2e/README.md +59 -66
  13. package/examples/e2e/bundles.tsv +6 -0
  14. package/examples/e2e/module.d/e2e_aliases.meta +1 -2
  15. package/examples/e2e/module.d/e2e_completions_bash.meta +0 -1
  16. package/examples/e2e/module.d/e2e_completions_zsh.meta +0 -1
  17. package/examples/e2e/module.d/e2e_env_setup.meta +2 -3
  18. package/examples/e2e/module.d/e2e_fish_features.meta +0 -1
  19. package/examples/e2e/module.d/e2e_generated_config.meta +1 -2
  20. package/examples/e2e/module.d/e2e_ksh_features.meta +0 -1
  21. package/examples/e2e/module.d/e2e_layer_probe.meta +1 -2
  22. package/examples/e2e/module.d/e2e_optional_network.meta +1 -2
  23. package/examples/e2e/module.d/e2e_platform_tools.meta +8 -0
  24. package/examples/e2e/module.d/e2e_platform_tools_bsd.meta +2 -3
  25. package/examples/e2e/module.d/e2e_platform_tools_darwin.meta +2 -3
  26. package/examples/e2e/module.d/e2e_platform_tools_linux.meta +2 -3
  27. package/examples/e2e/valise.conf +4 -0
  28. package/examples/e2e/variants.tsv +3 -0
  29. package/examples/local/module.d/hello_valise.meta +1 -2
  30. package/examples/profile-project/README.md +78 -108
  31. package/examples/profile-project/bundles.tsv +8 -0
  32. package/examples/profile-project/{shell/layers → layers}/demo/module.d/contrib_demo_greeting.meta +1 -2
  33. package/examples/profile-project/{shell/module.d → module.d}/greeting.meta +1 -2
  34. package/examples/profile-project/{shell/module.d → module.d}/profile_main.meta +0 -1
  35. package/examples/profile-project/{shell/modules → modules}/greeting.sh +1 -1
  36. package/examples/profile-project/valise.conf +4 -0
  37. package/examples/profile-project/variants.tsv +3 -0
  38. package/examples/shared/README.md +13 -34
  39. package/examples/shared/module.d/contrib_shared_paths.meta +2 -3
  40. package/examples/shared/module.d/contrib_shared_umask.meta +1 -2
  41. package/examples/shared/modules/shared/contrib_shared_paths.sh +2 -2
  42. package/lib/capabilities.sh +40 -13
  43. package/lib/cli/commands.sh +278 -0
  44. package/lib/cli/common.sh +326 -0
  45. package/lib/cli/devtools.sh +539 -0
  46. package/lib/cli/fish-hook.sh +986 -0
  47. package/lib/cli/integration.sh +337 -0
  48. package/lib/cli/lifecycle.sh +578 -0
  49. package/lib/cli/project.sh +1807 -0
  50. package/lib/cli/store.sh +1023 -0
  51. package/lib/cli/usage.sh +308 -0
  52. package/lib/compiler.awk +11 -4
  53. package/lib/fish/loader.fish +69 -9
  54. package/lib/fish/metadata.fish +49 -5
  55. package/lib/fish/profile.fish +170 -0
  56. package/lib/loader.sh +6 -8
  57. package/lib/metadata.sh +111 -5
  58. package/manifests/parser-support.tsv +2 -2
  59. package/module.d/valise_editor.meta +9 -0
  60. package/module.d/valise_history.meta +9 -0
  61. package/module.d/valise_pager.meta +9 -0
  62. package/module.d/valise_path.meta +9 -0
  63. package/module.d/{platform_bsd.meta → valise_platform_bsd.meta} +3 -4
  64. package/module.d/{platform_linux.meta → valise_platform_linux.meta} +3 -4
  65. package/module.d/{platform_macos.meta → valise_platform_macos.meta} +3 -4
  66. package/module.d/{platform_posix.meta → valise_platform_posix.meta} +2 -3
  67. package/module.d/valise_xdg.meta +9 -0
  68. package/modules/system/{contrib_editor.sh → valise_editor.sh} +2 -2
  69. package/modules/system/{contrib_history.sh → valise_history.sh} +2 -2
  70. package/modules/system/{contrib_pager.sh → valise_pager.sh} +2 -2
  71. package/modules/system/{contrib_path.sh → valise_path.sh} +3 -3
  72. package/modules/system/{contrib_xdg.sh → valise_xdg.sh} +2 -2
  73. package/package.json +91 -10
  74. package/docs/variants.md +0 -327
  75. package/entrypoints/.gitkeep +0 -0
  76. package/entrypoints/fish/profile.fish +0 -99
  77. package/examples/e2e/build-manifest.sh +0 -138
  78. package/examples/local/.gitkeep +0 -0
  79. package/examples/profile-project/shell/release/bundle-roots +0 -9
  80. package/examples/profile-project/shell/release/install-variants +0 -3
  81. package/examples/profile-project/shell/release/module-registry.tsv +0 -4
  82. package/examples/profile-project/shell/release/project.conf +0 -3
  83. package/lib/.gitkeep +0 -0
  84. package/manifests/.gitkeep +0 -0
  85. package/module.d/.gitkeep +0 -0
  86. package/module.d/contrib_editor.meta +0 -10
  87. package/module.d/contrib_history.meta +0 -10
  88. package/module.d/contrib_pager.meta +0 -10
  89. package/module.d/contrib_path.meta +0 -10
  90. package/module.d/contrib_xdg.meta +0 -10
  91. package/modules/.gitkeep +0 -0
  92. /package/examples/profile-project/{shell/layers → layers}/demo/modules/shared/contrib_demo_greeting.sh +0 -0
@@ -0,0 +1,539 @@
1
+ # Copyright (c) 2026 Joseph Cumines
2
+ # SPDX-License-Identifier: MIT
3
+ # valise CLI: developer commands: env, lint, test, and run. test and run load
4
+ # modules from a throwaway release built beside the project, so they exercise
5
+ # the same plans an installed profile uses.
6
+ # Sourced by bin/valise; not executable on its own.
7
+
8
+ _dev_tmp=""
9
+
10
+ # devtool_interpreter <class>
11
+ # Print the interpreter for a shell class, or fail. The sh class is dash, or
12
+ # an sh that is not bash, zsh, or ksh in disguise.
13
+ devtool_interpreter() {
14
+ case "$1" in
15
+ sh)
16
+ _di_path=$(command -v dash 2>/dev/null || :)
17
+ if [ -z "$_di_path" ]; then
18
+ _di_path=$(command -v sh 2>/dev/null || :)
19
+ [ -n "$_di_path" ] || return 1
20
+ # shellcheck disable=SC2016
21
+ "$_di_path" -c '[ -z "${BASH_VERSION-}${ZSH_VERSION-}${KSH_VERSION-}" ]' 2>/dev/null || return 1
22
+ fi
23
+ ;;
24
+ bash | zsh | ksh | fish) _di_path=$(command -v "$1" 2>/dev/null || :) ;;
25
+ *) return 1 ;;
26
+ esac
27
+ case "$_di_path" in
28
+ /*) printf '%s\n' "$_di_path" ;;
29
+ *) return 1 ;;
30
+ esac
31
+ }
32
+
33
+ # devtool_classes <shells-field>: the shell classes a module declares.
34
+ devtool_classes() {
35
+ _dc_out=
36
+ for _dc_word in $1; do
37
+ case "$_dc_word" in
38
+ posix) _dc_add='sh bash zsh ksh' ;;
39
+ *) _dc_add=$_dc_word ;;
40
+ esac
41
+ for _dc_class in $_dc_add; do
42
+ case " $_dc_out " in *" $_dc_class "*) ;; *) _dc_out="${_dc_out:+$_dc_out }$_dc_class" ;; esac
43
+ done
44
+ done
45
+ printf '%s\n' "$_dc_out"
46
+ unset _dc_out _dc_word _dc_add _dc_class
47
+ }
48
+
49
+ # devtool_scratch: create the scratch directory for one command.
50
+ devtool_scratch() {
51
+ _dev_tmp=$(trusted_mktemp dir "${TMPDIR:-/tmp}/valise-dev.XXXXXX") || die "cannot create scratch directory"
52
+ _dev_tmp=$(cd "$_dev_tmp" && pwd -P) || die "cannot resolve scratch directory"
53
+ }
54
+
55
+ devtool_cleanup() {
56
+ if [ -n "$_dev_tmp" ]; then
57
+ remove_tree "$_dev_tmp" || info "warning: cannot remove scratch directory: $_dev_tmp"
58
+ _dev_tmp=""
59
+ fi
60
+ }
61
+
62
+ # devtool_build <out> <log> <sh-roots> <bash-roots> <zsh-roots> <ksh-roots> <fish-roots>
63
+ # Build a release whose roots are given per shell, ignoring bundles and
64
+ # variants. Output goes to the log; fails when the build fails.
65
+ devtool_build() {
66
+ (
67
+ _pb_override=1
68
+ _pbo_sh=$3 _pbo_bash=$4 _pbo_zsh=$5 _pbo_ksh=$6 _pbo_fish=$7
69
+ project_build dev dev "$1" 0
70
+ ) >"$2" 2>&1
71
+ }
72
+
73
+ # devtool_runner <class> <release> <file> <body-file>
74
+ # Write a script that loads the release for one shell class in strict mode,
75
+ # defines valise_test_reload, then runs the body. VALISE_TEST_RELEASE and
76
+ # VALISE_TEST_PLAN are exported so the body can load the same modules in a
77
+ # child shell.
78
+ devtool_runner() {
79
+ _dr_release=$(shell_quote "$2") || return 1
80
+ _dr_cat=$(trusted_utility_path cat) || return 1
81
+ if [ "$1" = fish ]; then
82
+ _dr_release=$(fish_quote "$2") || return 1
83
+ {
84
+ printf '%s\n' \
85
+ "set -gx SHELL_PROJECT_ROOT $_dr_release" \
86
+ "set -gx VALISE_TEST_RELEASE $_dr_release" \
87
+ 'set -gx VALISE_TEST_PLAN "$VALISE_TEST_RELEASE/runtime/fish/profile.fish"' \
88
+ 'source "$SHELL_PROJECT_ROOT/runtime/fish/profile.fish"' \
89
+ 'or begin' \
90
+ " printf '%s\\n' 'valise: the modules failed to load' >&2" \
91
+ ' exit 97' \
92
+ 'end' \
93
+ 'function valise_test_reload' \
94
+ ' source "$SHELL_PROJECT_ROOT/runtime/fish/profile.fish"' \
95
+ 'end'
96
+ "$_dr_cat" "$4"
97
+ } >"$3"
98
+ else
99
+ {
100
+ printf '%s\n' \
101
+ "_valise_dev_release=$_dr_release" \
102
+ 'VALISE_TEST_RELEASE=$_valise_dev_release' \
103
+ "VALISE_TEST_PLAN=\$_valise_dev_release/plans/$1.sh" \
104
+ 'export VALISE_TEST_RELEASE VALISE_TEST_PLAN' \
105
+ 'valise_test_reload() {' \
106
+ ' VALISE_MODULE_ROOT=$_valise_dev_release' \
107
+ ' VALISE_MODE=strict' \
108
+ ' VALISE_RELOAD=1' \
109
+ " . \"\$_valise_dev_release/plans/$1.sh\"" \
110
+ ' _valise_dev_status=$?' \
111
+ ' unset VALISE_RELOAD' \
112
+ ' return "$_valise_dev_status"' \
113
+ '}' \
114
+ 'VALISE_MODULE_ROOT=$_valise_dev_release' \
115
+ 'VALISE_MODE=strict' \
116
+ ". \"\$_valise_dev_release/plans/$1.sh\" || {" \
117
+ " printf '%s\\n' 'valise: the modules failed to load' >&2" \
118
+ ' exit 97' \
119
+ '}'
120
+ "$_dr_cat" "$4"
121
+ } >"$3"
122
+ fi
123
+ _dr_status=$?
124
+ unset _dr_release _dr_cat
125
+ return "$_dr_status"
126
+ }
127
+
128
+ # devtool_quarantine <scratch> <interpreter>
129
+ # Create the test home and XDG tree; set _dev_path to the test PATH.
130
+ devtool_quarantine() {
131
+ _dq_scratch=$1
132
+ _dq_mkdir=$(trusted_utility_path mkdir) || die "no trusted mkdir utility available"
133
+ "$_dq_mkdir" -p "$_dq_scratch/home" "$_dq_scratch/tmp" "$_dq_scratch/xdg/config" \
134
+ "$_dq_scratch/xdg/data" "$_dq_scratch/xdg/state" "$_dq_scratch/xdg/cache" ||
135
+ die "cannot create the test home"
136
+ _dev_path="${VALISE_TEST_PATH:-/usr/bin:/bin:/usr/sbin:/sbin}:${2%/*}"
137
+ unset _dq_scratch _dq_mkdir
138
+ }
139
+
140
+ # devtool_exec <scratch> <interpreter> <runner> [args...]
141
+ # Run a runner script in a quarantined environment.
142
+ devtool_exec() {
143
+ _de_scratch=$1
144
+ _de_interp=$2
145
+ _de_runner=$3
146
+ shift 3
147
+ devtool_quarantine "$_de_scratch" "$_de_interp"
148
+ env -i HOME="$_de_scratch/home" TMPDIR="$_de_scratch/tmp" PATH="$_dev_path" TERM=dumb \
149
+ LANG="${LANG:-C}" USER="${USER:-valise}" LOGNAME="${LOGNAME:-${USER:-valise}}" \
150
+ XDG_CONFIG_HOME="$_de_scratch/xdg/config" XDG_DATA_HOME="$_de_scratch/xdg/data" \
151
+ XDG_STATE_HOME="$_de_scratch/xdg/state" XDG_CACHE_HOME="$_de_scratch/xdg/cache" \
152
+ VALISE_TEST_ROOT="$_de_scratch" VALISE_PROJECT_ROOT="$_project_root" \
153
+ VALISE_TEST_MODULE="${_dev_module:-}" VALISE_TEST_MODULE_DIR="${_dev_module_dir:-}" \
154
+ VALISE_TEST_SHELL="${_dev_class:-}" \
155
+ "$_de_interp" "$_de_runner" "$@"
156
+ }
157
+
158
+ # --- env ---
159
+
160
+ cmd_env() {
161
+ case "${1:-}" in
162
+ --help | -h) usage_env; return 0 ;;
163
+ '') ;;
164
+ *) die "unknown env option: $1" ;;
165
+ esac
166
+ _env_engine=$(engine_root) || die "cannot locate the valise engine"
167
+ _env_project=$(project_find_root 2>/dev/null || :)
168
+ _env_bin=$(commands_bin_dir 2>/dev/null || :)
169
+ for _env_pair in \
170
+ "VALISE_VERSION=$VALISE_VERSION" \
171
+ "VALISE_ENGINE_ROOT=$_env_engine" \
172
+ "VALISE_PROJECT_ROOT=$_env_project" \
173
+ "VALISE_DATA_DIR=$VALISE_XDG_DATA" \
174
+ "VALISE_CONFIG_DIR=$VALISE_XDG_CONFIG" \
175
+ "VALISE_STATE_DIR=$VALISE_XDG_STATE" \
176
+ "VALISE_RELEASES_DIR=$VALISE_RELEASES_DIR" \
177
+ "VALISE_ACTIVE_LINK=$VALISE_ACTIVE_LINK" \
178
+ "VALISE_BIN_DIR=$_env_bin"; do
179
+ printf '%s=%s\n' "${_env_pair%%=*}" "$(shell_quote "${_env_pair#*=}")"
180
+ done
181
+ unset _env_engine _env_project _env_bin _env_pair
182
+ }
183
+
184
+ # --- lint ---
185
+
186
+ # lint_report <ok|FAIL|skip> <message>
187
+ lint_report() {
188
+ printf '%s: %s\n' "$1" "$2"
189
+ [ "$1" != FAIL ] || _lint_problems=$((_lint_problems + 1))
190
+ }
191
+
192
+ # lint_parse <class> <file> <label>
193
+ lint_parse() {
194
+ if ! _lp_interp=$(devtool_interpreter "$1"); then
195
+ if [ "$_lint_require" -eq 1 ]; then
196
+ lint_report FAIL "$3: no $1 parser available"
197
+ else
198
+ case " $_lint_missing " in
199
+ *" $1 "*) ;;
200
+ *) lint_report skip "no $1 parser available; $1 syntax is not checked"; _lint_missing="$_lint_missing $1" ;;
201
+ esac
202
+ fi
203
+ return 0
204
+ fi
205
+ if [ "$1" = fish ]; then
206
+ _lp_out=$("$_lp_interp" --no-execute "$2" 2>&1) && _lp_rc=0 || _lp_rc=$?
207
+ else
208
+ _lp_out=$("$_lp_interp" -n "$2" 2>&1) && _lp_rc=0 || _lp_rc=$?
209
+ fi
210
+ if [ "$_lp_rc" -eq 0 ]; then
211
+ _lint_parsed=$((_lint_parsed + 1))
212
+ else
213
+ lint_report FAIL "$3: $1 syntax error: $(printf '%s' "$_lp_out" | head -n 3 | tr '\n' ' ')"
214
+ fi
215
+ unset _lp_interp _lp_out _lp_rc
216
+ }
217
+
218
+ cmd_lint() {
219
+ _lint_require=0
220
+ _lint_select=
221
+ for _lint_arg do
222
+ case "$_lint_arg" in
223
+ --require-parsers) _lint_require=1 ;;
224
+ --help | -h) usage_lint; return 0 ;;
225
+ -*) die "unknown lint option: $_lint_arg" ;;
226
+ *) _lint_select="$_lint_select $_lint_arg" ;;
227
+ esac
228
+ done
229
+ project_config_load
230
+ devtool_scratch
231
+ _lint_problems=0
232
+ _lint_parsed=0
233
+ _lint_missing=
234
+ _lint_awk=$(trusted_utility_path awk) || die "no trusted awk utility available"
235
+
236
+ # Metadata: registry generation validates every record, names, prefixes,
237
+ # and the files each module names.
238
+ _lint_registry="$_dev_tmp/registry"
239
+ if _lint_out=$( (registry_generate "$_project_root" "$_lint_registry") 2>&1); then
240
+ lint_report ok "metadata of $(($("$_lint_awk" 'END { print NR }' "$_lint_registry") - 1)) module(s)"
241
+ else
242
+ lint_report FAIL "$(printf '%s' "$_lint_out" | sed -n 's/^valise: error: //p' | head -n 1)"
243
+ printf 'valise lint: %d problem(s)\n' "$_lint_problems"
244
+ devtool_cleanup
245
+ exit 1
246
+ fi
247
+
248
+ # Layers: contrib_ names, versions, safe modes, and dependencies that stay
249
+ # inside the layer or the engine.
250
+ for _lint_layer in "$_project_root"/layers/*/; do
251
+ [ -d "$_lint_layer" ] || continue
252
+ _lint_layer=${_lint_layer%/}
253
+ _lint_name=${_lint_layer##*/}
254
+ if _lint_out=$(layer_lint_dir "$_lint_layer" "$_lint_name" 2>&1); then
255
+ lint_report ok "layer $_lint_name"
256
+ else
257
+ printf '%s\n' "$_lint_out" | sed -n 's/^ FAIL: //p' | while IFS= read -r _lint_line; do
258
+ printf 'FAIL: layers/%s: %s\n' "$_lint_name" "$_lint_line"
259
+ done
260
+ _lint_problems=$((_lint_problems + $(printf '%s\n' "$_lint_out" | grep -c '^ FAIL: ')))
261
+ fi
262
+ done
263
+ if [ -n "$_project_lint_forbid" ] && [ -d "$_project_root/layers" ]; then
264
+ if _lint_out=$(grep -rniE -- "$_project_lint_forbid" "$_project_root/layers" 2>/dev/null); then
265
+ printf '%s\n' "$_lint_out" | head -n 20 | while IFS= read -r _lint_line; do
266
+ printf 'FAIL: forbidden in layers (lint_layer_forbid): %s\n' "${_lint_line#"$_project_root"/}"
267
+ done
268
+ _lint_problems=$((_lint_problems + 1))
269
+ else
270
+ lint_report ok "layers contain nothing matching lint_layer_forbid"
271
+ fi
272
+ fi
273
+
274
+ # Every bundle compiles for every variant and shell: unknown roots and
275
+ # exclusions, missing dependencies, and exclusions that break a remaining
276
+ # module all fail here.
277
+ _lint_bundles=$(project_bundle_names "$_project_root") || die "cannot read bundles.tsv"
278
+ _lint_variants=$(project_variant_names "$_project_root") || die "cannot read variants.tsv"
279
+ for _lint_bundle in $_lint_bundles; do
280
+ for _lint_variant in $_lint_variants; do
281
+ _lint_excl=$(project_variant_field "$_project_root" "$_lint_variant" excluded 2>/dev/null) || {
282
+ lint_report FAIL "variant $_lint_variant cannot be read"
283
+ continue
284
+ }
285
+ _lint_combo_ok=1
286
+ for _lint_shell in $VALISE_PROJECT_SHELLS fish; do
287
+ _lint_roots=$(project_bundle_roots "$_project_root" "$_lint_shell" "$_lint_bundle" 2>&1) || {
288
+ lint_report FAIL "bundle $_lint_bundle: $_lint_roots"
289
+ _lint_combo_ok=0
290
+ continue
291
+ }
292
+ [ -n "$_lint_roots" ] || continue
293
+ if ! _lint_out=$( (project_compile "$_lint_registry" "$_lint_shell" "$_lint_roots" "$_lint_excl" \
294
+ "$_dev_tmp/plan.$_lint_shell" closure) 2>&1); then
295
+ # The compiler's own reason comes first; the wrapper's message last.
296
+ lint_report FAIL "bundle $_lint_bundle variant $_lint_variant ($_lint_shell): $(printf '%s\n' "$_lint_out" |
297
+ sed -n 's/^valise: error: //p; s/^ERROR: //p' | head -n 1)"
298
+ _lint_combo_ok=0
299
+ fi
300
+ done
301
+ [ "$_lint_combo_ok" -eq 0 ] || lint_report ok "bundle $_lint_bundle variant $_lint_variant compiles"
302
+ done
303
+ done
304
+
305
+ # Syntax of every body and test with each parser its shells name.
306
+ # Tab is IFS whitespace, so read would merge empty columns; awk emits a
307
+ # separator that no validated name or path contains.
308
+ "$_lint_awk" -F '\t' 'NR > 1 && $13 != "valise" { print $1 "|" $6 "|" $9 "|" $12 "|" $13 }' \
309
+ "$_lint_registry" >"$_dev_tmp/lint-rows" || die "cannot read the registry"
310
+ while IFS='|' read -r _lint_mname _lint_shells _lint_load _lint_tests _lint_src; do
311
+ if [ -n "$_lint_select" ]; then
312
+ case " $_lint_select " in
313
+ *" $_lint_mname "* | *" $_lint_mname@$_lint_src "*) ;;
314
+ *) continue ;;
315
+ esac
316
+ fi
317
+ _lint_dir=$(project_source_dir "$_project_root" "$_lint_src") || continue
318
+ _lint_rel=${_lint_dir#"$_project_root"}
319
+ _lint_rel=${_lint_rel#/}
320
+ for _lint_file in $_lint_load $_lint_tests; do
321
+ for _lint_class in $(devtool_classes "$_lint_shells"); do
322
+ case "$_lint_class:$_lint_file" in
323
+ fish:*.fish | sh:* | bash:* | zsh:* | ksh:*) ;;
324
+ *) continue ;;
325
+ esac
326
+ case "$_lint_class:$_lint_file" in
327
+ fish:*) ;;
328
+ *:*.fish) continue ;;
329
+ esac
330
+ lint_parse "$_lint_class" "$_lint_dir/$_lint_file" "${_lint_rel:+$_lint_rel/}$_lint_file"
331
+ done
332
+ done
333
+ done <"$_dev_tmp/lint-rows"
334
+ lint_report ok "$_lint_parsed syntax check(s)"
335
+ if [ -n "$_lint_select" ]; then
336
+ for _lint_want in $_lint_select; do
337
+ "$_lint_awk" -F '\t' -v w="$_lint_want" 'NR > 1 && $13 != "valise" && ($1 == w || $1 "@" $13 == w) { f = 1 } END { exit !f }' \
338
+ "$_lint_registry" || lint_report FAIL "unknown module: $_lint_want"
339
+ done
340
+ fi
341
+
342
+ devtool_cleanup
343
+ if [ "$_lint_problems" -ne 0 ]; then
344
+ printf 'valise lint: %d problem(s)\n' "$_lint_problems"
345
+ exit 1
346
+ fi
347
+ printf 'valise lint: no problems\n'
348
+ unset _lint_require _lint_select _lint_arg _lint_problems _lint_parsed _lint_missing _lint_awk _lint_registry \
349
+ _lint_out _lint_layer _lint_name _lint_line _lint_bundles _lint_variants _lint_bundle _lint_variant _lint_excl \
350
+ _lint_combo_ok _lint_shell _lint_roots _lint_mname _lint_shells _lint_load _lint_tests _lint_src \
351
+ _lint_dir _lint_rel _lint_file _lint_class _lint_want
352
+ }
353
+
354
+ # --- test ---
355
+
356
+ cmd_test() {
357
+ _test_shells=
358
+ _test_require=0
359
+ _test_select=
360
+ for _test_arg do
361
+ case "$_test_arg" in
362
+ --shell=*) _test_shells="$_test_shells $(printf '%s' "${_test_arg#--shell=}" | tr ',' ' ')" ;;
363
+ --require-shells) _test_require=1 ;;
364
+ --help | -h) usage_test; return 0 ;;
365
+ -*) die "unknown test option: $_test_arg" ;;
366
+ *) _test_select="$_test_select $_test_arg" ;;
367
+ esac
368
+ done
369
+ for _test_class in $_test_shells; do
370
+ case "$_test_class" in
371
+ sh | bash | zsh | ksh | fish) ;;
372
+ *) die "unknown shell class: $_test_class (use sh, bash, zsh, ksh, or fish)" ;;
373
+ esac
374
+ done
375
+ project_config_load
376
+ devtool_scratch
377
+ _test_awk=$(trusted_utility_path awk) || die "no trusted awk utility available"
378
+ registry_generate "$_project_root" "$_dev_tmp/registry"
379
+ for _test_want in $_test_select; do
380
+ "$_test_awk" -F '\t' -v w="$_test_want" 'NR > 1 && $13 != "valise" && ($1 == w || $1 "@" $13 == w) { f = 1 } END { exit !f }' \
381
+ "$_dev_tmp/registry" || die "unknown module: $_test_want"
382
+ done
383
+ _test_pass=0
384
+ _test_fail=0
385
+ _test_skip=0
386
+ _test_n=0
387
+ _test_any=0
388
+ _test_rows="$_dev_tmp/rows"
389
+ "$_test_awk" -F '\t' 'NR > 1 && $13 != "valise" && $12 != "" { print $1 "\t" $6 "\t" $12 "\t" $13 }' \
390
+ "$_dev_tmp/registry" >"$_test_rows"
391
+ while IFS="$(printf '\t')" read -r _dev_name _test_mshells _test_files _test_src; do
392
+ if [ -n "$_test_select" ]; then
393
+ case " $_test_select " in
394
+ *" $_dev_name "* | *" $_dev_name@$_test_src "*) ;;
395
+ *) continue ;;
396
+ esac
397
+ fi
398
+ _test_any=1
399
+ _dev_module=$_dev_name
400
+ _dev_module_dir=$(project_source_dir "$_project_root" "$_test_src") || die "unsupported registry source: $_test_src"
401
+ _test_id="$_dev_name@$_test_src"
402
+ _test_classes=$(devtool_classes "$_test_mshells")
403
+ _test_n=$((_test_n + 1))
404
+ _test_rel="$_dev_tmp/release-$_test_n"
405
+ # One release per module: the module is the only root for every shell it
406
+ # supports, and no variant applies.
407
+ _r_sh= _r_bash= _r_zsh= _r_ksh= _r_fish=
408
+ for _dev_class in $_test_classes; do
409
+ eval "_r_$_dev_class=\$_test_id"
410
+ done
411
+ if ! devtool_build "$_test_rel" "$_dev_tmp/build-$_test_n.log" "$_r_sh" "$_r_bash" "$_r_zsh" "$_r_ksh" "$_r_fish"; then
412
+ printf 'FAIL %s: cannot build a release for it\n' "$_dev_name"
413
+ sed 's/^/ /' "$_dev_tmp/build-$_test_n.log"
414
+ _test_fail=$((_test_fail + 1))
415
+ continue
416
+ fi
417
+ for _dev_class in $_test_classes; do
418
+ if [ -n "$_test_shells" ]; then
419
+ case " $_test_shells " in *" $_dev_class "*) ;; *) continue ;; esac
420
+ fi
421
+ if ! _test_interp=$(devtool_interpreter "$_dev_class"); then
422
+ if [ "$_test_require" -eq 1 ]; then
423
+ printf 'FAIL %s [%s]: no %s interpreter\n' "$_dev_name" "$_dev_class" "$_dev_class"
424
+ _test_fail=$((_test_fail + 1))
425
+ else
426
+ printf 'skip %s [%s]: no %s interpreter\n' "$_dev_name" "$_dev_class" "$_dev_class"
427
+ _test_skip=$((_test_skip + 1))
428
+ fi
429
+ continue
430
+ fi
431
+ for _test_file in $_test_files; do
432
+ # Fish tests run only in Fish; every other test only in POSIX shells.
433
+ case "$_dev_class:$_test_file" in
434
+ fish:*.fish) ;;
435
+ fish:* | *:*.fish) continue ;;
436
+ esac
437
+ _test_n=$((_test_n + 1))
438
+ _test_scratch="$_dev_tmp/run-$_test_n"
439
+ _test_runner="$_dev_tmp/runner-$_test_n"
440
+ devtool_runner "$_dev_class" "$_test_rel" "$_test_runner" "$_dev_module_dir/$_test_file" ||
441
+ die "cannot write the test runner"
442
+ if _test_out=$(devtool_exec "$_test_scratch" "$_test_interp" "$_test_runner" 2>&1 </dev/null); then
443
+ printf 'ok %s [%s] %s\n' "$_dev_name" "$_dev_class" "$_test_file"
444
+ _test_pass=$((_test_pass + 1))
445
+ else
446
+ printf 'FAIL %s [%s] %s\n' "$_dev_name" "$_dev_class" "$_test_file"
447
+ [ -z "$_test_out" ] || printf '%s\n' "$_test_out" | sed 's/^/ /'
448
+ _test_fail=$((_test_fail + 1))
449
+ fi
450
+ done
451
+ done
452
+ done <"$_test_rows"
453
+ devtool_cleanup
454
+ if [ "$_test_any" -eq 0 ]; then
455
+ printf 'valise test: no selected module declares tests=\n'
456
+ [ -z "$_test_select" ] || exit 1
457
+ return 0
458
+ fi
459
+ printf 'valise test: %d passed, %d failed, %d skipped\n' "$_test_pass" "$_test_fail" "$_test_skip"
460
+ [ "$_test_fail" -eq 0 ] || exit 1
461
+ unset _test_shells _test_require _test_select _test_arg _test_class _test_awk _test_want _test_pass _test_fail \
462
+ _test_skip _test_n _test_any _test_rows _dev_name _test_mshells _test_files _test_src _dev_module \
463
+ _dev_module_dir _test_id _test_classes _test_rel _r_sh _r_bash _r_zsh _r_ksh _r_fish _dev_class _test_interp \
464
+ _test_file _test_scratch _test_runner _test_out
465
+ }
466
+
467
+ # --- run ---
468
+
469
+ cmd_run() {
470
+ _run_class=
471
+ _run_modules=
472
+ _run_bundle=
473
+ _run_variant=
474
+ _run_keep=0
475
+ while [ "$#" -gt 0 ]; do
476
+ case "$1" in
477
+ --shell=*) _run_class=${1#--shell=} ;;
478
+ --module=*) _run_modules="${_run_modules:+$_run_modules }${1#--module=}" ;;
479
+ --bundle=*) _run_bundle=${1#--bundle=} ;;
480
+ --variant=*) _run_variant=${1#--variant=} ;;
481
+ --keep-env) _run_keep=1 ;;
482
+ --help | -h) usage_run; return 0 ;;
483
+ --) shift; break ;;
484
+ *) die "unknown run option: $1 (the script follows --)" ;;
485
+ esac
486
+ shift
487
+ done
488
+ [ "$#" -ge 1 ] || die "usage: valise run [options] -- <script> [args...]"
489
+ _run_script=$1
490
+ shift
491
+ if [ -z "$_run_class" ]; then
492
+ case "${SHELL:-}" in
493
+ */bash | bash) _run_class=bash ;;
494
+ */zsh | zsh) _run_class=zsh ;;
495
+ */ksh | ksh) _run_class=ksh ;;
496
+ */fish | fish) _run_class=fish ;;
497
+ *) _run_class=sh ;;
498
+ esac
499
+ fi
500
+ case "$_run_class" in
501
+ sh | bash | zsh | ksh | fish) ;;
502
+ *) die "unknown shell class: $_run_class (use sh, bash, zsh, ksh, or fish)" ;;
503
+ esac
504
+ [ -z "$_run_modules" ] || [ -z "$_run_bundle$_run_variant" ] ||
505
+ die "--module selects the roots itself; it cannot be combined with --bundle or --variant"
506
+ _run_interp=$(devtool_interpreter "$_run_class") || die "no $_run_class interpreter found"
507
+ project_config_load
508
+ devtool_scratch
509
+ _run_rel="$_dev_tmp/release"
510
+ if [ -n "$_run_modules" ]; then
511
+ _r_sh= _r_bash= _r_zsh= _r_ksh= _r_fish=
512
+ eval "_r_$_run_class=\$_run_modules"
513
+ devtool_build "$_run_rel" "$_dev_tmp/build.log" "$_r_sh" "$_r_bash" "$_r_zsh" "$_r_ksh" "$_r_fish" || {
514
+ sed 's/^/ /' "$_dev_tmp/build.log" >&2
515
+ die "cannot build the selected modules"
516
+ }
517
+ else
518
+ if ! (project_build "${_run_variant:-$_project_variant}" "${_run_bundle:-$_project_bundle}" "$_run_rel" 0) \
519
+ >"$_dev_tmp/build.log" 2>&1; then
520
+ sed 's/^/ /' "$_dev_tmp/build.log" >&2
521
+ die "cannot build the project"
522
+ fi
523
+ [ -f "$_run_rel/plans/$_run_class.sh" ] || [ "$_run_class" = fish ] ||
524
+ die "the bundle has no roots for $_run_class"
525
+ [ "$_run_class" != fish ] || [ -d "$_run_rel/runtime/fish" ] || die "the bundle has no roots for fish"
526
+ fi
527
+ printf '%s\n' "$_run_script" >"$_dev_tmp/script" || die "cannot write the script"
528
+ devtool_runner "$_run_class" "$_run_rel" "$_dev_tmp/runner" "$_dev_tmp/script" || die "cannot write the runner"
529
+ _run_rc=0
530
+ if [ "$_run_keep" -eq 1 ]; then
531
+ "$_run_interp" "$_dev_tmp/runner" "$@" || _run_rc=$?
532
+ else
533
+ devtool_exec "$_dev_tmp/home" "$_run_interp" "$_dev_tmp/runner" "$@" || _run_rc=$?
534
+ fi
535
+ devtool_cleanup
536
+ unset _run_class _run_modules _run_bundle _run_variant _run_keep _run_script _run_interp _run_rel \
537
+ _r_sh _r_bash _r_zsh _r_ksh _r_fish
538
+ return "$_run_rc"
539
+ }