fix(cli): list valid taps on bad --tap, make --help uniform across subcommands
Two CLI-UX defects the Stage-1 R fieldtest surfaced (#131): 1. Tap discovery. `aura chart <name> --tap <bad>` rejected an unknown tap but the error omitted the valid names. filter_to_tap now enumerates the run's available taps in the message ("... (available: equity, exposure)"), so the user can correct a typo instead of guessing -- refuse, but help. 2. Uniform --help. `aura chart --help` errored with "unexpected chart argument '--help'" (exit 2) and `aura run --help` leaked usage to stderr via the error path, while bare `aura --help` printed to stdout (exit 0). main() now intercepts `--help`/`-h` anywhere in the invocation, prints usage to stdout, and exits 0 -- uniform for every subcommand. The now-dead bare-flag match arm is removed (the early intercept covers it). RED-first: the existing chart_tap_nonexistent test gains an available-taps assertion (was: message had no names), and a new per_subcommand_help test pins `aura <sub> --help` -> stdout usage + exit 0 for run/chart/sweep/walkforward/ mc/graph/runs (was: chart --help exited 2). Unknown-subcommand stays exit 2. Verified: clippy --all-targets -D warnings clean; full suite 514 passed / 0 failed (one new test); live `aura chart --help` prints usage to stdout, exit 0. closes #131
This commit is contained in:
@@ -467,10 +467,12 @@ fn build_comparison_chart_data(
|
||||
/// run has no such tap (refuse-don't-guess). Used by the `--tap` flag on the
|
||||
/// single-run chart path; without `--tap` the single-run page is unchanged.
|
||||
fn filter_to_tap(data: ChartData, tap: &str) -> Result<ChartData, String> {
|
||||
let series: Vec<Series> = data.series.into_iter().filter(|s| s.name == tap).collect();
|
||||
if series.is_empty() {
|
||||
return Err(format!("run has no tap named '{tap}'"));
|
||||
if !data.series.iter().any(|s| s.name == tap) {
|
||||
// List the valid taps so the user can correct a typo (#131) — refuse, but help.
|
||||
let available: Vec<&str> = data.series.iter().map(|s| s.name.as_str()).collect();
|
||||
return Err(format!("run has no tap named '{tap}' (available: {})", available.join(", ")));
|
||||
}
|
||||
let series: Vec<Series> = data.series.into_iter().filter(|s| s.name == tap).collect();
|
||||
let mut meta = data.meta;
|
||||
meta.taps = vec![tap.to_string()];
|
||||
Ok(ChartData { xs: data.xs, series, meta })
|
||||
@@ -1966,6 +1968,13 @@ fn main() {
|
||||
// so an unexpected trailing token falls through to the usage-error path rather
|
||||
// than masquerading as a successful run (#16 strict reading).
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
// A `--help` / `-h` anywhere prints usage to stdout and exits 0, uniformly across
|
||||
// subcommands (#131): `aura chart --help` shows usage instead of erroring
|
||||
// "unexpected argument", and the bare `aura --help` is the same affordance.
|
||||
if args.iter().any(|a| a == "--help" || a == "-h") {
|
||||
println!("{USAGE}");
|
||||
return;
|
||||
}
|
||||
match args.iter().map(String::as_str).collect::<Vec<_>>().as_slice() {
|
||||
["run", rest @ ..] => match parse_run_args(rest) {
|
||||
Ok(args) => match run_dispatch(args) {
|
||||
@@ -2012,7 +2021,6 @@ fn main() {
|
||||
["runs", "families"] => runs_families(),
|
||||
["runs", "family", id] => runs_family(id, None),
|
||||
["runs", "family", id, "rank", metric] => runs_family(id, Some(metric)),
|
||||
["--help"] | ["-h"] => println!("{USAGE}"),
|
||||
_ => {
|
||||
eprintln!("aura: {USAGE}");
|
||||
std::process::exit(2);
|
||||
|
||||
@@ -489,6 +489,12 @@ fn chart_tap_nonexistent_on_run_refuses_with_exit_2() {
|
||||
stderr.contains("run has no tap named 'nope'"),
|
||||
"expected the no-such-tap refusal message, got: {stderr}"
|
||||
);
|
||||
// #131: the refusal lists the valid taps so the user can correct the typo
|
||||
// (the demo run's taps are `equity` and `exposure`).
|
||||
assert!(
|
||||
stderr.contains("available:") && stderr.contains("equity") && stderr.contains("exposure"),
|
||||
"the refusal should enumerate the available taps, got: {stderr}"
|
||||
);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&cwd);
|
||||
}
|
||||
@@ -557,6 +563,36 @@ fn help_flag_prints_usage_to_stdout_and_exits_zero() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Property (#131): `--help`/`-h` is UNIFORM across subcommands — `aura <sub> --help`
|
||||
/// prints usage to stdout and exits 0 for every subcommand, not only the bare `aura
|
||||
/// --help`. Pre-fix, `aura chart --help` errored with "unexpected chart argument"
|
||||
/// (exit 2) while `aura run --help` leaked usage to stderr; this pins the uniform
|
||||
/// success-path help affordance for all subcommands.
|
||||
#[test]
|
||||
fn per_subcommand_help_is_uniform_stdout_exit_zero() {
|
||||
for sub in ["run", "chart", "sweep", "walkforward", "mc", "graph", "runs"] {
|
||||
for flag in ["--help", "-h"] {
|
||||
let out = Command::new(BIN).args([sub, flag]).output().expect("spawn aura <sub> --help");
|
||||
assert_eq!(
|
||||
out.status.code(),
|
||||
Some(0),
|
||||
"`aura {sub} {flag}` should exit 0, got: {:?}",
|
||||
out.status
|
||||
);
|
||||
let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout");
|
||||
assert!(
|
||||
stdout.contains("usage"),
|
||||
"`aura {sub} {flag}` should print usage to stdout, got: {stdout:?}"
|
||||
);
|
||||
assert!(
|
||||
out.stderr.is_empty(),
|
||||
"`aura {sub} {flag}` should not write to stderr, got: {:?}",
|
||||
String::from_utf8_lossy(&out.stderr)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Property: `aura graph` emits a single self-contained HTML page — the
|
||||
/// interactive pin-graph viewer with the deterministic model inlined and the
|
||||
/// vendored Graphviz-WASM + pan-zoom inlined (no remote fetch). Driven through
|
||||
|
||||
Reference in New Issue
Block a user