$_ bashkit

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() and write_stderr() append virtual command output; direct println!/eprintln! writes to the host process instead.
  • Set non-zero command status with ctx.set_exit_code(code) or ctx.fail(message, code).
  • --help and --version return exit code 0 with 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.

See Also