huck as a library

huck isn't only a shell you run — it's built as layered Rust crates, and two of them are public libraries you can depend on directly. Parse shell syntax, or embed a whole shell, inside your own program.

huck-syntax — the shell-free frontend

A standalone lexer, command-AST parser, brace expander, and source generator, with nodependency on huck's runtime — so it's a clean base for linters, formatters, and other shell tooling. Bytes become a Vec<Token> plus a Word AST; tokens become a Sequence / Command tree; and generate turns a tree back into canonical source for a round-trip.

The public AST enums (Token, WordPart, Command, ParseError, …) are #[non_exhaustive], so new variants in future releases stay SemVer-compatible — match with a _ => arm.

use huck_syntax::lexer::{Lexer, LexerOptions};
use huck_syntax::parser::parse_sequence;

let src = "echo hello | wc -l";
let mut lx = Lexer::new(src, &Default::default(), LexerOptions::default());

// A Sequence is huck's command AST: pipelines, and-or lists, redirections.
let seq = parse_sequence(&mut lx).expect("valid syntax").expect("non-empty");
assert!(!seq.background);

huck-engine — the terminal-free execution core

Engine is the embedding entry point: a persistent shell session with no terminal or line-editor attached. Run or capture script strings, run files, and get or set variables and positional parameters. Shells signal failure through exit codes, so these methods return an i32 status rather than a Result.

use huck_engine::Engine;

let mut e = Engine::new();
e.set_var("NAME", "world");
assert_eq!(e.run(r#"echo "hi $NAME""#), 0); // prints: hi world

// capture() splits stdout, stderr, and the exit code.
let out = e.capture("echo $((6 * 7)); echo done >&2");
assert_eq!(out.stdout, "42\n");
assert_eq!(out.stderr, "done\n");
assert_eq!(out.exit_code, 0);

For finer control, Engine::exec returns an ExecBuilder: feed it stdin, redirect the working directory, stream each output line to a callback as it's written, run under restricted mode, or cap execution with a timeout — useful for running untrusted or generated scripts.

use std::time::Duration;

// Stream each line as the script runs...
let mut lines: Vec<String> = Vec::new();
let exit = e.exec("for i in 1 2 3; do echo $i; done")
    .on_stdout_line(|line| lines.push(line.to_string()))
    .run();
assert_eq!(lines, vec!["1", "2", "3"]);

// ...or run it sandboxed: a scratch cwd, restricted mode, a time budget.
let out = e.exec(untrusted_script)
    .cwd(scratch_dir)
    .restricted(true)
    .timeout(Duration::from_secs(5))
    .capture();

Adding it to your project

The crates aren't published to crates.io yet, so depend on them from git. huck-cli(the rustyline-based REPL) layers on top of these two and isn't meant to be embedded — reach for huck-engine to run scripts and huck-syntax to parse them.

[dependencies]
# Not on crates.io yet — pull from git:
huck-syntax = { git = "https://github.com/jdstanhope/huck" }
huck-engine = { git = "https://github.com/jdstanhope/huck" }

Both crates carry runnable doc examples and examples/ programs. Browse the full API surface on GitHub.