Skip to content

Commit

Permalink
fix: matchspec empty namespace and channel cannonical name (#582)
Browse files Browse the repository at this point in the history
* Adds a simple way to go from a Channel to a name and back using a
`ChannelConfig`.
* Parses `conda-forge::foo` as not having a namespace.
  • Loading branch information
baszalmstra authored Mar 30, 2024
1 parent 426b136 commit 2a05201
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 1 deletion.
90 changes: 90 additions & 0 deletions crates/rattler_conda_types/src/channel/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use itertools::Itertools;
use std::borrow::Cow;
use std::fmt::{Display, Formatter};
use std::path::{Component, Path, PathBuf};
use std::str::FromStr;

Expand Down Expand Up @@ -29,6 +30,76 @@ pub struct ChannelConfig {
pub channel_alias: Url,
}

impl ChannelConfig {
/// Returns the canonical name of a channel with the given base url.
pub fn canonical_name(&self, base_url: &Url) -> NamedChannelOrUrl {
if let Some(stripped) = base_url.as_str().strip_prefix(self.channel_alias.as_str()) {
NamedChannelOrUrl::Name(stripped.trim_matches('/').to_string())
} else {
NamedChannelOrUrl::Url(base_url.clone())
}
}
}

/// Represents a channel description as either a name (e.g. `conda-forge`) or a base url.
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum NamedChannelOrUrl {
/// A named channel
Name(String),

/// A url
Url(Url),
}

impl NamedChannelOrUrl {
/// Returns the string representation of the channel.
///
/// This method ensures that if the channel is a url, it does not end with a `/`.
pub fn as_str(&self) -> &str {
match self {
NamedChannelOrUrl::Name(name) => name,
NamedChannelOrUrl::Url(url) => url.as_str().trim_end_matches('/'),
}
}

/// Converts the channel to a base url using the given configuration.
pub fn into_base_url(self, config: &ChannelConfig) -> Url {
match self {
NamedChannelOrUrl::Name(name) => {
let mut base_url = config.channel_alias.clone();
if let Ok(mut segments) = base_url.path_segments_mut() {
segments.push(&name);
}
base_url
}
NamedChannelOrUrl::Url(url) => url,
}
}

/// Converts this instance into a channel.
pub fn into_channel(self, config: &ChannelConfig) -> Channel {
let name = match &self {
NamedChannelOrUrl::Name(name) => Some(name.clone()),
NamedChannelOrUrl::Url(base_url) => match config.canonical_name(base_url) {
NamedChannelOrUrl::Name(name) => Some(name),
NamedChannelOrUrl::Url(_) => None,
},
};
let base_url = self.into_base_url(config);
Channel {
platforms: None,
base_url,
name,
}
}
}

impl Display for NamedChannelOrUrl {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}

impl Default for ChannelConfig {
fn default() -> Self {
ChannelConfig {
Expand Down Expand Up @@ -583,4 +654,23 @@ mod tests {

assert!(!is_path("conda-forge/label/rust_dev"));
}

#[test]
fn config_canonical_name() {
let channel_config = ChannelConfig {
channel_alias: Url::from_str("https://conda.anaconda.org").unwrap(),
};
assert_eq!(
channel_config
.canonical_name(&Url::from_str("https://conda.anaconda.org/conda-forge/").unwrap())
.as_str(),
"conda-forge"
);
assert_eq!(
channel_config
.canonical_name(&Url::from_str("https://prefix.dev/conda-forge/").unwrap())
.as_str(),
"https://prefix.dev/conda-forge"
);
}
}
2 changes: 1 addition & 1 deletion crates/rattler_conda_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ mod package_name;
pub mod prefix_record;

pub use build_spec::{BuildNumber, BuildNumberSpec, ParseBuildNumberSpecError};
pub use channel::{Channel, ChannelConfig, ParseChannelError};
pub use channel::{Channel, ChannelConfig, NamedChannelOrUrl, ParseChannelError};
pub use channel_data::{ChannelData, ChannelDataPackage};
pub use explicit_environment_spec::{
ExplicitEnvironmentEntry, ExplicitEnvironmentSpec, PackageArchiveHash,
Expand Down
8 changes: 8 additions & 0 deletions crates/rattler_conda_types/src/match_spec/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,8 @@ fn matchspec_parser(
};

nameless_match_spec.namespace = namespace
.map(str::trim)
.filter(|namespace| !namespace.is_empty())
.map(ToOwned::to_owned)
.or(nameless_match_spec.namespace);

Expand Down Expand Up @@ -795,4 +797,10 @@ mod tests {
let package_name = strip_package_name("");
assert_matches!(package_name, Err(ParseMatchSpecError::MissingPackageName));
}

#[test]
fn test_empty_namespace() {
let spec = MatchSpec::from_str("conda-forge::foo", Strict).unwrap();
assert!(spec.namespace.is_none());
}
}

0 comments on commit 2a05201

Please sign in to comment.