Skip to content

Commit bbd82b9

Browse files
committed
Refactor parsing code for readabilly
Signed-off-by: Brian Hardock <brian.hardock@fermyon.com>
1 parent 891e202 commit bbd82b9

2 files changed

Lines changed: 184 additions & 73 deletions

File tree

src/commands/up.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -957,4 +957,44 @@ mod test {
957957
assert_eq!("-L", groups[2][0]);
958958
assert_eq!("/fie", groups[2][1]);
959959
}
960+
961+
#[test]
962+
fn splits_up_and_trigger_args() {
963+
// Mix known UpCommandInner flags with unknown trigger-specific flags.
964+
let cmd = UpCommand::try_parse_from([
965+
"up",
966+
"-f",
967+
"app.wasm",
968+
"--direct-mounts",
969+
"-e",
970+
"KEY=VAL",
971+
"--listen",
972+
"127.0.0.1:3000",
973+
"--unknown-trigger-flag",
974+
"trigger-value",
975+
])
976+
.unwrap();
977+
978+
// Known flags are routed into UpCommandInner fields.
979+
assert_eq!(cmd.0.app_source.as_deref(), Some("app.wasm"));
980+
assert!(cmd.0.direct_mounts);
981+
assert_eq!(cmd.0.env, vec![("KEY".to_owned(), "VAL".to_owned())]);
982+
983+
// Unknown flags end up in trigger_args.
984+
let ta: Vec<&str> = cmd
985+
.0
986+
.trigger_args
987+
.iter()
988+
.map(|s| s.to_str().unwrap())
989+
.collect();
990+
assert_eq!(
991+
ta,
992+
[
993+
"--listen",
994+
"127.0.0.1:3000",
995+
"--unknown-trigger-flag",
996+
"trigger-value"
997+
]
998+
);
999+
}
9601000
}

src/commands/up/parsing.rs

Lines changed: 144 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,148 @@ fn arg_num_vals(arg: &clap::Arg) -> usize {
2828
}
2929
}
3030

31+
/// Lookup tables mapping flag names to their argument definitions.
32+
///
33+
/// Used to identify which flags belong to `UpCommandInner` vs. trigger-specific
34+
/// flags that should be passed through untouched.
35+
struct FlagIndex<'a> {
36+
long: HashMap<&'a str, &'a clap::Arg>,
37+
short: HashMap<char, &'a clap::Arg>,
38+
}
39+
40+
impl<'a> FlagIndex<'a> {
41+
fn from_command(cmd: &'a clap::Command) -> Self {
42+
let mut long = HashMap::new();
43+
let mut short = HashMap::new();
44+
for arg in cmd.get_arguments() {
45+
for name in arg
46+
.get_long()
47+
.into_iter()
48+
.chain(arg.get_all_aliases().unwrap_or_default())
49+
{
50+
long.insert(name, arg);
51+
}
52+
for ch in arg
53+
.get_short()
54+
.into_iter()
55+
.chain(arg.get_all_short_aliases().unwrap_or_default())
56+
{
57+
short.insert(ch, arg);
58+
}
59+
}
60+
Self { long, short }
61+
}
62+
}
63+
64+
/// Consume the next `n` raw values from the cursor and append them to `dest`.
65+
fn take_values(
66+
dest: &mut Vec<OsString>,
67+
raw_args: &clap_lex::RawArgs,
68+
cursor: &mut clap_lex::ArgCursor,
69+
n: usize,
70+
) {
71+
for _ in 0..n {
72+
dest.extend(raw_args.next_os(cursor).map(ToOwned::to_owned));
73+
}
74+
}
75+
76+
/// Split raw arguments into those recognized by `UpCommandInner` ("up args")
77+
/// and everything else ("trigger args").
78+
///
79+
/// Unrecognized arguments are kept as trigger args so that each trigger's
80+
/// own parser can handle them. If something truly invalid slips through,
81+
/// the later `try_parse_from` call will produce a clear clap error.
82+
fn split_args(flags: &FlagIndex, raw_args: clap_lex::RawArgs) -> (Vec<OsString>, Vec<OsString>) {
83+
let mut up_args: Vec<OsString> = vec!["spin up".into()];
84+
let mut trigger_args: Vec<OsString> = vec![];
85+
let mut cursor = raw_args.cursor();
86+
87+
while let Some(parsed) = raw_args.next(&mut cursor) {
88+
if let Some((Ok(long), eq_value)) = parsed.to_long() {
89+
classify_long_flag(
90+
long,
91+
eq_value,
92+
&parsed,
93+
flags,
94+
&raw_args,
95+
&mut cursor,
96+
&mut up_args,
97+
&mut trigger_args,
98+
);
99+
} else if let Some(shorts) = parsed.to_short() {
100+
classify_short_flags(
101+
shorts,
102+
flags,
103+
&raw_args,
104+
&mut cursor,
105+
&mut up_args,
106+
&mut trigger_args,
107+
);
108+
} else {
109+
// Not a flag — UpCommandInner has no positional args, so this
110+
// belongs to a trigger.
111+
trigger_args.push(parsed.to_value_os().into());
112+
}
113+
}
114+
115+
(up_args, trigger_args)
116+
}
117+
118+
/// Route a `--long` flag (and its value(s)) to either up_args or trigger_args.
119+
#[allow(clippy::too_many_arguments)]
120+
fn classify_long_flag(
121+
long: &str,
122+
eq_value: Option<&OsStr>,
123+
parsed: &clap_lex::ParsedArg<'_>,
124+
flags: &FlagIndex,
125+
raw_args: &clap_lex::RawArgs,
126+
cursor: &mut clap_lex::ArgCursor,
127+
up_args: &mut Vec<OsString>,
128+
trigger_args: &mut Vec<OsString>,
129+
) {
130+
let Some(arg) = flags.long.get(long) else {
131+
trigger_args.push(parsed.to_value_os().into());
132+
return;
133+
};
134+
135+
up_args.push(parsed.to_value_os().to_os_string());
136+
137+
// Determine how many trailing values to consume. If the value was
138+
// provided inline (e.g. `--key=value`), one has already been consumed.
139+
let mut remaining = arg_num_vals(arg);
140+
if remaining > 0 && eq_value.is_some() {
141+
remaining -= 1;
142+
}
143+
take_values(up_args, raw_args, cursor, remaining);
144+
}
145+
146+
/// Route each flag in a short-flag cluster (e.g. `-abc`) to up_args or
147+
/// trigger_args. Clap allows stacking, so `-abc` is equivalent to `-a -b -c`.
148+
fn classify_short_flags(
149+
shorts: clap_lex::ShortFlags<'_>,
150+
flags: &FlagIndex,
151+
raw_args: &clap_lex::RawArgs,
152+
cursor: &mut clap_lex::ArgCursor,
153+
up_args: &mut Vec<OsString>,
154+
trigger_args: &mut Vec<OsString>,
155+
) {
156+
for short_res in shorts {
157+
match short_res {
158+
Ok(ch) if flags.short.contains_key(&ch) => {
159+
up_args.push(format!("-{ch}").into());
160+
let remaining = arg_num_vals(flags.short[&ch]);
161+
take_values(up_args, raw_args, cursor, remaining);
162+
}
163+
Ok(ch) => {
164+
trigger_args.push(format!("-{ch}").into());
165+
}
166+
Err(invalid) => {
167+
trigger_args.push(OsString::from_iter([OsStr::new("-"), invalid]));
168+
}
169+
}
170+
}
171+
}
172+
31173
impl Args for UpCommand {
32174
fn augment_args(cmd: clap::Command) -> clap::Command {
33175
// The FromArgMatches impl below depends on some restrictions on
@@ -61,81 +203,10 @@ impl Args for UpCommand {
61203
impl clap::FromArgMatches for UpCommand {
62204
fn from_arg_matches(matches: &clap::ArgMatches) -> std::result::Result<Self, clap::Error> {
63205
let cmd = UpCommandInner::command();
206+
let flags = FlagIndex::from_command(&cmd);
64207

65-
// Build our own maps of flags -> arguments, because clap doesn't like to share
66-
let mut long_flags = HashMap::new();
67-
let mut short_flags = HashMap::new();
68-
for arg in cmd.get_arguments() {
69-
for long in arg
70-
.get_long()
71-
.into_iter()
72-
.chain(arg.get_all_aliases().unwrap_or_default())
73-
{
74-
long_flags.insert(long, arg);
75-
}
76-
for short in arg
77-
.get_short()
78-
.into_iter()
79-
.chain(arg.get_all_short_aliases().unwrap_or_default())
80-
{
81-
short_flags.insert(short, arg);
82-
}
83-
}
84-
85-
// Use clap's own lexer against it
86208
let raw_args = clap_lex::RawArgs::new(matches.get_raw("args").into_iter().flatten());
87-
let mut cursor = raw_args.cursor();
88-
89-
// Split the arguments into those recognized by UpInnerCommand and all
90-
// others. As a general strategy if we see something unexpected at this
91-
// stage we let it through with the expectation that later parsing will
92-
// catch it and produce a nice clap error.
93-
let mut up_args = vec!["spin up".into()];
94-
let mut trigger_args = vec![];
95-
while let Some(parsed) = raw_args.next(&mut cursor) {
96-
// --long flags
97-
if let Some((Ok(long), eq_value)) = parsed.to_long() {
98-
if let Some(arg) = long_flags.get(long) {
99-
up_args.push(parsed.to_value_os().to_os_string());
100-
// We asserted earlier that there are a fixed number of values
101-
let mut need_args = arg_num_vals(arg);
102-
// For --key=value args `value` needs to be counted
103-
if need_args > 0 && eq_value.is_some() {
104-
need_args -= 1;
105-
}
106-
// Grab any required value(s)
107-
for _ in 0..need_args {
108-
up_args.extend(raw_args.next_os(&mut cursor).map(ToOwned::to_owned));
109-
}
110-
} else {
111-
trigger_args.push(parsed.to_value_os().into());
112-
}
113-
// -short flags; note that clap allows "stacked" flags, i.e. `-abc` == `-a -b -c`
114-
} else if let Some(shorts) = parsed.to_short() {
115-
for short_res in shorts {
116-
match short_res {
117-
Ok(short) if short_flags.contains_key(&short) => {
118-
up_args.push(format!("-{short}").into());
119-
let arg = short_flags[&short];
120-
let need_args = arg_num_vals(arg);
121-
for _ in 0..need_args {
122-
up_args
123-
.extend(raw_args.next_os(&mut cursor).map(ToOwned::to_owned));
124-
}
125-
}
126-
Ok(short) => {
127-
trigger_args.push(format!("-{short}").into());
128-
}
129-
Err(invalid) => {
130-
trigger_args.push(OsString::from_iter([OsStr::new("-"), invalid]));
131-
}
132-
}
133-
}
134-
} else {
135-
// We asserted that UpCommandInner has no positional args
136-
trigger_args.push(parsed.to_value_os().into());
137-
}
138-
}
209+
let (up_args, trigger_args) = split_args(&flags, raw_args);
139210

140211
let mut inner = UpCommandInner::try_parse_from(up_args)?;
141212
inner.trigger_args = trigger_args;

0 commit comments

Comments
 (0)