diff --git a/src/cli.rs b/src/cli.rs index ee5df02..4597c6f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -71,12 +71,12 @@ pub struct EditArgs { pub name: String, /// Enable or disable the self-monitor loopback. - #[arg(short, long, value_name = "BOOL")] + #[arg(short, long, num_args = 0..=1, default_missing_value = "true", value_name = "BOOL")] pub loopback: Option, /// Keep a mixed-in source audible in the loopback instead of isolating /// it (2-node topology instead of 4). - #[arg(long = "loopback-no-mix", value_name = "BOOL")] + #[arg(long = "loopback-no-mix", num_args = 0..=1, default_missing_value = "true", value_name = "BOOL")] pub loopback_no_mix: Option, /// Loopback volume: fraction (0.8) or percent (80). @@ -84,7 +84,7 @@ pub struct EditArgs { pub volume: Option, /// Mixed-in source volume: fraction (0.8) or percent (80). - #[arg(long = "source-volume", visible_alias = "sv", value_name = "PCT")] + #[arg(long = "source-volume", value_name = "PCT")] pub source_volume: Option, } diff --git a/src/main.rs b/src/main.rs index b823087..4572307 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,12 +44,22 @@ fn main() { } } -/// clap's `#[arg(short)]` only supports single-character short flags, so -/// `-sv` can't be a real short flag. Rewrite it to `--source-volume` before -/// clap ever sees it. +/// `(long name, multi-char alias)` pairs for flags whose short form is more +/// than one character - clap's `#[arg(short)]` only supports single +/// characters, so these can't be real clap short flags. Rewritten to their +/// long form before clap ever sees them (`normalize_args`), and shown +/// alongside the long form in the top-level help (`flag_names`) so both +/// places stay in sync from one definition. +const MULTI_CHAR_ALIASES: &[(&str, &str)] = &[("source-volume", "sv")]; + fn normalize_args(args: Vec) -> Vec { args.into_iter() - .map(|a| if a == "-sv" { "--source-volume".to_string() } else { a }) + .map(|a| { + a.strip_prefix('-') + .and_then(|rest| MULTI_CHAR_ALIASES.iter().find(|(_, alias)| *alias == rest)) + .map(|(long, _)| format!("--{long}")) + .unwrap_or(a) + }) .collect() } @@ -100,13 +110,8 @@ fn print_full_help() { let name = s.get_name(); let positionals = positional_args(s); let about = s.get_about().map(|a| a.to_string()).unwrap_or_default(); - let aliases = s.get_visible_aliases().collect::>().join("/"); - print!(" {name:name_w$} {} {about}", pad_visual(&positionals, pos_w)); - if !aliases.is_empty() { - print!(" | {aliases}"); - } - println!(); + println!(" {name:name_w$} {} {about}", pad_visual(&positionals, pos_w)); let rows = &flag_rows_by_cmd[i]; for (flag, help) in rows { @@ -120,6 +125,22 @@ fn print_full_help() { } println!(); + let alias_rows: Vec<(String, String)> = subcommands + .iter() + .filter_map(|s| { + let aliases = s.get_visible_aliases().collect::>().join(", "); + (!aliases.is_empty()).then(|| (s.get_name().to_string(), aliases)) + }) + .collect(); + if !alias_rows.is_empty() { + println!("{}", ui::blue("Aliases:")); + let name_w = alias_rows.iter().map(|(n, _)| n.len()).max().unwrap_or(0); + for (name, aliases) in &alias_rows { + println!(" {name:name_w$} {aliases}"); + } + println!(); + } + println!("{}", ui::blue("Options:")); let opt_rows: Vec<(String, String)> = cmd .get_arguments() @@ -167,13 +188,18 @@ fn flag_rows(cmd: &clap::Command) -> Vec<(String, String)> { } /// `-x/--long` / `-x` / `--long` for a non-positional arg, or `None` for -/// one with no visible flag at all. +/// one with no visible flag at all. A long flag with an entry in +/// `MULTI_CHAR_ALIASES` shows that alias in the short-flag position (e.g. +/// `-sv/--source-volume`), since it works the same way in practice. fn flag_names(arg: &clap::Arg) -> Option { - match (arg.get_short(), arg.get_long()) { - (Some(s), Some(l)) => Some(format!("-{s}/--{l}")), - (Some(s), None) => Some(format!("-{s}")), - (None, Some(l)) => Some(format!("--{l}")), - (None, None) => None, + let long = arg.get_long(); + let multi_char_alias = long.and_then(|l| MULTI_CHAR_ALIASES.iter().find(|(name, _)| *name == l)).map(|(_, a)| *a); + match (arg.get_short(), long, multi_char_alias) { + (Some(s), Some(l), _) => Some(format!("-{s}/--{l}")), + (Some(s), None, _) => Some(format!("-{s}")), + (None, Some(l), Some(a)) => Some(format!("-{a}/--{l}")), + (None, Some(l), None) => Some(format!("--{l}")), + (None, None, _) => None, } }