cargo_edit/
crate_spec.rs

1//! Crate name parsing.
2use super::errors::*;
3
4/// User-specified crate
5///
6/// This can be a
7/// - Name (e.g. `docopt`)
8/// - Name and a version req (e.g. `docopt@^0.8`)
9/// - Path
10#[derive(Debug)]
11pub struct CrateSpec {
12    /// Crate name
13    pub name: String,
14    /// Optional version requirement
15    pub version_req: Option<String>,
16}
17
18impl CrateSpec {
19    /// Convert a string to a `Crate`
20    pub fn resolve(pkg_id: &str) -> CargoResult<Self> {
21        let (name, version) = pkg_id
22            .split_once('@')
23            .map(|(n, v)| (n, Some(v)))
24            .unwrap_or((pkg_id, None));
25
26        let invalid: Vec<_> = name
27            .chars()
28            .filter(|c| !is_name_char(*c))
29            .map(|c| c.to_string())
30            .collect();
31        if !invalid.is_empty() {
32            return Err(anyhow::format_err!(
33                "Invalid name `{}`: {}",
34                name,
35                invalid.join(", ")
36            ));
37        }
38
39        if let Some(version) = version {
40            semver::VersionReq::parse(version)
41                .with_context(|| format!("Invalid version requirement `{version}`"))?;
42        }
43
44        Ok(Self {
45            name: name.to_owned(),
46            version_req: version.map(|s| s.to_owned()),
47        })
48    }
49}
50
51impl std::str::FromStr for CrateSpec {
52    type Err = Error;
53
54    fn from_str(s: &str) -> CargoResult<Self> {
55        Self::resolve(s)
56    }
57}
58
59fn is_name_char(c: char) -> bool {
60    c.is_alphanumeric() || ['-', '_'].contains(&c)
61}