use anyhow::{bail, Result};
use core::hash::Hash;
use indexmap::IndexMap;
use semver::Version;
#[derive(Clone, Debug)]
pub struct NameMap<K: Clone + Hash + Eq + Ord, V> {
definitions: IndexMap<K, V>,
alternate_lookups: IndexMap<K, (K, Version)>,
}
impl<K, V> NameMap<K, V>
where
K: Clone + Hash + Eq + Ord,
{
pub fn insert<I>(&mut self, name: &str, cx: &mut I, allow_shadowing: bool, item: V) -> Result<K>
where
I: NameMapIntern<Key = K>,
{
let key = cx.intern(name);
if let Some(prev) = self.definitions.insert(key.clone(), item) {
if !allow_shadowing {
self.definitions.insert(key, prev);
bail!("map entry `{name}` defined twice")
}
}
if let Some((alternate_key, version)) = alternate_lookup_key(name) {
let alternate_key = cx.intern(alternate_key);
if let Some((prev_key, prev_version)) = self
.alternate_lookups
.insert(alternate_key.clone(), (key.clone(), version.clone()))
{
if version < prev_version {
self.alternate_lookups
.insert(alternate_key, (prev_key, prev_version));
}
}
}
Ok(key)
}
pub fn get<I>(&self, name: &str, cx: &I) -> Option<&V>
where
I: NameMapIntern<Key = K>,
{
let candidate = cx.lookup(name).and_then(|k| self.definitions.get(&k));
if let Some(def) = candidate {
return Some(def);
}
let (alternate_name, _version) = alternate_lookup_key(name)?;
let alternate_key = cx.lookup(alternate_name)?;
let (exact_key, _version) = self.alternate_lookups.get(&alternate_key)?;
self.definitions.get(exact_key)
}
pub fn raw_iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.definitions.iter()
}
pub fn raw_get_mut(&mut self, key: &K) -> Option<&mut V> {
self.definitions.get_mut(key)
}
}
impl<K, V> Default for NameMap<K, V>
where
K: Clone + Hash + Eq + Ord,
{
fn default() -> NameMap<K, V> {
NameMap {
definitions: Default::default(),
alternate_lookups: Default::default(),
}
}
}
pub trait NameMapIntern {
type Key;
fn intern(&mut self, s: &str) -> Self::Key;
fn lookup(&self, s: &str) -> Option<Self::Key>;
}
pub struct NameMapNoIntern;
impl NameMapIntern for NameMapNoIntern {
type Key = String;
fn intern(&mut self, s: &str) -> String {
s.to_string()
}
fn lookup(&self, s: &str) -> Option<String> {
Some(s.to_string())
}
}
pub fn are_semver_compatible(a: &str, b: &str) -> bool {
if a == b {
return true;
}
match (alternate_lookup_key(a), alternate_lookup_key(b)) {
(Some((key_a, _)), Some((key_b, _))) => key_a == key_b,
_ => false,
}
}
pub(crate) fn alternate_lookup_key(name: &str) -> Option<(&str, Version)> {
let at = name.find('@')?;
let version_string = &name[at + 1..];
let version = Version::parse(version_string).ok()?;
if !version.pre.is_empty() {
None
} else if version.major != 0 {
let first_dot = version_string.find('.')? + at + 1;
Some((&name[..first_dot], version))
} else if version.minor != 0 {
let first_dot = version_string.find('.')? + at + 1;
let second_dot = name[first_dot + 1..].find('.')? + first_dot + 1;
Some((&name[..second_dot], version))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::{NameMap, NameMapNoIntern};
#[test]
fn alternate_lookup_key() {
fn alt(s: &str) -> Option<&str> {
super::alternate_lookup_key(s).map(|(s, _)| s)
}
assert_eq!(alt("x"), None);
assert_eq!(alt("x:y/z"), None);
assert_eq!(alt("x:y/z@1.0.0"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@1.1.0"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@1.1.2"), Some("x:y/z@1"));
assert_eq!(alt("x:y/z@2.1.2"), Some("x:y/z@2"));
assert_eq!(alt("x:y/z@2.1.2+abc"), Some("x:y/z@2"));
assert_eq!(alt("x:y/z@0.1.2"), Some("x:y/z@0.1"));
assert_eq!(alt("x:y/z@0.1.3"), Some("x:y/z@0.1"));
assert_eq!(alt("x:y/z@0.2.3"), Some("x:y/z@0.2"));
assert_eq!(alt("x:y/z@0.2.3+abc"), Some("x:y/z@0.2"));
assert_eq!(alt("x:y/z@0.0.1"), None);
assert_eq!(alt("x:y/z@0.0.1-pre"), None);
assert_eq!(alt("x:y/z@0.1.0-pre"), None);
assert_eq!(alt("x:y/z@1.0.0-pre"), None);
}
#[test]
fn name_map_smoke() {
let mut map = NameMap::default();
let mut intern = NameMapNoIntern;
map.insert("a", &mut intern, false, 0).unwrap();
map.insert("b", &mut intern, false, 1).unwrap();
assert!(map.insert("a", &mut intern, false, 0).is_err());
assert!(map.insert("a", &mut intern, true, 0).is_ok());
assert_eq!(map.get("a", &intern), Some(&0));
assert_eq!(map.get("b", &intern), Some(&1));
assert_eq!(map.get("c", &intern), None);
map.insert("a:b/c@1.0.0", &mut intern, false, 2).unwrap();
map.insert("a:b/c@1.0.1", &mut intern, false, 3).unwrap();
assert_eq!(map.get("a:b/c@1.0.0", &intern), Some(&2));
assert_eq!(map.get("a:b/c@1.0.1", &intern), Some(&3));
assert_eq!(map.get("a:b/c@1.0.2", &intern), Some(&3));
assert_eq!(map.get("a:b/c@1.1.0", &intern), Some(&3));
}
}