cargo_component/
config.rs

1//! Module for cargo-component configuration.
2//!
3//! This implements an argument parser because `clap` is not
4//! designed for parsing unknown or unsupported arguments.
5//!
6//! See https://github.com/clap-rs/clap/issues/1404 for some
7//! discussion around this issue.
8//!
9//! To properly "wrap" `cargo` commands, we need to be able to
10//! detect certain arguments, but not error out if the arguments
11//! are otherwise unknown as they will be passed to `cargo` directly.
12//!
13//! This will allow `cargo-component` to be used as a drop-in
14//! replacement for `cargo` without having to be fully aware of
15//! the many subcommands and options that `cargo` supports.
16//!
17//! What is detected here is the minimal subset of the arguments
18//! that `cargo` supports which are necessary for `cargo-component`
19//! to function.
20
21use anyhow::{anyhow, bail, Context, Result};
22use cargo_component_core::cache_dir;
23use cargo_component_core::terminal::{Color, Terminal};
24use cargo_metadata::Metadata;
25use parse_arg::{iter_short, match_arg};
26use semver::Version;
27use std::fmt;
28use std::str::FromStr;
29use std::sync::Arc;
30use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
31use toml_edit::DocumentMut;
32use wasm_pkg_client::caching::{CachingClient, FileCache};
33use wasm_pkg_client::Client;
34
35/// Represents a cargo package specifier.
36///
37/// See `cargo help pkgid` for more information.
38#[derive(Debug, Clone, Eq, PartialEq)]
39pub struct CargoPackageSpec {
40    /// The name of the package, e.g. `foo`.
41    pub name: String,
42    /// The version of the package, if specified.
43    pub version: Option<Version>,
44}
45
46impl CargoPackageSpec {
47    /// Creates a new package specifier from a string.
48    pub fn new(spec: impl Into<String>) -> Result<Self> {
49        let spec = spec.into();
50
51        // Bail out if the package specifier contains a URL.
52        if spec.contains("://") {
53            bail!("URL package specifier `{spec}` is not supported");
54        }
55
56        Ok(match spec.split_once('@') {
57            Some((name, version)) => Self {
58                name: name.to_string(),
59                version: Some(
60                    version
61                        .parse()
62                        .with_context(|| format!("invalid package specified `{spec}`"))?,
63                ),
64            },
65            None => Self {
66                name: spec,
67                version: None,
68            },
69        })
70    }
71
72    /// Loads Cargo.toml in the current directory, attempts to find the matching package from metadata.
73    pub fn find_current_package_spec(metadata: &Metadata) -> Option<Self> {
74        let current_manifest = std::fs::read_to_string("Cargo.toml").ok()?;
75        let document: DocumentMut = current_manifest.parse().ok()?;
76        let name = document["package"]["name"].as_str()?;
77        let version = metadata
78            .packages
79            .iter()
80            .find(|found| found.name == name)
81            .map(|found| found.version.clone());
82        Some(CargoPackageSpec {
83            name: name.to_string(),
84            version,
85        })
86    }
87}
88
89impl FromStr for CargoPackageSpec {
90    type Err = anyhow::Error;
91
92    fn from_str(s: &str) -> Result<Self> {
93        Self::new(s)
94    }
95}
96
97impl fmt::Display for CargoPackageSpec {
98    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99        write!(f, "{name}", name = self.name)?;
100        if let Some(version) = &self.version {
101            write!(f, "@{version}")?;
102        }
103
104        Ok(())
105    }
106}
107
108#[derive(Debug, Clone)]
109enum Arg {
110    Flag {
111        name: &'static str,
112        short: Option<char>,
113        value: bool,
114    },
115    Single {
116        name: &'static str,
117        value_name: &'static str,
118        short: Option<char>,
119        value: Option<String>,
120    },
121    Multiple {
122        name: &'static str,
123        value_name: &'static str,
124        short: Option<char>,
125        values: Vec<String>,
126    },
127    Counting {
128        name: &'static str,
129        short: Option<char>,
130        value: usize,
131    },
132}
133
134impl Arg {
135    fn name(&self) -> &'static str {
136        match self {
137            Self::Flag { name, .. }
138            | Self::Single { name, .. }
139            | Self::Multiple { name, .. }
140            | Self::Counting { name, .. } => name,
141        }
142    }
143
144    fn short(&self) -> Option<char> {
145        match self {
146            Self::Flag { short, .. }
147            | Self::Single { short, .. }
148            | Self::Multiple { short, .. }
149            | Self::Counting { short, .. } => *short,
150        }
151    }
152
153    fn expects_value(&self) -> bool {
154        matches!(self, Self::Single { .. } | Self::Multiple { .. })
155    }
156
157    fn set_value(&mut self, v: String) -> Result<()> {
158        match self {
159            Self::Single { value, .. } => {
160                if value.is_some() {
161                    bail!("the argument '{self}' cannot be used multiple times");
162                }
163
164                *value = Some(v);
165                Ok(())
166            }
167            Self::Multiple { values, .. } => {
168                values.push(v);
169                Ok(())
170            }
171            _ => unreachable!(),
172        }
173    }
174
175    fn set_present(&mut self) -> Result<()> {
176        match self {
177            Self::Flag { value, .. } => {
178                if *value {
179                    bail!("the argument '{self}' cannot be used multiple times");
180                }
181
182                *value = true;
183                Ok(())
184            }
185            Self::Counting { value, .. } => {
186                *value += 1;
187                Ok(())
188            }
189            _ => unreachable!(),
190        }
191    }
192
193    fn take_single(&mut self) -> Option<String> {
194        match self {
195            Self::Single { value, .. } => value.take(),
196            _ => None,
197        }
198    }
199
200    fn take_multiple(&mut self) -> Vec<String> {
201        match self {
202            Self::Multiple { values, .. } => std::mem::take(values),
203            _ => Vec::new(),
204        }
205    }
206
207    fn count(&self) -> usize {
208        match self {
209            Arg::Flag { value, .. } => *value as usize,
210            Arg::Single { value, .. } => value.is_some() as usize,
211            Arg::Multiple { values, .. } => values.len(),
212            Arg::Counting { value, .. } => *value,
213        }
214    }
215
216    #[cfg(test)]
217    fn reset(&mut self) {
218        match self {
219            Arg::Flag { value, .. } => *value = false,
220            Arg::Single { value, .. } => *value = None,
221            Arg::Multiple { values, .. } => values.clear(),
222            Arg::Counting { value, .. } => *value = 0,
223        }
224    }
225}
226
227impl Display for Arg {
228    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229        write!(f, "{name}", name = self.name())?;
230        match self {
231            Self::Single { value_name, .. } | Self::Multiple { value_name, .. } => {
232                write!(f, " <{value_name}>")
233            }
234            _ => Ok(()),
235        }
236    }
237}
238
239#[derive(Default, Debug, Clone)]
240struct Args {
241    args: Vec<Arg>,
242    long: BTreeMap<&'static str, usize>,
243    short: BTreeMap<char, usize>,
244}
245
246impl Args {
247    fn flag(self, name: &'static str, short: Option<char>) -> Self {
248        self.insert(Arg::Flag {
249            name,
250            short,
251            value: false,
252        })
253    }
254
255    fn single(self, name: &'static str, value_name: &'static str, short: Option<char>) -> Self {
256        self.insert(Arg::Single {
257            name,
258            value_name,
259            short,
260            value: None,
261        })
262    }
263
264    fn multiple(self, name: &'static str, value_name: &'static str, short: Option<char>) -> Self {
265        self.insert(Arg::Multiple {
266            name,
267            value_name,
268            short,
269            values: Vec::new(),
270        })
271    }
272
273    fn counting(self, name: &'static str, short: Option<char>) -> Self {
274        self.insert(Arg::Counting {
275            name,
276            short,
277            value: 0,
278        })
279    }
280
281    fn get(&mut self, name: &str) -> Option<&Arg> {
282        self.long.get(name).copied().map(|i| &self.args[i])
283    }
284
285    fn get_mut(&mut self, name: &str) -> Option<&mut Arg> {
286        self.long.get(name).copied().map(|i| &mut self.args[i])
287    }
288
289    fn get_short_mut(&mut self, short: char) -> Option<&mut Arg> {
290        self.short.get(&short).copied().map(|i| &mut self.args[i])
291    }
292
293    fn insert(mut self, arg: Arg) -> Self {
294        let name = arg.name();
295        let short = arg.short();
296
297        let index = self.args.len();
298        self.args.push(arg);
299
300        let prev = self.long.insert(name, index);
301        assert!(prev.is_none(), "duplicate argument `{name}` provided");
302
303        if let Some(short) = short {
304            let prev = self.short.insert(short, index);
305            assert!(prev.is_none(), "duplicate argument `-{short}` provided");
306        }
307
308        self
309    }
310
311    /// Parses an argument as an option.
312    ///
313    /// Returns `Ok(true)` if the argument is an option.
314    ///
315    /// Returns `Ok(false)` if the argument is not an option.
316    fn parse(&mut self, arg: &str, iter: &mut impl Iterator<Item = String>) -> Result<bool> {
317        // Handle short options
318        if let Some(mut short) = iter_short(arg) {
319            while let Some(c) = short.next() {
320                if let Some(option) = self.get_short_mut(c) {
321                    if option.expects_value() {
322                        let value: String = short.parse_remaining(iter).map_err(|_| {
323                            anyhow!("a value is required for '{option}' but none was supplied")
324                        })?;
325
326                        // Strip a leading `=` out of the value if present
327                        option
328                            .set_value(value.strip_prefix('=').map(Into::into).unwrap_or(value))?;
329                        return Ok(true);
330                    }
331
332                    option.set_present()?;
333                }
334            }
335
336            // The argument is an option
337            return Ok(true);
338        }
339
340        // Handle long options
341        if arg.starts_with("--") {
342            if let Some(option) = self.get_mut(arg.split_once('=').map(|(n, _)| n).unwrap_or(arg)) {
343                if option.expects_value() {
344                    if let Some(v) = match_arg(option.name(), &arg, iter) {
345                        option.set_value(v.map_err(|_| {
346                            anyhow!("a value is required for '{option}' but none was supplied")
347                        })?)?;
348                    }
349                } else if option.name() == arg {
350                    option.set_present()?;
351                }
352            }
353
354            // The argument is an option
355            return Ok(true);
356        }
357
358        // Not an option
359        Ok(false)
360    }
361}
362
363/// Represents known cargo arguments.
364///
365/// This is a subset of the arguments that cargo supports that
366/// are necessary for cargo-component to function.
367#[derive(Default, Debug, Clone, Eq, PartialEq)]
368pub struct CargoArguments {
369    /// The --color argument.
370    pub color: Option<Color>,
371    /// The (count of) --verbose argument.
372    pub verbose: usize,
373    /// The --help argument.
374    pub help: bool,
375    /// The --quiet argument.
376    pub quiet: bool,
377    /// The --target argument.
378    pub targets: Vec<String>,
379    /// The --manifest-path argument.
380    pub manifest_path: Option<PathBuf>,
381    /// The `--message-format`` argument.
382    pub message_format: Option<String>,
383    /// The --frozen argument.
384    pub frozen: bool,
385    /// The --locked argument.
386    pub locked: bool,
387    /// The --release argument.
388    pub release: bool,
389    /// The --offline argument.
390    pub offline: bool,
391    /// The --workspace argument.
392    pub workspace: bool,
393    /// The --package argument.
394    pub packages: Vec<CargoPackageSpec>,
395}
396
397impl CargoArguments {
398    /// Determines if network access is allowed based on the configuration.
399    pub fn network_allowed(&self) -> bool {
400        !self.frozen && !self.offline
401    }
402
403    /// Determines if an update to the lock file is allowed based on the configuration.
404    pub fn lock_update_allowed(&self) -> bool {
405        !self.frozen && !self.locked
406    }
407
408    /// Parses the arguments from the environment.
409    pub fn parse() -> Result<Self> {
410        Self::parse_from(std::env::args().skip(1))
411    }
412
413    /// Parses the arguments from an iterator.
414    pub fn parse_from<T>(iter: impl Iterator<Item = T>) -> Result<Self>
415    where
416        T: Into<String>,
417    {
418        let mut args = Args::default()
419            .single("--color", "WHEN", Some('c'))
420            .single("--manifest-path", "PATH", None)
421            .single("--message-format", "FMT", None)
422            .multiple("--package", "SPEC", Some('p'))
423            .multiple("--target", "TRIPLE", None)
424            .flag("--release", Some('r'))
425            .flag("--frozen", None)
426            .flag("--locked", None)
427            .flag("--offline", None)
428            .flag("--all", None)
429            .flag("--workspace", None)
430            .counting("--verbose", Some('v'))
431            .flag("--quiet", Some('q'))
432            .flag("--help", Some('h'));
433
434        let mut iter = iter.map(Into::into).peekable();
435
436        // Skip the first argument if it is `component`
437        if let Some(arg) = iter.peek() {
438            if arg == "component" {
439                iter.next().unwrap();
440            }
441        }
442
443        while let Some(arg) = iter.next() {
444            // Break out of processing at the first `--`
445            if arg == "--" {
446                break;
447            }
448
449            // Parse options
450            if args.parse(&arg, &mut iter)? {
451                continue;
452            }
453        }
454
455        Ok(Self {
456            color: args
457                .get_mut("--color")
458                .unwrap()
459                .take_single()
460                .map(|v| v.parse())
461                .transpose()?,
462            verbose: args.get("--verbose").unwrap().count(),
463            help: args.get("--help").unwrap().count() > 0,
464            quiet: args.get("--quiet").unwrap().count() > 0,
465            manifest_path: args
466                .get_mut("--manifest-path")
467                .unwrap()
468                .take_single()
469                .map(PathBuf::from),
470            message_format: args.get_mut("--message-format").unwrap().take_single(),
471            targets: args.get_mut("--target").unwrap().take_multiple(),
472            frozen: args.get("--frozen").unwrap().count() > 0,
473            locked: args.get("--locked").unwrap().count() > 0,
474            offline: args.get("--offline").unwrap().count() > 0,
475            release: args.get("--release").unwrap().count() > 0,
476            workspace: args.get("--workspace").unwrap().count() > 0
477                || args.get("--all").unwrap().count() > 0,
478            packages: args
479                .get_mut("--package")
480                .unwrap()
481                .take_multiple()
482                .into_iter()
483                .map(CargoPackageSpec::new)
484                .collect::<Result<_>>()?,
485        })
486    }
487}
488
489/// Configuration information for cargo-component.
490///
491/// This is used to configure the behavior of cargo-component.
492#[derive(Debug)]
493pub struct Config {
494    /// The package configuration to use
495    pub pkg_config: wasm_pkg_client::Config,
496    /// The terminal to use.
497    terminal: Terminal,
498}
499
500impl Config {
501    /// Create a new `Config` with the given terminal.
502    pub async fn new(terminal: Terminal, config_path: Option<PathBuf>) -> Result<Self> {
503        let pkg_config = match config_path {
504            Some(path) => wasm_pkg_client::Config::from_file(path).await?,
505            None => wasm_pkg_client::Config::global_defaults().await?,
506        };
507        Ok(Self {
508            pkg_config,
509            terminal,
510        })
511    }
512
513    /// Gets the package configuration.
514    pub fn pkg_config(&self) -> &wasm_pkg_client::Config {
515        &self.pkg_config
516    }
517
518    /// Gets a reference to the terminal for writing messages.
519    pub fn terminal(&self) -> &Terminal {
520        &self.terminal
521    }
522
523    /// Creates a [`Client`] from this configuration.
524    pub async fn client(
525        &self,
526        cache: Option<PathBuf>,
527        offline: bool,
528    ) -> anyhow::Result<Arc<CachingClient<FileCache>>> {
529        Ok(Arc::new(CachingClient::new(
530            (!offline).then(|| Client::new(self.pkg_config.clone())),
531            FileCache::new(cache_dir(cache)?).await?,
532        )))
533    }
534}
535
536#[cfg(test)]
537mod test {
538    use super::*;
539    use std::iter::empty;
540
541    #[test]
542    fn it_parses_flags() {
543        let mut args = Args::default().flag("--flag", Some('f'));
544
545        // Test not the flag
546        args.parse("--not-flag", &mut empty::<String>()).unwrap();
547        let arg = args.get("--flag").unwrap();
548        assert_eq!(arg.count(), 0);
549
550        // Test the flag
551        args.parse("--flag", &mut empty::<String>()).unwrap();
552        assert_eq!(
553            args.parse("--flag", &mut empty::<String>())
554                .unwrap_err()
555                .to_string(),
556            "the argument '--flag' cannot be used multiple times"
557        );
558        let arg = args.get_mut("--flag").unwrap();
559        assert_eq!(arg.count(), 1);
560        arg.reset();
561
562        // Test not the short flag
563        args.parse("-rxd", &mut empty::<String>()).unwrap();
564        let arg = args.get("--flag").unwrap();
565        assert_eq!(arg.count(), 0);
566
567        // Test the short flag
568        args.parse("-rfx", &mut empty::<String>()).unwrap();
569        assert_eq!(
570            args.parse("-fxz", &mut empty::<String>())
571                .unwrap_err()
572                .to_string(),
573            "the argument '--flag' cannot be used multiple times"
574        );
575        let arg = args.get("--flag").unwrap();
576        assert_eq!(arg.count(), 1);
577
578        // Test it prints correctly
579        assert_eq!(arg.to_string(), "--flag")
580    }
581
582    #[test]
583    fn it_parses_single_values() {
584        let mut args = Args::default().single("--option", "VALUE", Some('o'));
585
586        // Test not the option
587        args.parse("--not-option", &mut empty::<String>()).unwrap();
588        let arg = args.get_mut("--option").unwrap();
589        assert_eq!(arg.take_single(), None);
590
591        // Test missing value
592        assert_eq!(
593            args.parse("--option", &mut empty::<String>())
594                .unwrap_err()
595                .to_string(),
596            "a value is required for '--option <VALUE>' but none was supplied"
597        );
598
599        // Test the option with equals
600        args.parse("--option=value", &mut empty::<String>())
601            .unwrap();
602        assert_eq!(
603            args.parse("--option=value", &mut empty::<String>())
604                .unwrap_err()
605                .to_string(),
606            "the argument '--option <VALUE>' cannot be used multiple times"
607        );
608        let arg = args.get_mut("--option").unwrap();
609        assert_eq!(arg.take_single(), Some("value".to_string()));
610        arg.reset();
611
612        // Test the option with space
613        let mut iter = ["value".to_string()].into_iter();
614        args.parse("--option", &mut iter).unwrap();
615        assert!(iter.next().is_none());
616        let mut iter = ["value".to_string()].into_iter();
617        assert_eq!(
618            args.parse("--option", &mut iter).unwrap_err().to_string(),
619            "the argument '--option <VALUE>' cannot be used multiple times"
620        );
621        let arg = args.get_mut("--option").unwrap();
622        assert_eq!(arg.take_single(), Some("value".to_string()));
623        arg.reset();
624
625        // Test not the short option
626        args.parse("-xyz", &mut empty::<String>()).unwrap();
627        let arg = args.get_mut("--option").unwrap();
628        assert_eq!(arg.take_single(), None);
629
630        assert_eq!(
631            args.parse("-fo", &mut empty::<String>())
632                .unwrap_err()
633                .to_string(),
634            "a value is required for '--option <VALUE>' but none was supplied"
635        );
636
637        // Test the short option without equals
638        args.parse("-xofoo", &mut empty::<String>()).unwrap();
639        assert_eq!(
640            args.parse("-zyobar", &mut iter).unwrap_err().to_string(),
641            "the argument '--option <VALUE>' cannot be used multiple times"
642        );
643        let arg = args.get_mut("--option").unwrap();
644        assert_eq!(arg.take_single(), Some(String::from("foo")));
645
646        // Test the short option with equals
647        args.parse("-xo=foo", &mut empty::<String>()).unwrap();
648        assert_eq!(
649            args.parse("-zyo=bar", &mut iter).unwrap_err().to_string(),
650            "the argument '--option <VALUE>' cannot be used multiple times"
651        );
652        let arg = args.get_mut("--option").unwrap();
653        assert_eq!(arg.take_single(), Some(String::from("foo")));
654
655        // Test the short option with space
656        let mut iter = ["value".to_string()].into_iter();
657        args.parse("-xo", &mut iter).unwrap();
658        let mut iter = ["value".to_string()].into_iter();
659        assert_eq!(
660            args.parse("-zyo", &mut iter).unwrap_err().to_string(),
661            "the argument '--option <VALUE>' cannot be used multiple times"
662        );
663        let arg = args.get_mut("--option").unwrap();
664        assert_eq!(arg.take_single(), Some(String::from("value")));
665
666        // Test it prints correctly
667        assert_eq!(arg.to_string(), "--option <VALUE>")
668    }
669
670    #[test]
671    fn it_parses_multiple_values() {
672        let mut args = Args::default().multiple("--option", "VALUE", Some('o'));
673
674        // Test not the option
675        args.parse("--not-option", &mut empty::<String>()).unwrap();
676        let arg = args.get_mut("--option").unwrap();
677        assert_eq!(arg.take_multiple(), Vec::<String>::new());
678
679        // Test missing value
680        assert_eq!(
681            args.parse("--option", &mut empty::<String>())
682                .unwrap_err()
683                .to_string(),
684            "a value is required for '--option <VALUE>' but none was supplied"
685        );
686
687        // Test the option with equals
688        args.parse("--option=foo", &mut empty::<String>()).unwrap();
689        args.parse("--option=bar", &mut empty::<String>()).unwrap();
690        args.parse("--option=baz", &mut empty::<String>()).unwrap();
691        let arg = args.get_mut("--option").unwrap();
692        assert_eq!(
693            arg.take_multiple(),
694            vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),]
695        );
696        arg.reset();
697
698        // Test the option with space
699        let mut iter = ["foo".to_string()].into_iter();
700        args.parse("--option", &mut iter).unwrap();
701        assert!(iter.next().is_none());
702        let mut iter = ["bar".to_string()].into_iter();
703        args.parse("--option", &mut iter).unwrap();
704        assert!(iter.next().is_none());
705        let mut iter = ["baz".to_string()].into_iter();
706        args.parse("--option", &mut iter).unwrap();
707        assert!(iter.next().is_none());
708        let arg = args.get_mut("--option").unwrap();
709        assert_eq!(
710            arg.take_multiple(),
711            vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),]
712        );
713        arg.reset();
714
715        // Test not the short option
716        args.parse("-xyz", &mut empty::<String>()).unwrap();
717        let arg = args.get_mut("--option").unwrap();
718        assert_eq!(arg.take_single(), None);
719
720        // Test missing shot option value
721        assert_eq!(
722            args.parse("-fo", &mut empty::<String>())
723                .unwrap_err()
724                .to_string(),
725            "a value is required for '--option <VALUE>' but none was supplied"
726        );
727
728        // Test the short option without equals
729        args.parse("-xofoo", &mut empty::<String>()).unwrap();
730        args.parse("-yobar", &mut empty::<String>()).unwrap();
731        args.parse("-zobaz", &mut empty::<String>()).unwrap();
732        let arg = args.get_mut("--option").unwrap();
733        assert_eq!(
734            arg.take_multiple(),
735            vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),]
736        );
737
738        // Test the short option with equals
739        args.parse("-xo=foo", &mut empty::<String>()).unwrap();
740        args.parse("-yo=bar", &mut empty::<String>()).unwrap();
741        args.parse("-zo=baz", &mut empty::<String>()).unwrap();
742        let arg = args.get_mut("--option").unwrap();
743        assert_eq!(
744            arg.take_multiple(),
745            vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),]
746        );
747
748        // Test the short option with space
749        let mut iter = ["foo".to_string()].into_iter();
750        args.parse("-xo", &mut iter).unwrap();
751        let mut iter = ["bar".to_string()].into_iter();
752        args.parse("-yo", &mut iter).unwrap();
753        let mut iter = ["baz".to_string()].into_iter();
754        args.parse("-zo", &mut iter).unwrap();
755        let arg = args.get_mut("--option").unwrap();
756        assert_eq!(
757            arg.take_multiple(),
758            vec!["foo".to_string(), "bar".to_string(), "baz".to_string(),]
759        );
760
761        // Test it prints correctly
762        assert_eq!(arg.to_string(), "--option <VALUE>")
763    }
764
765    #[test]
766    fn it_parses_counting_flag() {
767        let mut args = Args::default().counting("--flag", Some('f'));
768
769        // Test not the the flag
770        args.parse("--not-flag", &mut empty::<String>()).unwrap();
771        let arg = args.get("--flag").unwrap();
772        assert_eq!(arg.count(), 0);
773
774        // Test the flag
775        args.parse("--flag", &mut empty::<String>()).unwrap();
776        args.parse("--flag", &mut empty::<String>()).unwrap();
777        args.parse("--flag", &mut empty::<String>()).unwrap();
778        let arg = args.get_mut("--flag").unwrap();
779        assert_eq!(arg.count(), 3);
780        arg.reset();
781
782        // Test the short flag
783        args.parse("-xfzf", &mut empty::<String>()).unwrap();
784        args.parse("-pfft", &mut empty::<String>()).unwrap();
785        args.parse("-abcd", &mut empty::<String>()).unwrap();
786        let arg = args.get_mut("--flag").unwrap();
787        assert_eq!(arg.count(), 4);
788
789        // Test it prints correctly
790        assert_eq!(arg.to_string(), "--flag")
791    }
792
793    #[test]
794    fn it_parses_cargo_arguments() {
795        let args: CargoArguments =
796            CargoArguments::parse_from(["component", "build", "--workspace"].into_iter()).unwrap();
797        assert_eq!(
798            args,
799            CargoArguments {
800                color: None,
801                verbose: 0,
802                help: false,
803                quiet: false,
804                targets: Vec::new(),
805                manifest_path: None,
806                message_format: None,
807                release: false,
808                frozen: false,
809                locked: false,
810                offline: false,
811                workspace: true,
812                packages: Vec::new(),
813            }
814        );
815
816        let args = CargoArguments::parse_from(
817            [
818                "component",
819                "publish",
820                "--help",
821                "-vvv",
822                "--color=auto",
823                "--manifest-path",
824                "Cargo.toml",
825                "--message-format",
826                "json-render-diagnostics",
827                "--release",
828                "--package",
829                "package1",
830                "-p=package2@1.1.1",
831                "--target=foo",
832                "--target",
833                "bar",
834                "--quiet",
835                "--frozen",
836                "--locked",
837                "--offline",
838                "--all",
839                "--not-an-option",
840            ]
841            .into_iter(),
842        )
843        .unwrap();
844        assert_eq!(
845            args,
846            CargoArguments {
847                color: Some(Color::Auto),
848                verbose: 3,
849                help: true,
850                quiet: true,
851                targets: vec!["foo".to_string(), "bar".to_string()],
852                manifest_path: Some("Cargo.toml".into()),
853                message_format: Some("json-render-diagnostics".into()),
854                release: true,
855                frozen: true,
856                locked: true,
857                offline: true,
858                workspace: true,
859                packages: vec![
860                    CargoPackageSpec {
861                        name: "package1".to_string(),
862                        version: None
863                    },
864                    CargoPackageSpec {
865                        name: "package2".to_string(),
866                        version: Some(Version::parse("1.1.1").unwrap())
867                    }
868                ],
869            }
870        );
871    }
872}