Skip to content

Commit 8186450

Browse files
committed
rewatch: harden default command parsing
1 parent 75b10d3 commit 8186450

File tree

2 files changed

+48
-15
lines changed

2 files changed

+48
-15
lines changed

rewatch/src/cli.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ pub enum FileExtension {
2020

2121
/// ReScript - Fast, Simple, Fully Typed JavaScript from the Future
2222
#[derive(Parser, Debug)]
23+
#[command(name = "rescript", bin_name = "rescript")]
2324
#[command(version)]
2425
#[command(
2526
after_help = "Note: If no command is provided, the build command is run by default. See `rescript help build` for more information."

rewatch/src/main.rs

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,7 @@ fn parse_cli(raw_args: Vec<OsString>) -> Result<cli::Cli, clap::Error> {
120120
Ok(cli) => Ok(cli),
121121
Err(err) => {
122122
if should_default_to_build(&err, &raw_args) {
123-
let mut fallback_args = raw_args.clone();
124-
let insert_at = index_after_global_flags(&fallback_args);
125-
fallback_args.insert(insert_at, OsString::from("build"));
123+
let fallback_args = build_default_args(&raw_args);
126124

127125
match cli::Cli::try_parse_from(&fallback_args) {
128126
Ok(cli) => Ok(cli),
@@ -151,18 +149,6 @@ fn should_default_to_build(err: &clap::Error, args: &[OsString]) -> bool {
151149
}
152150
}
153151

154-
fn index_after_global_flags(args: &[OsString]) -> usize {
155-
let mut idx = 1;
156-
while let Some(arg) = args.get(idx) {
157-
if is_global_flag(arg) {
158-
idx += 1;
159-
} else {
160-
break;
161-
}
162-
}
163-
idx.min(args.len())
164-
}
165-
166152
fn is_global_flag(arg: &OsString) -> bool {
167153
matches!(
168154
arg.to_str(),
@@ -198,9 +184,44 @@ fn is_known_subcommand(arg: &OsString) -> bool {
198184
})
199185
}
200186

187+
fn build_default_args(raw_args: &[OsString]) -> Vec<OsString> {
188+
let mut result = Vec::with_capacity(raw_args.len() + 1);
189+
if raw_args.is_empty() {
190+
return vec![OsString::from("build")];
191+
}
192+
193+
let mut globals = Vec::new();
194+
let mut others = Vec::new();
195+
let mut saw_double_dash = false;
196+
197+
for arg in raw_args.iter().skip(1) {
198+
if !saw_double_dash {
199+
if arg == "--" {
200+
saw_double_dash = true;
201+
others.push(arg.clone());
202+
continue;
203+
}
204+
205+
if is_global_flag(arg) {
206+
globals.push(arg.clone());
207+
continue;
208+
}
209+
}
210+
211+
others.push(arg.clone());
212+
}
213+
214+
result.push(raw_args[0].clone());
215+
result.extend(globals);
216+
result.push(OsString::from("build"));
217+
result.extend(others);
218+
result
219+
}
220+
201221
#[cfg(test)]
202222
mod tests {
203223
use super::*;
224+
use log::LevelFilter;
204225
use std::ffi::OsString;
205226

206227
fn parse(args: &[&str]) -> Result<cli::Cli, clap::Error> {
@@ -234,6 +255,17 @@ mod tests {
234255
assert!(matches!(cli.command, cli::Command::Watch(_)));
235256
}
236257

258+
#[test]
259+
fn trailing_global_flag_is_treated_as_global() {
260+
let cli = parse(&["rescript", "my-project", "-v"]).expect("expected build command");
261+
262+
assert_eq!(cli.verbose.log_level_filter(), LevelFilter::Debug);
263+
match cli.command {
264+
cli::Command::Build(build_args) => assert_eq!(build_args.folder.folder, "my-project"),
265+
other => panic!("expected build command, got {other:?}"),
266+
}
267+
}
268+
237269
#[test]
238270
fn invalid_option_for_subcommand_does_not_fallback() {
239271
let err = parse(&["rescript", "watch", "--no-timing"]).expect_err("expected watch parse failure");

0 commit comments

Comments
 (0)