huck 0.2.0
The first substantial release since v0.1.0 — 38 numbered iterations (v174 → v211) covering the biggest architectural change in the project's history, a wave of bash-compatibility fixes from three independent corpus sweeps, and a new public embedding API.
Headline: huck is now embeddable as a library
The biggest change in 0.2.0 is that huck has been refactored into a 3-crate workspace and exposes a stable embedding API. You can now drive a huck shell session from your own Rust program — run scripts, capture output, complete tab-completion candidates, sandbox the exec — without spawning the CLI.
use huck_engine::Engine;
let mut engine = Engine::new();
let out = engine.exec("a=(x y z); echo \"${a[@]^^}\"").capture();
assert_eq!(out.stdout, "X Y Z\n");
assert_eq!(out.exit_code, 0);The embedding crate is huck-engine; the lexer/parser/AST frontend is huck-syntax (separately consumable for shell linters and tooling — see its examples/ directory).
Embedding API surface (v203-v208)
huck_engine::Engine— persistent session owningRc<RefCell<Shell>>. Methods:new/builder/from_shell_cellfor construction;run/capture/run_file/run_scriptfor execution;var/set_var/set_args/set_arg0/last_statusfor state.Engine::exec(src).run() / .capture() / .stdin(...) / .merge_stderr() / .cwd(...) / .restricted(true) / .timeout(d)— per-call builder for IO routing, sandboxing, and timeouts.Engine::exec(src).on_stdout_line(cb).on_stderr_line(cb)— real-time streaming output callbacks; noSendbound, no drainer threads visible at the API surface.Engine::complete(line, cursor)— tab-completion on the embedded session: returnsCompletion { start, candidates }where eachCandidateis tagged withCandidateKind::{Command, Variable, File, Directory, Custom}.huck_engine::Output { stdout, stderr, exit_code }— capture return type.
Crate split
| Crate | Role |
|---|---|
huck-syntax |
Shell-free frontend: lexer, parser, command AST, source generator. No runtime dependency; usable for linters and tooling. |
huck-engine |
Embeddable terminal-free interpreter. Owns Engine. |
huck-cli |
REPL + rustyline line editor. |
huck |
The binary. |
huck-syntax was polished in v211 for external publication: Display + std::error::Error impls on the error types, #[non_exhaustive] on the AST enums (forward-compatible matching for downstream consumers), curated root re-exports, a try_split_assignment_ref peek variant, and a module-level doc with a doctest-checked Quick example.
Bash-compatibility expansion
Whole-array parameter-expansion modifiers (v209, v210)
${a[@]^^}, ${a[@]%.*}, ${a[@]/a/X}, ${a[@]@U}, ${a[@]@A}, ${a[@]@K}, ${a[@]@k}, ${a[@]@a} — every per-element and whole-array ${var@OP}-style operator now works across both indexed and associative arrays. v209 covers per-element ops (Case / RemovePrefix / RemoveSuffix / Substitute / per-element Transform); v210 covers whole-array ops (@A declare-style assignment string, @K/@k key/value pairs, @a attribute flags). Resolves M-127 and M-93.
declare -A m=([k]=v1 [j]=v2)
echo "${m[@]@A}" # declare -A m=([k]="v1" [j]="v2" )
declare -irx mix=7
echo "${mix@a}" # irxOther bash-compat highlights
set -xxtrace for compound commands (v198) — for-loops, case,(( )),[[ ]], C-for loops, etc. Resolves L-21(a).$"…"/$'…'quote forms outside double quotes (v181) — locale-aware identity and ANSI-C escape.shopt globstarcorrectness (v193) —**is single-level when off, recursive when on. Resolves M-53 (narrowed).- Legacy
$[ expr ]arithmetic (v188) — works again, equivalent to$(( )). - Quoted glob/regex metachars match literally (v199-v201) — closes a quote-provenance bug family in
[[ =~ ]],${var:-W}, and${var/pat/repl}operands. coprocsupport (v157) — single coproc, withNAME[0]/NAME[1]/NAME_PIDtriple.declare -p/ baredeclarebyte-faithful to bash for associative arrays (v211 closes L-44 a+b) — bareword subscript keys when safe, trailing space on assoc body.shiftover-range,trap '' KILL/STOP,${!1}on unset positional,$0not rebound in functions,trap … 0= EXIT, multi-line assoc-array init — runtime bugs caught and fixed.kill -l <N>now covers signals 1-31 (v189).
Parser robustness
Closed roughly 70 real-script parse gaps discovered by a 4191-script corpus sweep (v174-v192): comments inside delimiters, function-name charset, function-def trailing redirects, (( arith-vs-subshell disambiguation, case inside $(), for (( C-for, $[ ], multi-line array-init line continuations, name=\<NL>(array) line continuations, redirect-on-pipeline-stage, lexer-level |& desugar, and more.
Coverage + observability
The tests/scripts/*_diff_check.sh harness count grew from ~110 to 130+ harnesses — each runs the same shell fragments through bash and huck and asserts byte-identical output. Coverage rose to 88.7% line / 88.4% region / 96.0% function via the v188-era cargo-llvm-cov sweep.
Distribution
Distribution is self-hosted off GitHub Releases:
- Homebrew tap
jdstanhope/homebrew-huck— build-from-source formula, ships for macOS viabrew install jdstanhope/huck/huck. .debpackage attached to the GitHub release for Debian-derived Linux:dpkg -i huck_0.2.0_amd64.deb.curl | shinstaller —curl -fsSL https://raw.githubusercontent.com/jdstanhope/huck/v0.2.0/scripts/install.sh | sh.
Quality
cargo test --workspace --quiet: 744 unit + ~1668 integration + ~130 harness scripts, all green.cargo clippy --workspace --all-targets -- -D warnings: clean.- macOS portability is a standing constraint; cfg-gated platform-specifics throughout.
Compatibility notes
- Public API additions in
huck-engine(the newEnginefacade) andhuck-syntax(Display+Errorimpls,try_split_assignment_ref, root re-exports) are additive. #[non_exhaustive]on AST enums inhuck-syntax0.2.0 is a one-time SemVer floor for downstream consumers — future variants won't be breaking. Downstream matches must include a_ =>arm.set_last_statusmirror after run inEngine::runis a deliberate deviation from "exit trap fires AFTER run completes" — embedders see the correct exit code vialast_status()after a successful run.
Full iteration-by-iteration history: see docs/superpowers/{specs,plans}/ in the source tree, or git log v0.1.0..v0.2.0 --grep='^Merge v' for the merge stream.