1use crate::cli::custom_check_opts::CustomCheckOpts;
2use crate::cli::rust_releases_opts::RustReleasesOpts;
3use crate::cli::shared_opts::SharedOpts;
4use crate::cli::toolchain_opts::ToolchainOpts;
5use crate::context::list::ListMsrvVariant;
6use crate::manifest::bare_version::BareVersion;
7use clap::{Args, Parser, Subcommand};
8use clap_cargo::style::CLAP_STYLING;
9use std::ffi::{OsStr, OsString};
10
11pub(crate) mod custom_check_opts;
12pub(crate) mod rust_releases_opts;
13pub(crate) mod shared_opts;
14pub(crate) mod toolchain_opts;
15
16#[derive(Debug, Parser)]
17#[command(version, name = "cargo", bin_name = "cargo", max_term_width = 120, styles = CLAP_STYLING)]
18pub struct CargoCli {
19 #[command(subcommand)]
20 subcommand: CargoMsrvCli,
21}
22
23impl CargoCli {
24 pub fn parse_args<I: IntoIterator<Item = T>, T: Into<OsString> + Clone>(args: I) -> Self {
25 let modified_args = modify_args(args);
26 CargoCli::parse_from(modified_args)
27 }
28
29 pub fn to_cargo_msrv_cli(self) -> CargoMsrvCli {
30 self.subcommand
31 }
32}
33
34fn modify_args<I: IntoIterator<Item = T>, T: Into<OsString> + Clone>(
40 args: I,
41) -> impl IntoIterator<Item = OsString> {
42 let mut args = args.into_iter().map(Into::into).collect::<Vec<_>>();
43
44 if args.len() >= 2 {
45 let program: &OsStr = args[0].as_os_str();
46 let program = program.to_string_lossy();
47
48 if program.ends_with("cargo-msrv") || program.ends_with("cargo-msrv.exe") {
50 args[0] = OsStr::new("cargo").to_os_string();
52
53 let cargo_msrv_subcmd: &OsStr = args[1].as_os_str();
54 let cargo_msrv_subcmd = cargo_msrv_subcmd.to_string_lossy();
55
56 if cargo_msrv_subcmd != "msrv" {
57 args.insert(1, OsStr::new("msrv").to_os_string());
58 }
59 }
60 }
61
62 args
63}
64
65#[derive(Debug, Subcommand)]
66pub enum CargoMsrvCli {
67 #[command(
69 author = "Martijn Gribnau <garm@ilumeo.com>",
70 after_help = indoc::indoc!{"
71 You can provide a custom compatibility check command as the last positional argument via
72 the -- syntax, e.g. `$ cargo msrv find -- my custom command`.
73
74 This custom check command will then be used to validate whether a Rust version is
75 compatible.
76
77 A custom `check` command should be runnable by rustup, as it will be passed to
78 rustup like so: `rustup run <toolchain> <COMMAND...>`.
79 NB: You only need to provide the <COMMAND...> part.
80
81 By default, the compatibility check command is `cargo check`.
82 "}
83 )]
84 Msrv(CargoMsrvOpts),
85}
86
87impl CargoMsrvCli {
88 pub fn to_opts(self) -> CargoMsrvOpts {
89 match self {
90 Self::Msrv(opts) => opts,
91 }
92 }
93}
94
95#[derive(Debug, Args)]
96#[command(version)]
97pub struct CargoMsrvOpts {
98 #[command(flatten)]
99 pub shared_opts: SharedOpts,
100
101 #[command(subcommand)]
102 pub subcommand: SubCommand,
103}
104
105#[derive(Debug, Subcommand)]
106#[command(propagate_version = true)]
107pub enum SubCommand {
108 Find(FindOpts),
110 List(ListOpts),
112 Set(SetOpts),
114 Show,
116 Verify(VerifyOpts),
120}
121
122#[derive(Debug, Args)]
124#[command(next_help_heading = "Find MSRV options")]
125pub struct FindOpts {
126 #[arg(long, conflicts_with = "linear")]
132 pub bisect: bool,
133
134 #[arg(long, conflicts_with = "bisect")]
138 pub linear: bool,
139
140 #[arg(long, alias = "toolchain-file")]
144 pub write_toolchain_file: bool,
145
146 #[arg(long)]
151 pub ignore_lockfile: bool,
152
153 #[arg(long)]
158 pub skip_unavailable_toolchains: bool,
159
160 #[arg(long)]
167 pub no_check_feedback: bool,
168
169 #[arg(long, visible_alias = "set")]
175 pub write_msrv: bool,
176
177 #[command(flatten)]
178 pub rust_releases_opts: RustReleasesOpts,
179
180 #[command(flatten)]
181 pub toolchain_opts: ToolchainOpts,
182
183 #[command(flatten)]
184 pub custom_check_opts: CustomCheckOpts,
185}
186
187#[derive(Debug, Args)]
188#[command(next_help_heading = "List options")]
189pub struct ListOpts {
190 #[arg(long, value_enum, default_value_t)]
192 pub variant: ListMsrvVariant,
193}
194
195#[derive(Debug, Args)]
196#[command(next_help_heading = "Set options")]
197pub struct SetOpts {
198 #[arg(value_name = "MSRV")]
205 pub msrv: BareVersion,
206
207 #[command(flatten)]
208 pub rust_releases_opts: RustReleasesOpts,
209}
210
211#[derive(Debug, Args)]
212#[command(next_help_heading = "Verify options")]
213pub struct VerifyOpts {
214 #[arg(long)]
216 pub ignore_lockfile: bool,
217
218 #[arg(long)]
225 pub no_check_feedback: bool,
226
227 #[command(flatten)]
228 pub rust_releases_opts: RustReleasesOpts,
229
230 #[arg(long, value_name = "rust-version")]
234 pub rust_version: Option<BareVersion>,
235
236 #[command(flatten)]
237 pub toolchain_opts: ToolchainOpts,
238
239 #[command(flatten)]
240 pub custom_check_opts: CustomCheckOpts,
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn verify_cli() {
249 use clap::CommandFactory;
250 CargoCli::command().debug_assert();
251 }
252
253 mod top_level {
254 use super::*;
255
256 fn assert_find_opts(opts: CargoMsrvOpts, assertions: impl Fn(FindOpts)) {
257 if let SubCommand::Find(find_opts) = opts.subcommand {
258 assertions(find_opts);
259 return;
260 }
261
262 panic!("Assertion failed: expected subcommand 'cargo msrv find'");
263 }
264
265 mod find_opts {
266 use super::*;
267
268 #[test]
269 fn has_bisect() {
270 let cargo = CargoCli::parse_args(["cargo", "msrv", "find", "--bisect"]);
271 let cargo_msrv = cargo.to_cargo_msrv_cli();
272 let opts = cargo_msrv.to_opts();
273
274 assert_find_opts(opts, |find_opts| {
275 assert!(find_opts.bisect);
276 assert!(!find_opts.linear);
277 });
278 }
279
280 #[test]
281 fn has_not_bisect() {
282 let cargo = CargoCli::parse_args(["cargo", "msrv", "find"]);
283 let cargo_msrv = cargo.to_cargo_msrv_cli();
284 let opts = cargo_msrv.to_opts();
285
286 assert_find_opts(opts, |find_opts| {
287 assert!(!find_opts.bisect);
288 });
289 }
290
291 #[test]
292 fn has_linear() {
293 let cargo = CargoCli::parse_args(["cargo", "msrv", "find", "--linear"]);
294 let cargo_msrv = cargo.to_cargo_msrv_cli();
295 let opts = cargo_msrv.to_opts();
296
297 assert_find_opts(opts, |find_opts| {
298 assert!(find_opts.linear);
299 assert!(!find_opts.bisect);
300 });
301 }
302
303 #[test]
304 fn has_not_linear() {
305 let cargo = CargoCli::parse_args(["cargo", "msrv", "find"]);
306 let cargo_msrv = cargo.to_cargo_msrv_cli();
307 let opts = cargo_msrv.to_opts();
308
309 assert_find_opts(opts, |find_opts| {
310 assert!(!find_opts.linear);
311 });
312 }
313
314 #[test]
315 fn has_write_toolchain_file() {
316 let cargo =
317 CargoCli::parse_args(["cargo", "msrv", "find", "--write-toolchain-file"]);
318 let cargo_msrv = cargo.to_cargo_msrv_cli();
319 let opts = cargo_msrv.to_opts();
320
321 assert_find_opts(opts, |find_opts| {
322 assert!(find_opts.write_toolchain_file);
323 });
324 }
325
326 #[test]
327 fn has_not_write_toolchain_file() {
328 let cargo = CargoCli::parse_args(["cargo", "msrv", "find"]);
329 let cargo_msrv = cargo.to_cargo_msrv_cli();
330 let opts = cargo_msrv.to_opts();
331
332 assert_find_opts(opts, |find_opts| {
333 assert!(!find_opts.write_toolchain_file);
334 });
335 }
336
337 #[test]
338 fn has_ignore_lockfile() {
339 let cargo = CargoCli::parse_args(["cargo", "msrv", "find", "--ignore-lockfile"]);
340 let cargo_msrv = cargo.to_cargo_msrv_cli();
341 let opts = cargo_msrv.to_opts();
342
343 assert_find_opts(opts, |find_opts| {
344 assert!(find_opts.ignore_lockfile);
345 });
346 }
347
348 #[test]
349 fn has_not_ignore_lockfile() {
350 let cargo = CargoCli::parse_args(["cargo", "msrv", "find"]);
351 let cargo_msrv = cargo.to_cargo_msrv_cli();
352 let opts = cargo_msrv.to_opts();
353
354 assert_find_opts(opts, |find_opts| {
355 assert!(!find_opts.ignore_lockfile);
356 });
357 }
358
359 #[test]
360 fn has_no_check_feedback() {
361 let cargo = CargoCli::parse_args(["cargo", "msrv", "find", "--no-check-feedback"]);
362 let cargo_msrv = cargo.to_cargo_msrv_cli();
363 let opts = cargo_msrv.to_opts();
364
365 assert_find_opts(opts, |find_opts| {
366 assert!(find_opts.no_check_feedback);
367 });
368 }
369
370 #[test]
371 fn has_not_no_check_feedback() {
372 let cargo = CargoCli::parse_args(["cargo", "msrv", "find"]);
373 let cargo_msrv = cargo.to_cargo_msrv_cli();
374 let opts = cargo_msrv.to_opts();
375
376 assert_find_opts(opts, |find_opts| {
377 assert!(!find_opts.no_check_feedback);
378 });
379 }
380
381 #[test]
382 fn has_write_msrv() {
383 let cargo = CargoCli::parse_args(["cargo", "msrv", "find", "--write-msrv"]);
384 let cargo_msrv = cargo.to_cargo_msrv_cli();
385 let opts = cargo_msrv.to_opts();
386
387 assert_find_opts(opts, |find_opts| {
388 assert!(find_opts.write_msrv);
389 });
390 }
391
392 #[test]
393 fn has_not_write_msrv() {
394 let cargo = CargoCli::parse_args(["cargo", "msrv", "find"]);
395 let cargo_msrv = cargo.to_cargo_msrv_cli();
396 let opts = cargo_msrv.to_opts();
397
398 assert_find_opts(opts, |find_opts| {
399 assert!(!find_opts.write_msrv);
400 });
401 }
402
403 }
409 }
410}