Clap Builtins
ClapBuiltin lets Rust applications define custom Bashkit commands with
#[derive(clap::Parser)] argument structs. Handlers receive a
BashkitContext that captures stdout, stderr, and exit code for the virtual
shell. clap is an unconditional dependency of bashkit, so this trait is
always available.
Basic Usage
use bashkit::clap::Parser;
use bashkit::{Bash, BashkitContext, ClapBuiltin, async_trait};
#[derive(Parser)]
#[command(name = "greet", about = "Print a greeting")]
struct GreetArgs {
#[arg(short, long, default_value = "World")]
name: String,
#[arg(short, long)]
shout: bool,
}
struct Greet;
#[async_trait]
impl ClapBuiltin for Greet {
type Args = GreetArgs;
async fn execute_clap(
&self,
args: Self::Args,
ctx: &mut BashkitContext<'_>,
) -> bashkit::Result<()> {
let greeting = format!("Hello, {}!", args.name);
let greeting = if args.shout {
greeting.to_uppercase()
} else {
greeting
};
ctx.write_stdout(format!("{greeting}\n"));
Ok(())
}
}
#[tokio::main]
async fn main() -> bashkit::Result<()> {
let mut bash = Bash::builder().builtin("greet", Box::new(Greet)).build();
let result = bash.exec("greet --name Alice --shout").await?;
assert_eq!(result.stdout, "HELLO, ALICE!\n");
let help = bash.exec("greet --help").await?;
assert_eq!(help.exit_code, 0);
assert!(help.stdout.contains("Usage: greet"));
assert!(help.stderr.is_empty());
let error = bash.exec("greet --unknown").await?;
assert_eq!(error.exit_code, 2);
assert!(error.stderr.contains("unexpected argument"));
Ok(())
}
Subcommands
Use normal clap subcommands for nested command surfaces.
use bashkit::clap::{Parser, Subcommand};
use bashkit::{Bash, BashkitContext, ClapBuiltin, async_trait};
#[derive(Parser)]
#[command(name = "math")]
struct MathArgs {
#[command(subcommand)]
command: MathCommand,
}
#[derive(Subcommand)]
enum MathCommand {
Add { left: i64, right: i64 },
StdinLen,
}
struct Math;
#[async_trait]
impl ClapBuiltin for Math {
type Args = MathArgs;
async fn execute_clap(
&self,
args: Self::Args,
ctx: &mut BashkitContext<'_>,
) -> bashkit::Result<()> {
let value = match args.command {
MathCommand::Add { left, right } => left + right,
MathCommand::StdinLen => ctx.stdin().unwrap_or("").len() as i64,
};
ctx.write_stdout(format!("{value}\n"));
Ok(())
}
}
#[tokio::main]
async fn main() -> bashkit::Result<()> {
let mut bash = Bash::builder().builtin("math", Box::new(Math)).build();
let sum = bash.exec("math add 20 22").await?;
assert_eq!(sum.stdout, "42\n");
let stdin_len = bash.exec("printf abc | math stdin-len").await?;
assert_eq!(stdin_len.stdout, "3\n");
Ok(())
}
Behavior
- The clap parser receives only the command arguments, not the shell command name from the script.
BashkitContext::write_stdout()andwrite_stderr()append virtual command output; directprintln!/eprintln!writes to the host process instead.- Set non-zero command status with
ctx.set_exit_code(code)orctx.fail(message, code). --helpand--versionreturn exit code0with clap output on stdout.- Parse failures return clap’s exit code, usually
2, with diagnostics on stderr. - Diagnostics are capped at 1 KB so custom builtins keep Bashkit’s builtin error-output safety guarantees.