use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, Subcommand};
use hblank_cli::{
CatalogOptions, DevOptions, InitOptions, TestOptions, initialize, run_dev, run_list, run_tests,
};
#[derive(Debug, Parser)]
#[command(
version,
about = "Develop GPUI components in isolation",
after_long_help = "OPEN A FIXTURE DIRECTLY:
hblank dev --fixture src/button.hblank.rs
Run 'hblank dev --help' for relative-path rules and complete usage details."
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Init {
#[arg(long, default_value = ".")]
project: PathBuf,
#[arg(long, value_name = "PATH")]
runtime_path: Option<PathBuf>,
},
#[command(
long_about = "Launch the GPUI component development harness and watch for changes. Pass --fixture to select a fixture by its source-file path on the initial launch.",
after_long_help = "PATH RULES:
Relative fixture paths are resolved from --project. Absolute paths are accepted.
The file must match the fixture file patterns in .hblank/config.toml.
If a file registers multiple fixtures, the first in navigation order opens.
USAGE:
hblank dev --fixture src/button.hblank.rs
hblank dev --project crates/ui --fixture src/card.hblank.rs
hblank dev --fixture /absolute/path/to/src/badge.hblank.rs"
)]
Dev {
#[arg(long, default_value = ".")]
project: PathBuf,
#[arg(long, value_name = "PATH")]
fixture: Option<PathBuf>,
#[arg(long, value_name = "ID", conflicts_with = "fixture")]
fixture_id: Option<String>,
},
List {
#[arg(long, default_value = ".")]
project: PathBuf,
},
Test {
#[arg(long, default_value = ".")]
project: PathBuf,
#[arg(long, value_name = "FILTER")]
filter: Option<String>,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Command::Init {
project,
runtime_path,
} => {
let mut options = InitOptions::new(project);
options.runtime_path = runtime_path;
let report = initialize(&options)?;
println!("Initialized Hblank in {}", report.project_root.display());
for path in report.created {
println!(" create {}", path.display());
}
}
Command::Dev {
project,
fixture,
fixture_id,
} => {
let mut options = DevOptions::new(project);
options.fixture = fixture;
options.fixture_id = fixture_id;
run_dev(&options)?;
}
Command::List { project } => run_list(&CatalogOptions::new(project))?,
Command::Test { project, filter } => {
let mut options = TestOptions::new(project);
options.filter = filter;
run_tests(&options)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use clap::CommandFactory;
use super::Cli;
#[test]
fn top_level_help_points_to_direct_fixture_launch() {
let mut command = Cli::command();
let mut output = Vec::new();
command
.write_long_help(&mut output)
.expect("top-level help should render");
let help = String::from_utf8(output).expect("help should be UTF-8");
assert!(help.contains("hblank dev --fixture src/button.hblank.rs"));
assert!(help.contains("Run 'hblank dev --help'"));
}
#[test]
fn list_help_documents_registered_catalog() {
let mut command = Cli::command();
let list = command
.find_subcommand_mut("list")
.expect("list subcommand should exist");
let mut output = Vec::new();
list.write_long_help(&mut output)
.expect("list help should render");
let help = String::from_utf8(output).expect("help should be UTF-8");
assert!(help.contains("canonical fixture ids"));
}
#[test]
fn test_help_documents_generated_cargo_target() {
let mut command = Cli::command();
let test = command
.find_subcommand_mut("test")
.expect("test subcommand should exist");
let mut output = Vec::new();
test.write_long_help(&mut output)
.expect("test help should render");
let help = String::from_utf8(output).expect("help should be UTF-8");
assert!(help.contains("explicit inline Rust tests"));
assert!(help.contains("--filter <FILTER>"));
}
#[test]
fn dev_help_documents_fixture_path_semantics() {
let mut command = Cli::command();
let dev = command
.find_subcommand_mut("dev")
.expect("dev subcommand should exist");
let mut output = Vec::new();
dev.write_long_help(&mut output)
.expect("dev help should render");
let help = String::from_utf8(output).expect("help should be UTF-8");
assert!(help.contains("--fixture <PATH>"));
assert!(help.contains("--fixture-id <ID>"));
assert!(help.contains("path#function"));
assert!(help.contains("Relative fixture paths are resolved from --project"));
assert!(help.contains("If a file registers multiple fixtures"));
assert!(help.contains("hblank dev --project crates/ui --fixture src/card.hblank.rs"));
}
}