Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions lib/src/spec/arg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,31 @@ impl FromStr for SpecArg {
}
}

/// A clap argument's defaults, as the spec has to record them.
///
/// clap splits a value by the argument's `value_delimiter` before anyone sees it, defaults
/// included — so `default_value = "a,b,c"` with `value_delimiter = ','` is three values, not one.
/// The spec has no delimiter of its own; it has a list, which is the same statement. Recording the
/// joined string instead described a CLI whose default is a single value that its own `choices`
/// forbid, which is how mise's `--fs-events` reached the spec.
#[cfg(feature = "clap")]
pub(crate) fn default_values(arg: &clap::Arg) -> Vec<String> {
let raw = arg
.get_default_values()
.iter()
.map(|v| v.to_string_lossy().to_string());
match arg.get_value_delimiter() {
Some(delimiter) => raw
.flat_map(|v| {
v.split(delimiter)
.map(|part| part.to_string())
.collect::<Vec<_>>()
})
.collect(),
None => raw.collect(),
}
}

#[cfg(feature = "clap")]
impl From<&clap::Arg> for SpecArg {
fn from(arg: &clap::Arg) -> Self {
Expand Down Expand Up @@ -376,11 +401,7 @@ impl From<&clap::Arg> for SpecArg {
var_max: None,
var_min: None,
hide,
default: arg
.get_default_values()
.iter()
.map(|v| v.to_string_lossy().to_string())
.collect(),
default: default_values(arg),
choices: None,
effect: None,
env: None,
Expand Down
6 changes: 1 addition & 5 deletions lib/src/spec/flag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,11 +535,7 @@ impl From<&clap::Arg> for SpecFlag {
c.get_action(),
clap::ArgAction::Count | clap::ArgAction::Append
);
let default: Vec<String> = c
.get_default_values()
.iter()
.map(|s| s.to_string_lossy().to_string())
.collect();
let default: Vec<String> = crate::spec::arg::default_values(c);
let short = c.get_short_and_visible_aliases().unwrap_or_default();
let long = c
.get_long_and_visible_aliases()
Expand Down
31 changes: 31 additions & 0 deletions lib/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,37 @@ source_code_link_template "https://github.com/jdx/mise/blob/main/src/cli/{{path}
"#);
}

#[test]
#[cfg(feature = "clap")]
fn a_delimited_default_becomes_the_values_clap_would_split_it_into() {
// clap splits by the delimiter before anyone sees a value, defaults included, so the
// joined string is not something the CLI ever holds. The spec has no delimiter — it has
// a list, which says the same thing.
//
// mise's `--fs-events` is why: `default_value = "create,remove,rename,modify,metadata"`
// beside `value_parser` listing those as its choices, so the recorded default was a
// single value its own spec forbade.
let cmd = clap::Command::new("test").arg(
clap::Arg::new("events")
.long("events")
.value_delimiter(',')
.action(clap::ArgAction::Append)
.value_parser(["a", "b", "c"])
.default_value("a,b"),
);
let spec = Spec::from(&cmd);
let flag = spec.cmd.flags.iter().find(|f| f.name == "events").unwrap();
assert_eq!(flag.default, ["a", "b"]);

// And without a delimiter the value is whatever was written, commas and all: a path list
// is not every CLI's idea of a separator, so splitting on speculation would be worse.
let cmd = clap::Command::new("test")
.arg(clap::Arg::new("events").long("events").default_value("a,b"));
let spec = Spec::from(&cmd);
let flag = spec.cmd.flags.iter().find(|f| f.name == "events").unwrap();
assert_eq!(flag.default, ["a,b"]);
}

macro_rules! extract_usage_tests {
($($name:ident: $input:expr, $expected:expr,)*) => {
$(
Expand Down