1use 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#[derive(Debug, Clone, Eq, PartialEq)]
39pub struct CargoPackageSpec {
40 pub name: String,
42 pub version: Option<Version>,
44}
45
46impl CargoPackageSpec {
47 pub fn new(spec: impl Into<String>) -> Result<Self> {
49 let spec = spec.into();
50
51 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 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 fn parse(&mut self, arg: &str, iter: &mut impl Iterator<Item = String>) -> Result<bool> {
317 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 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 return Ok(true);
338 }
339
340 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 return Ok(true);
356 }
357
358 Ok(false)
360 }
361}
362
363#[derive(Default, Debug, Clone, Eq, PartialEq)]
368pub struct CargoArguments {
369 pub color: Option<Color>,
371 pub verbose: usize,
373 pub help: bool,
375 pub quiet: bool,
377 pub targets: Vec<String>,
379 pub manifest_path: Option<PathBuf>,
381 pub message_format: Option<String>,
383 pub frozen: bool,
385 pub locked: bool,
387 pub release: bool,
389 pub offline: bool,
391 pub workspace: bool,
393 pub packages: Vec<CargoPackageSpec>,
395}
396
397impl CargoArguments {
398 pub fn network_allowed(&self) -> bool {
400 !self.frozen && !self.offline
401 }
402
403 pub fn lock_update_allowed(&self) -> bool {
405 !self.frozen && !self.locked
406 }
407
408 pub fn parse() -> Result<Self> {
410 Self::parse_from(std::env::args().skip(1))
411 }
412
413 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 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 if arg == "--" {
446 break;
447 }
448
449 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#[derive(Debug)]
493pub struct Config {
494 pub pkg_config: wasm_pkg_client::Config,
496 terminal: Terminal,
498}
499
500impl Config {
501 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 pub fn pkg_config(&self) -> &wasm_pkg_client::Config {
515 &self.pkg_config
516 }
517
518 pub fn terminal(&self) -> &Terminal {
520 &self.terminal
521 }
522
523 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 args.parse("--not-flag", &mut empty::<String>()).unwrap();
547 let arg = args.get("--flag").unwrap();
548 assert_eq!(arg.count(), 0);
549
550 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 args.parse("-rxd", &mut empty::<String>()).unwrap();
564 let arg = args.get("--flag").unwrap();
565 assert_eq!(arg.count(), 0);
566
567 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 args.parse("-xyz", &mut empty::<String>()).unwrap();
717 let arg = args.get_mut("--option").unwrap();
718 assert_eq!(arg.take_single(), None);
719
720 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 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 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 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 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 args.parse("--not-flag", &mut empty::<String>()).unwrap();
771 let arg = args.get("--flag").unwrap();
772 assert_eq!(arg.count(), 0);
773
774 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 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 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}