xargo_lib/
cli.rs

1use std::env;
2use std::path::{Path, PathBuf};
3
4use anyhow::{anyhow, Result};
5
6pub struct Args {
7    all: Vec<String>,
8    target: Option<String>,
9    manifest_path: Option<PathBuf>,
10    verbosity: Option<Verbosity>,
11}
12
13#[derive(Debug, Eq, PartialEq)]
14pub enum Verbosity {
15    Quiet,
16    Verbose,
17}
18
19impl Args {
20    /// Create args explicitly, with other args passed unchanged to cargo invocation
21    pub fn new<T, P, A, S>(
22        target: Option<T>,
23        manifest_path: Option<P>,
24        verbosity: Option<Verbosity>,
25        other_args: A,
26    ) -> Result<Self>
27    where
28        T: Into<String> + Clone,
29        P: AsRef<Path>,
30        A: IntoIterator<Item = S>,
31        S: AsRef<str>,
32    {
33        let other_args = other_args
34            .into_iter()
35            .map(|a| a.as_ref().to_string())
36            .collect::<Vec<_>>();
37
38        // check for duplicates of the explicit args
39        let explicit_args = ["--target", "--manifest-path", "--verbose", "--quiet"];
40        let duplicates = other_args
41            .iter()
42            .filter(|a| {
43                explicit_args
44                    .iter()
45                    .any(|ea| a == ea || a.starts_with(&format!("{}=", ea)))
46            })
47            .collect::<Vec<_>>();
48        if !duplicates.is_empty() {
49            return Err(anyhow!(
50                "The following args should be passed explicitly: {:?}",
51                duplicates
52            ));
53        }
54
55        // add the explicit args to `all` which will be passed on to `cargo`
56        let mut all = other_args;
57        if let Some(target) = target.clone() {
58            all.push(format!("--target={}", target.into()))
59        }
60        if let Some(ref manifest_path) = manifest_path {
61            all.push(format!(
62                "--manifest-path={}",
63                manifest_path.as_ref().to_string_lossy()
64            ))
65        }
66        if let Some(ref verbosity) = verbosity {
67            match verbosity {
68                Verbosity::Verbose => all.push("--verbose".into()),
69                Verbosity::Quiet => all.push("--quiet".into()),
70            }
71        }
72
73        Ok(Args {
74            all,
75            target: target.map(Into::into),
76            manifest_path: manifest_path.map(|p| p.as_ref().into()),
77            verbosity,
78        })
79    }
80
81    /// Parse raw args from command line
82    pub fn from_raw<A, S>(all: A) -> Result<Self>
83    where
84        A: IntoIterator<Item = S>,
85        S: AsRef<str>,
86    {
87        let all = all
88            .into_iter()
89            .map(|a| a.as_ref().to_string())
90            .collect::<Vec<_>>();
91
92        let mut target: Option<String> = None;
93        let mut manifest_path = None;
94        let mut verbosity = None;
95        {
96            let mut args = all.iter();
97            while let Some(arg) = args.next() {
98                if arg == "--target" {
99                    target = args.next().map(|s| s.to_owned());
100                } else if arg.starts_with("--target=") {
101                    target = arg.splitn(2, '=').nth(1).map(|s| s.to_owned());
102                }
103                if arg == "--manifest-path" {
104                    manifest_path = args.next().map(|s| s.to_owned());
105                } else if arg.starts_with("--manifest-path=") {
106                    manifest_path = arg.splitn(2, '=').nth(1).map(|s| s.to_owned());
107                }
108                if arg == "--verbose" || arg == "-v" || arg == "-vv" {
109                    if let Some(Verbosity::Quiet) = verbosity {
110                        return Err(anyhow!("cannot set both --verbose and --quiet"));
111                    }
112                    verbosity = Some(Verbosity::Verbose)
113                }
114                if arg == "--quiet" || arg == "-q" {
115                    if let Some(Verbosity::Verbose) = verbosity {
116                        return Err(anyhow!("cannot set both --verbose and --quiet"));
117                    }
118                    verbosity = Some(Verbosity::Quiet)
119                }
120            }
121        }
122
123        Ok(Args {
124            all,
125            target: target.map(Into::into),
126            manifest_path: manifest_path.map(Into::into),
127            verbosity,
128        })
129    }
130
131    pub fn all(&self) -> &[String] {
132        &self.all
133    }
134
135    pub fn target(&self) -> Option<&str> {
136        self.target.as_ref().map(|s| &**s)
137    }
138
139    pub fn manifest_path(&self) -> Option<&Path> {
140        self.manifest_path.as_ref().map(|s| &**s)
141    }
142
143    pub fn quiet(&self) -> bool {
144        self.verbosity == Some(Verbosity::Quiet)
145    }
146
147    pub fn verbose(&self) -> bool {
148        self.verbosity == Some(Verbosity::Verbose)
149    }
150}
151
152pub fn args(command_name: &str) -> Result<(Command, Args)> {
153    let mut args = env::args().skip(1);
154    if args.next() != Some("x".to_string() + command_name) {
155        Err(anyhow!(
156            "must be invoked as cargo subcommand: `cargo x{}`",
157            command_name
158        ))?;
159    }
160    let all = args.collect::<Vec<_>>();
161    let command = match all.first().map(|s| s.as_str()) {
162        Some("-h") | Some("--help") => Command::Help,
163        Some("-v") | Some("--version") => Command::Version,
164        _ => Command::Build,
165    };
166
167    let args = Args::from_raw(all)?;
168    Ok((command, args))
169}
170
171#[derive(Clone, PartialEq)]
172pub enum Command {
173    Build,
174    Help,
175    Version,
176}