use super::{ManagerError, Result};
#[derive(Debug, Clone, PartialEq)]
pub struct ProviderReference {
pub repo: String,
pub name: String,
pub tag: Option<String>,
pub tarball: Option<String>,
}
impl ProviderReference {
pub fn parse(input: &str) -> Result<ProviderReference> {
let input = input.trim();
if input.is_empty() {
return Err(ManagerError::User("empty provider reference".into()));
}
let (path, tag) = match input.split_once('@') {
Some((p, t)) => (p, Some(t.to_string())),
None => (input, None),
};
let github = path
.strip_prefix("https://github.com/")
.or_else(|| path.strip_prefix("http://github.com/"));
if github.is_none() && (path.starts_with("https://") || path.starts_with("http://")) {
return parse_tarball_url(path, tag);
}
let repo = if let Some(rest) = github {
rest.trim_end_matches('/')
.trim_end_matches(".git")
.to_string()
} else {
path.to_string()
};
let has_slash = repo.contains('/');
let short = if has_slash {
let stem = repo.rsplit('/').next().unwrap_or(&repo).to_string();
stem.strip_prefix("rootle-").unwrap_or(&stem).to_string()
} else {
repo.clone()
};
let repo = if has_slash {
repo
} else {
format!("rootledev/rootle-{repo}")
};
let Some((owner, name)) = repo.split_once('/') else {
return Err(ManagerError::User(format!(
"provider reference {input:?} must be owner/repo, a GitHub URL, or a bare name"
)));
};
if owner.is_empty() || name.is_empty() {
return Err(ManagerError::User(format!("malformed reference {input:?}")));
}
Ok(ProviderReference {
repo,
name: short,
tag,
tarball: None,
})
}
}
fn parse_tarball_url(url: &str, tag: Option<String>) -> Result<ProviderReference> {
let file = url.rsplit('/').next().unwrap_or_default();
let target = super::release::platform_target();
let suffix = format!("-{target}.tar.gz");
let stem = file.strip_suffix(&suffix).ok_or_else(|| {
ManagerError::User(format!(
"plain-HTTP install needs the platform tarball: a URL ending in {suffix}"
))
})?;
let stem = stem.strip_prefix("rootle-").unwrap_or(stem);
let (name, derived) = match stem.rsplit_once('-') {
Some((n, v)) if is_versionish(v) => (n, Some(normalize_tag(v))),
_ => (stem, None),
};
if name.is_empty() {
return Err(ManagerError::User(format!(
"cannot derive a provider name from {url:?}"
)));
}
Ok(ProviderReference {
repo: url.to_string(),
name: name.to_string(),
tag: tag.or(derived),
tarball: Some(url.to_string()),
})
}
fn is_versionish(s: &str) -> bool {
let s = s.strip_prefix('v').unwrap_or(s);
!s.is_empty() && s.bytes().all(|b| b.is_ascii_digit() || b == b'.')
}
fn normalize_tag(v: &str) -> String {
if v.starts_with('v') {
v.to_string()
} else {
format!("v{v}")
}
}
pub(super) fn binary_name_of(r: &ProviderReference) -> String {
format!("rootle-{}", r.name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ref_grammar() {
assert_eq!(
ProviderReference::parse("gitlab").unwrap(),
ProviderReference {
repo: "rootledev/rootle-gitlab".into(),
name: "gitlab".into(),
tag: None,
tarball: None
}
);
assert_eq!(
ProviderReference::parse("rootledev/rootle-gitlab").unwrap(),
ProviderReference {
repo: "rootledev/rootle-gitlab".into(),
name: "gitlab".into(),
tag: None,
tarball: None
}
);
assert_eq!(
ProviderReference::parse("https://github.com/rootledev/rootle-gitlab").unwrap(),
ProviderReference {
repo: "rootledev/rootle-gitlab".into(),
name: "gitlab".into(),
tag: None,
tarball: None
}
);
assert_eq!(
ProviderReference::parse("rootledev/rootle-gitlab@v0.1.0")
.unwrap()
.tag,
Some("v0.1.0".into())
);
assert_eq!(
ProviderReference::parse("gitlab@v0.2.0").unwrap().tag,
Some("v0.2.0".into())
);
assert!(ProviderReference::parse("").is_err());
assert!(ProviderReference::parse("justtext/").is_err());
assert_eq!(
ProviderReference::parse("someone/myprovider").unwrap().name,
"myprovider"
);
}
#[test]
fn plain_http_tarball_urls() {
let target = super::super::release::platform_target();
let url =
format!("https://artifacts.corp.example/providers/rootle-gitlab-0.1.0-{target}.tar.gz");
let r = ProviderReference::parse(&url).unwrap();
assert_eq!(r.name, "gitlab");
assert_eq!(r.tag, Some("v0.1.0".into()));
assert_eq!(r.tarball, Some(url.clone()));
assert_eq!(r.repo, url);
let bare = format!("https://artifacts.corp.example/p/rootle-gitlab-{target}.tar.gz");
let r = ProviderReference::parse(&bare).unwrap();
assert_eq!((r.name.as_str(), r.tag), ("gitlab", None));
let pinned = ProviderReference::parse(&format!("{url}@v9.9.9")).unwrap();
assert_eq!(pinned.tag, Some("v9.9.9".into()));
assert!(
ProviderReference::parse("https://artifacts.corp.example/rootle-gitlab.tar.gz")
.is_err()
);
assert!(ProviderReference::parse("https://artifacts.corp.example/rootle-gitlab").is_err());
}
}