Skip to content
Open
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
41 changes: 38 additions & 3 deletions lib/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,11 +454,11 @@ fn parse_partial_with_env(
if enable_flags && w.starts_with("--") {
grouped_flag = false;
let (word, val) = w.split_once('=').unwrap_or_else(|| (&w, ""));
if !val.is_empty() {
input.push_front(val.to_string());
}
if let Some(f) = out.available_flags.get(word) {
if f.arg.is_some() {
if !val.is_empty() {
input.push_front(val.to_string());
}
out.flag_awaiting_value.push(Arc::clone(f));
Comment on lines 458 to 462
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation moves the push_front logic inside the f.arg.is_some() block. While this correctly fixes the issue for unknown flags, it introduces a change in behavior for known flags that do not take arguments (e.g., boolean flags).

Previously, a value provided via = to a boolean flag (e.g., --verbose=true) would be pushed back and treated as a subsequent positional argument. With this change, that value is silently swallowed.

To maintain consistency with the short-flag parsing logic (lines 488-494), which pushes the remainder regardless of whether the flag takes an argument, consider moving the push_front call outside the f.arg.is_some() check but still within the if let Some(f) block. This ensures that unknown flags are still treated as single positional arguments while preserving existing behavior for known flags.

                if !val.is_empty() {
                    input.push_front(val.to_string());
                }
                if f.arg.is_some() {
                    out.flag_awaiting_value.push(Arc::clone(f));

} else if f.count {
let arr = out
Expand Down Expand Up @@ -1312,6 +1312,41 @@ mod tests {
}
}

#[test]
fn test_arg_var_true_unknown_long_eq_flag_not_split() {
// Regression: `--unknown=value` against a spec with only a variadic
// positional must be captured as a single arg, not split into
// `--unknown=value` plus a trailing `value`.
let cmd = SpecCommand::builder()
.name("test")
.arg(
SpecArg::builder()
.name("args")
.var(true)
.required(false)
.build(),
)
.build();
let spec = Spec {
name: "test".to_string(),
bin: "test".to_string(),
cmd,
..Default::default()
};

let input = vec!["test".to_string(), "--depth=3".to_string()];
let parsed = parse(&spec, &input).unwrap();

assert_eq!(parsed.args.len(), 1);
let value = parsed.args.values().next().unwrap();
match value {
ParseValue::MultiString(v) => {
assert_eq!(v, &vec!["--depth=3".to_string()]);
}
_ => panic!("Expected MultiString, got {:?}", value),
}
}

#[test]
fn test_arg_var_false_with_default_remains_string() {
// When arg has var=false (default), the default should still be String
Expand Down