Render a clap parse failure as `key: value` lines. A pure function returning a `String`, so the whole failure surface is unit-testable without spawning a process.
(err: &clap::Error, root: &clap::Command, argv: &[OsString])
| 577 | /// A pure function returning a `String`, so the whole failure surface is |
| 578 | /// unit-testable without spawning a process. |
| 579 | pub fn render(err: &clap::Error, root: &clap::Command, argv: &[OsString]) -> String { |
| 580 | // Scope first, everything else from it. clap's usage line is authoritative |
| 581 | // about WHICH command the error is about; argv is only a fallback for the |
| 582 | // kinds that attach no usage context. Deriving `cmd`/`verb`/`help` from a |
| 583 | // different source than `usage` is what lets them contradict. |
| 584 | let ctx_usage = ctx_str(err, ContextKind::Usage) |
| 585 | .map(|u| { |
| 586 | u.trim_start_matches("Usage:") |
| 587 | .trim_start_matches("usage:") |
| 588 | .trim() |
| 589 | .to_string() |
| 590 | }) |
| 591 | .filter(|u| !u.is_empty()); |
| 592 | |
| 593 | let (key, display) = match ctx_usage.as_deref().and_then(|u| path_from_usage(root, u)) { |
| 594 | Some(k) => { |
| 595 | let d = display_path(root, &k); |
| 596 | (k, d) |
| 597 | } |
| 598 | None => resolve_invoked(root, argv), |
| 599 | }; |
| 600 | |
| 601 | let mut kv: Vec<(&str, String)> = vec![ |
| 602 | ("error", kind_slug(err.kind()).to_string()), |
| 603 | ("cmd", display.clone()), |
| 604 | ]; |
| 605 | |
| 606 | // Keep the rejected value and the argument it belonged to separate. The |
| 607 | // old renderer selected only one context entry, which turned |
| 608 | // `--count nope` into a bare `got: nope` and dropped both `--count` and the |
| 609 | // parser's "invalid digit" cause. |
| 610 | match err.kind() { |
| 611 | ErrorKind::InvalidSubcommand => { |
| 612 | if let Some(value) = ctx_str(err, ContextKind::InvalidSubcommand) { |
| 613 | kv.push(("got", value)); |
| 614 | } |
| 615 | } |
| 616 | ErrorKind::UnknownArgument => { |
| 617 | if let Some(arg) = ctx_str(err, ContextKind::InvalidArg) { |
| 618 | kv.push(("got", arg)); |
| 619 | } |
| 620 | } |
| 621 | ErrorKind::MissingRequiredArgument => { |
| 622 | let missing = ctx_list(err, ContextKind::InvalidArg); |
| 623 | for required in missing { |
| 624 | kv.push(("required", required)); |
| 625 | } |
| 626 | } |
| 627 | ErrorKind::MissingSubcommand => {} |
| 628 | ErrorKind::InvalidUtf8 => { |
| 629 | if let Some(value) = rejected_non_utf8_arg(root, argv).and_then(encode_invalid_units) { |
| 630 | kv.push(("got-raw", value)); |
| 631 | } |
| 632 | } |
| 633 | _ => { |
| 634 | let invalid_arg = ctx_str(err, ContextKind::InvalidArg); |
| 635 | if let Some(arg) = invalid_arg.clone() { |
| 636 | kv.push(("arg", arg)); |