feat: zsh support — utilities + first test suite/CI - #15
Merged
Conversation
xsh runs imported utilities under zsh's ksh emulation (`emulate -L ksh`).
Most utilities already work as-is; this fixes the constructs the emulation
doesn't cover, and adds a test suite + CI exercising both shells.
Code (function-type utilities; scripts/ always run via bash, so unaffected):
- ${!var} name-indirection (value, ${!var+x} is-set, ${!var#prefix},
${!arr[@]} array-form) -> portable `eval` — cfg/get, cfg/set, cfn/stack/
create+update, cfn/vpn/cluster+config.
- ${!PREFIX@} variable-name listing -> zsh `parameters` association
(eval-wrapped so bash never parses the zsh-only syntax) — cfn/vpn/config.
- ${!#} -> ${*: -1}; positional ${!n} -> ${*:N:1}.
- ${!arr[@]} index iteration over a contiguous array -> counted loop —
cfn/vpn/ami.
- FUNCNAME -> built from zsh `funcstack` where passed to x-trap-return —
cfn/deploy, s3/test-upload-performance.
- BASH_REMATCH -> `setopt bash_rematch` — s3/uri/parser.
- `read -n N -p` -> zsh `read -k N "?prompt"` branch — ses/domain-dkim,
ses/sandbox/move.
- `status` local variable renamed (zsh ties `status` to $?, read-only) —
cfn/__init__, cfn/stack/list+status/wait, ses/domain-dkim+domain-identity,
ses/sandbox/move, rds/access. (`options` locals are left as-is: under the
ksh emulation a function-local `declare -a options` safely shadows zsh's
special parameter.)
- cfn STACK_STATUS: the sparse `[code]=name` array (zsh can't represent
sparse arrays; bash 3.2 has no associative arrays) is rebuilt as a flat
(code, name) pairs list; the derived *_STABLE / *_SERVICEABLE / ... arrays
are appended to (consumed by value, never by index, so equivalent).
- s3/uri/parser: the -s/-a/-h/-p/-k branches delegated to `xsh /uri/parser`
directly inside the getopts loop; under zsh OPTIND is shared between
ksh-emulated functions, so the nested getopts reset this loop's OPTIND and
spun forever. Capture the nested call in a subshell to isolate its OPTIND.
Incidental: fixed a pre-existing bash syntax error in spt/create (a missing
line-continuation made the function body swallow its closing brace, so the
util failed to source under bash; the util is marked untested upstream).
Tests:
- New test.sh: import-smokes every function utility under the running shell,
then asserts s3/uri/parser, s3/uri/translate, cfg/get (fixtures, throwaway
HOME), and the cfn STACK_STATUS classification. Self-sources ~/.xshrc so it
runs as a child of bash or zsh; uses an assert helper + failure counter
rather than `set -e` (utils ending in a getopts loop return its non-zero
status on success, which zsh's ERR_EXIT would trip inside `$()`).
- New CI (.github/workflows/test.yml): os × {bash, zsh} matrix; loads
xsh-lib/core then this library and runs test.sh. The zsh jobs are
continue-on-error until a zsh-supporting xsh + xsh-lib/core are released.
Verified: 14/14 assertions pass and all 76 function utilities import cleanly
under both zsh 5.9 and macOS bash 3.2.57.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
cfg/get parses ~/.aws/config via xsh-lib/core's /ini/parser. Older core releases ship an ini/parser.awk that aborts under gawk (most Linux); fixed in core, but this suite loads core's latest *stable tag*, which may predate the fix. Probe /ini/parser on the fixture and SKIP (not fail) the cfg/get assertions when it's unusable, so the suite doesn't go red on a dependency-version mismatch. Once a core with the gawk-safe parser is released, the assertions run automatically. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes the aws library run under zsh (the default shell on modern macOS), in addition to bash 3.2+. xsh executes imported utilities under zsh's ksh emulation; this PR fixes the constructs the emulation doesn't cover and adds the library's first test suite + CI, exercising both shells.
Companion to alexzhangs/xsh#32 (framework zsh support) and xsh-lib/core#5 (core lib zsh support).
Code (function-type utilities only —
scripts/always run viabash)${!var}name-indirection → portableeval(value,${!var+x}is-set,${!var#prefix},${!arr[@]}array form):cfg/get,cfg/set,cfn/stack/create+update,cfn/vpn/cluster+config.${!PREFIX@}variable-name listing → zshparametersassociation, eval-wrapped so neither shell's parser chokes on the other's syntax:cfn/vpn/config.${!#}→${*: -1}; positional${!n}→${*:N:1}.${!arr[@]}index iteration over a contiguous array → counted loop:cfn/vpn/ami.FUNCNAME→ built from zshfuncstackwhere passed tox-trap-return:cfn/deploy,s3/test-upload-performance.BASH_REMATCH→setopt bash_rematch:s3/uri/parser.read -n N -p→ zshread -k N "?prompt"branch:ses/domain-dkim,ses/sandbox/move.statuslocal variable renamed (zsh tiesstatusto$?, read-only):cfn/__init__,cfn/stack/list+status/wait,ses/domain-dkim+domain-identity,ses/sandbox/move,rds/access. (optionslocals are left as-is — under the ksh emulation a function-localdeclare -a optionssafely shadows zsh's special parameter.)cfnSTACK_STATUS: the sparse[code]=namearray (zsh can't represent sparse arrays; bash 3.2 has no associative arrays) is rebuilt as a flat(code, name)pairs list; the derived*_STABLE/*_SERVICEABLE/… arrays are appended to (consumed by value, never by index, so equivalent).s3/uri/parserOPTIND: the-s/-a/-h/-p/-kbranches delegated toxsh /uri/parserdirectly inside the getopts loop; under zsh OPTIND is shared between ksh-emulated functions, so the nested getopts reset this loop's OPTIND and spun forever. Now captured in a subshell to isolate OPTIND. (Audited the whole lib — this was the only util with that pattern.)Incidental fix:
spt/createhad a pre-existing bash syntax error (a missing line-continuation made the function body swallow its closing brace, so the util failed to source under bash at all; it's marked "untested" upstream). Fixed so the test suite can import every util.Tests
test.sh: import-smokes every function utility under the running shell, then assertss3/uri/parser,s3/uri/translate,cfg/get(ini fixtures under a throwawayHOME), and thecfnSTACK_STATUS classification. Self-sources~/.xshrcso it runs as a child of bash or zsh; uses an assert helper + failure counter rather thanset -e(many utils end in a getopts loop and return its non-zero status on success, which zsh'sERR_EXITwould trip inside$())..github/workflows/test.yml):os × {bash, zsh}matrix; loadsxsh-lib/corethen this library and runstest.sh.Locally: 14/14 assertions pass and all 76 function utilities import cleanly under both zsh 5.9 and macOS bash 3.2.57.
Sequencing
The zsh CI jobs are
continue-on-errorfor now: they install xsh fromalexzhangs/xshmaster andxsh-lib/core's latest stable tag, neither of which carries zsh support yet, so they stay red until those release. Removecontinue-on-erroronce a zsh-supporting xsh + core are released.🤖 Generated with Claude Code