diff --git a/docs/concepts.md b/docs/concepts.md index 39dc705..f29e74e 100644 --- a/docs/concepts.md +++ b/docs/concepts.md @@ -216,6 +216,9 @@ populate middleware: | Flag | Middleware field | Default | Purpose | | --- | --- | --- | --- | | `--output`, `-o` | `output_format` | `json` | Output format: `json`, `human`, or `toon`. | +| `--json` | `output_format` | — | Shorthand for `--output json`. | +| `--toon` | `output_format` | — | Shorthand for `--output toon`. | +| `--human` | `output_format` | — | Shorthand for `--output human`. | | `--verbose` | `verbose` | empty | Includes metadata; no value means `all`. | | `--dry-run` | `dry_run` | `false` | Short-circuits mutating/destructive commands. | | `--fields` | `fields` | empty | Selects comma-separated output fields. | @@ -337,8 +340,9 @@ my-cli project list --fields name,status my-cli project list --output human ``` -JSON output is the default. Human output is optimized for terminal reading. TOON can be requested -explicitly with `--output toon`. +JSON output is the default. Human output is optimized for terminal reading. Each format has a +shorthand flag: `--json`, `--human`, and `--toon` are equivalent to `--output json`, +`--output human`, and `--output toon` respectively. Errors are rendered through the same envelope path as successful data. Framework errors are mapped to process exit codes by category. Callers that need a specific process status can use diff --git a/docs/design.md b/docs/design.md index fef5cdc..cc81527 100644 --- a/docs/design.md +++ b/docs/design.md @@ -228,6 +228,9 @@ Framework global flags populate middleware and apply consistently to every comma | Flag | Purpose | | --- | --- | | `--output`, `-o` | Output format: `json`, `human`, or `toon`. | +| `--json` | Shorthand for `--output json`. | +| `--toon` | Shorthand for `--output toon`. | +| `--human` | Shorthand for `--output human`. | | `--verbose` | Includes metadata; no value means all metadata. | | `--dry-run` | Short-circuits mutating/destructive commands. | | `--fields` | Selects comma-separated output fields. | diff --git a/src/flags.rs b/src/flags.rs index ed703d4..697eca5 100644 --- a/src/flags.rs +++ b/src/flags.rs @@ -170,16 +170,45 @@ pub fn register_global_flags(command: Command) -> Command { .value_name("KEYWORD") .help("Search commands and guides by keyword"), ) + .arg( + Arg::new("json") + .long("json") + .global(true) + .action(ArgAction::SetTrue) + .help("Shorthand for --output json"), + ) + .arg( + Arg::new("toon") + .long("toon") + .global(true) + .action(ArgAction::SetTrue) + .help("Shorthand for --output toon"), + ) + .arg( + Arg::new("human") + .long("human") + .global(true) + .action(ArgAction::SetTrue) + .help("Shorthand for --output human"), + ) } #[must_use] /// Extracts framework-global flags from parsed `clap` matches. pub fn global_flags_from_matches(matches: &ArgMatches) -> GlobalFlags { - GlobalFlags { - output_format: matches + let output_format = if matches.get_flag("toon") { + "toon".to_owned() + } else if matches.get_flag("human") { + "human".to_owned() + } else { + matches .get_one::("output") .cloned() - .unwrap_or_else(|| "json".to_owned()), + .unwrap_or_else(|| "json".to_owned()) + }; + + GlobalFlags { + output_format, verbose: matches .get_one::("verbose") .cloned() @@ -237,7 +266,10 @@ pub fn extract_search_query(args: &[impl AsRef]) -> String { } #[must_use] -/// Extracts `--output`/`-o` from raw args before normal parsing. +/// Extracts output format from raw args. +/// +/// Recognizes `--output ` / `-o ` / `--output=`, +/// plus `--json`, `--toon`, and `--human` as shorthand for their respective formats. pub fn extract_output_format(args: &[impl AsRef]) -> String { for index in 0..args.len() { let arg = args[index].as_ref(); @@ -249,6 +281,15 @@ pub fn extract_output_format(args: &[impl AsRef]) -> String { if let Some(value) = arg.strip_prefix("--output=") { return value.to_owned(); } + if arg == "--json" { + return "json".to_owned(); + } + if arg == "--toon" { + return "toon".to_owned(); + } + if arg == "--human" { + return "human".to_owned(); + } } "json".to_owned() } diff --git a/tests/derive_bridge.rs b/tests/derive_bridge.rs index 81cdab7..55cac73 100644 --- a/tests/derive_bridge.rs +++ b/tests/derive_bridge.rs @@ -283,3 +283,54 @@ async fn derive_bridge_handles_flattened_structs() { assert_eq!(json["data"]["page_size"], 50); assert_eq!(json["data"]["page"], 0); } + +// --- output shorthand aliases --- + +#[tokio::test] +async fn json_shorthand_flag_produces_json_output() { + let cli = derive_cli(); + + let result = cli + .run(["derive-test", "greet", "hello", "--name", "World", "--json"]) + .await; + assert_eq!(result.exit_code, 0, "stderr: {}", result.rendered); + let json: Value = serde_json::from_str(&result.rendered).expect("valid json"); + assert_eq!(json["data"]["messages"], json!(["Hello, World!"])); +} + +#[tokio::test] +async fn toon_shorthand_flag_produces_toon_output() { + let cli = derive_cli(); + + let result = cli + .run(["derive-test", "greet", "hello", "--name", "World", "--toon"]) + .await; + assert_eq!(result.exit_code, 0, "output: {}", result.rendered); + assert!( + !result.rendered.starts_with('{'), + "toon output should not be raw JSON: {}", + result.rendered + ); +} + +#[tokio::test] +async fn human_shorthand_flag_produces_human_output() { + let cli = derive_cli(); + + let result = cli + .run([ + "derive-test", + "greet", + "hello", + "--name", + "World", + "--human", + ]) + .await; + assert_eq!(result.exit_code, 0, "output: {}", result.rendered); + assert!( + !result.rendered.starts_with('{'), + "human output should not be raw JSON: {}", + result.rendered + ); +} diff --git a/tests/foundation.rs b/tests/foundation.rs index 38ef51e..2ffd525 100644 --- a/tests/foundation.rs +++ b/tests/foundation.rs @@ -3435,6 +3435,22 @@ fn raw_search_and_output_extraction_matches_legacy_bypass_helpers() { extract_output_format(&["my-cli", "--search", "foo"]), "json" ); + assert_eq!( + extract_output_format(&["my-cli", "project", "list", "--json"]), + "json" + ); + assert_eq!( + extract_output_format(&["my-cli", "project", "list", "--toon"]), + "toon" + ); + assert_eq!( + extract_output_format(&["my-cli", "--toon", "project", "list"]), + "toon" + ); + assert_eq!( + extract_output_format(&["my-cli", "project", "list", "--human"]), + "human" + ); assert!(has_true_schema_flag(&["my-cli", "release", "--schema"])); assert!(has_true_schema_flag(&[