use std::collections::BTreeMap;
pub(super) fn package_scope(name: &str) -> Option<&str> {
if name.starts_with('@') {
name.find('/').map(|idx| &name[..idx])
} else {
None
}
}
pub(super) fn registry_uri_key(url: &str) -> String {
let (rest, default_port) = if let Some(rest) = url.strip_prefix("https:") {
(rest, ":443")
} else if let Some(rest) = url.strip_prefix("http:") {
(rest, ":80")
} else {
return url.to_string();
};
strip_authority_port_suffix(rest, default_port)
}
pub(super) fn normalize_npmrc_uri_key(key: &str) -> String {
let stripped = strip_authority_port_suffix(key, ":443");
if stripped != key {
return stripped;
}
strip_authority_port_suffix(key, ":80")
}
fn strip_authority_port_suffix(key: &str, port_suffix: &str) -> String {
let Some(after) = key.strip_prefix("//") else {
return key.to_string();
};
let (authority, path) = match after.find('/') {
Some(idx) => (&after[..idx], &after[idx..]),
None => (after, ""),
};
let Some(authority) = authority.strip_suffix(port_suffix) else {
return key.to_string();
};
format!("//{authority}{path}")
}
pub(crate) fn lookup_by_uri_prefix<'a, V>(
map: &'a BTreeMap<String, V>,
key: &str,
) -> Option<&'a V> {
if let Some(v) = map.get(key) {
return Some(v);
}
let trimmed = key.trim_end_matches('/');
if !trimmed.is_empty()
&& trimmed != key
&& let Some(v) = map.get(trimmed)
{
return Some(v);
}
let mut cursor = trimmed;
while let Some(idx) = cursor.rfind('/') {
cursor = &cursor[..idx];
if cursor.len() <= 2 {
break;
}
let with_slash = format!("{cursor}/");
if let Some(v) = map.get(&with_slash) {
return Some(v);
}
if let Some(v) = map.get(cursor) {
return Some(v);
}
}
None
}
pub fn normalize_registry_url_pub(url: &str) -> String {
normalize_registry_url(url)
}
pub fn registry_uri_key_pub(url: &str) -> String {
registry_uri_key(url)
}
pub(super) fn is_public_npmjs_url(url: &str) -> bool {
let url = url.trim();
let after_scheme = strip_prefix_ignore_ascii_case(url, "https://")
.or_else(|| strip_prefix_ignore_ascii_case(url, "http://"))
.or_else(|| url.strip_prefix("//"))
.unwrap_or(url);
if after_scheme == url && url.contains("://") {
return false;
}
let host = after_scheme
.split_once('/')
.map(|(h, _)| h)
.unwrap_or(after_scheme);
let host = host.split_once('@').map(|(_, h)| h).unwrap_or(host);
let host = host.split_once(':').map(|(h, _)| h).unwrap_or(host);
host.eq_ignore_ascii_case("registry.npmjs.org")
}
fn strip_prefix_ignore_ascii_case<'a>(s: &'a str, prefix: &str) -> Option<&'a str> {
let (head, tail) = s.split_at_checked(prefix.len())?;
head.eq_ignore_ascii_case(prefix).then_some(tail)
}
pub(super) fn normalize_registry_url(url: &str) -> String {
let url = url.trim();
if url.ends_with('/') {
url.to_string()
} else {
format!("{url}/")
}
}