#
cargo/ops/cargo_add/
crate_spec.rs

    1//! Crate name parsing.
    2
    3use anyhow::Context as _;
    4
    5use super::Dependency;
    6use crate::CargoResult;
    7use crate::util::toml_mut::dependency::RegistrySource;
    8use cargo_util_schemas::manifest::PackageName;
    9
    10/// User-specified crate
    11///
    12/// This can be a
    13/// - Name (e.g. `docopt`)
    14/// - Name and a version req (e.g. `docopt@^0.8`)
    15#[derive(Debug)]
    16pub struct CrateSpec {
    17    /// Crate name
    18    name: String,
    19    /// Optional version requirement
    20    version_req: Option<String>,
    21}
    22
    23impl CrateSpec {
    24    /// Convert a string to a `Crate`
    25    pub fn resolve(pkg_id: &str) -> CargoResult<Self> {
    26        let (name, version) = pkg_id
    27            .split_once('@')
    28            .map(|(n, v)| (n, Some(v)))
    29            .unwrap_or((pkg_id, None));
    30
    31        let package_name = PackageName::new(name);
    32        if !pkg_id.contains("@") && package_name.is_err() {
    33            for (idx, ch) in pkg_id.char_indices() {
    34                if !(unicode_ident::is_xid_continue(ch) || ch == '-') {
    35                    let mut suggested_pkg_id = pkg_id.to_string();
    36                    suggested_pkg_id.insert_str(idx, "@");
    37                    if let Ok(_) = CrateSpec::resolve(&suggested_pkg_id.as_str()) {
    38                        let err = package_name.unwrap_err();
    39                        return Err(
    40                            anyhow::format_err!("{err}\n\n\
    41                                help: if this is meant to be a package name followed by a version, insert an `@` like `{suggested_pkg_id}`").into());
    42                    }
    43                }
    44            }
    45        }
    46
    47        package_name?;
    48
    49        if let Some(version) = version {
    50            semver::VersionReq::parse(version).with_context(|| {
    51                if let Some(stripped) = version.strip_prefix("v") {
    52                    return format!(
    53                        "the version provided, `{version}` is not a \
    54                         valid SemVer requirement\n\n\
    55                         help: changing the package to `{name}@{stripped}`",
    56                    );
    57                }
    58                format!("invalid version requirement `{version}`")
    59            })?;
    60        }
    61
    62        let id = Self {
    63            name: name.to_owned(),
    64            version_req: version.map(|s| s.to_owned()),
    65        };
    66
    67        Ok(id)
    68    }
    69
    70    /// Generate a dependency entry for this crate specifier
    71    pub fn to_dependency(&self) -> CargoResult<Dependency> {
    72        let mut dep = Dependency::new(self.name());
    73        if let Some(version_req) = self.version_req() {
    74            dep = dep.set_source(RegistrySource::new(version_req));
    75        }
    76        Ok(dep)
    77    }
    78
    79    pub fn name(&self) -> &str {
    80        &self.name
    81    }
    82
    83    pub fn version_req(&self) -> Option<&str> {
    84        self.version_req.as_deref()
    85    }
    86}