use super::ComponentId;
use std::collections::BTreeMap;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Level {
Local,
Listed,
Wildcard,
Core,
Ambient,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Binding {
Component(ComponentId, Level),
Ambiguous(Vec<String>),
Dropped,
Pending(String),
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Names {
bindings: BTreeMap<String, Binding>,
incomplete: bool,
pending: Option<Binding>,
}
impl Names {
#[must_use]
pub fn get(&self, name: &str) -> Option<&Binding> {
const DROPPED: &Binding = &Binding::Dropped;
self.bindings
.get(name)
.or(self.pending.as_ref())
.or_else(|| self.incomplete.then_some(DROPPED))
}
pub fn all(&self) -> impl Iterator<Item = &str> {
self.bindings.keys().map(String::as_str)
}
pub(crate) fn components(&self) -> impl Iterator<Item = (&str, ComponentId)> {
self.leveled().map(|(name, id, _)| (name, id))
}
pub(crate) fn leveled(&self) -> impl Iterator<Item = (&str, ComponentId, Level)> {
self.bindings
.iter()
.filter_map(|(name, binding)| match binding {
Binding::Component(id, level) => Some((name.as_str(), *id, *level)),
Binding::Ambiguous(_) | Binding::Dropped | Binding::Pending(_) => None,
})
}
}
impl Level {
pub(crate) fn rank(self) -> u8 {
match self {
Self::Local => 0,
Self::Listed => 1,
Self::Wildcard | Self::Core => 2,
Self::Ambient => 3,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Renderer {
Component(String, ComponentId),
Ambiguous(Vec<String>),
}
#[derive(Debug, Default)]
pub(crate) struct Builder {
declared: BTreeMap<String, Binding>,
shared: BTreeMap<String, Vec<(ComponentId, String, Level)>>,
ambient: BTreeMap<String, ComponentId>,
incomplete: bool,
pending: Option<Binding>,
}
impl Builder {
pub(crate) fn is_declared(&self, name: &str) -> bool {
self.declared.contains_key(name)
}
pub(crate) fn declared(&self, name: &str) -> Option<&Binding> {
self.declared.get(name)
}
pub(crate) fn declare(&mut self, name: &str, binding: Binding) {
self.declared.insert(name.to_string(), binding);
}
pub(crate) fn share(&mut self, name: &str, id: ComponentId, source: &str, level: Level) {
let providers = self.shared.entry(name.to_string()).or_default();
if !providers.iter().any(|(known, ..)| *known == id) {
providers.push((id, source.to_string(), level));
}
}
pub(crate) fn ambient(&mut self, name: &str, id: ComponentId) {
self.ambient.entry(name.to_string()).or_insert(id);
}
pub(crate) fn incomplete(&mut self) {
self.incomplete = true;
}
pub(crate) fn pending(&mut self, pack: &str) {
self.pending
.get_or_insert_with(|| Binding::Pending(pack.to_string()));
}
pub(crate) fn build(self) -> Names {
let mut bindings = self.declared;
for (name, providers) in self.shared {
bindings
.entry(name)
.or_insert_with(|| match &providers[..] {
[(id, _, level)] => Binding::Component(*id, *level),
_ => Binding::Ambiguous(
providers.into_iter().map(|(_, source, _)| source).collect(),
),
});
}
for (name, id) in self.ambient {
bindings
.entry(name)
.or_insert(Binding::Component(id, Level::Ambient));
}
Names {
bindings,
incomplete: self.incomplete,
pending: self.pending,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn higher_levels_win_and_one_level_can_be_ambiguous() {
let mut names = Builder::default();
names.declare("card", Binding::Component(ComponentId(0), Level::Local));
names.share("card", ComponentId(1), "./ui.comp.md", Level::Wildcard);
names.share("chip", ComponentId(2), "./one.comp.md", Level::Wildcard);
names.share("chip", ComponentId(3), "./two.comp.md", Level::Wildcard);
names.share("row", ComponentId(4), "@compmd/core", Level::Core);
names.share(
"row",
ComponentId(4),
"./re-export.comp.md",
Level::Wildcard,
);
names.ambient("row", ComponentId(5));
names.ambient("list-view", ComponentId(6));
names.ambient("list-view", ComponentId(7));
assert!(names.is_declared("card"));
let names = names.build();
assert_eq!(
names.get("card"),
Some(&Binding::Component(ComponentId(0), Level::Local))
);
assert_eq!(
names.get("chip"),
Some(&Binding::Ambiguous(vec![
"./one.comp.md".into(),
"./two.comp.md".into()
]))
);
assert_eq!(
names.get("row"),
Some(&Binding::Component(ComponentId(4), Level::Core))
);
assert_eq!(
names.get("list-view"),
Some(&Binding::Component(ComponentId(6), Level::Ambient))
);
assert_eq!(names.get("nothing"), None);
assert_eq!(names.all().count(), 4);
}
#[test]
fn a_failed_wildcard_import_may_hold_any_name() {
let mut names = Builder::default();
names.incomplete();
assert_eq!(names.build().get("anything"), Some(&Binding::Dropped));
}
#[test]
fn a_pending_wildcard_import_may_hold_any_name_the_others_dont() {
let mut names = Builder::default();
names.declare("card", Binding::Component(ComponentId(0), Level::Local));
names.incomplete();
names.pending("@kit/charts");
names.pending("@kit/maps");
let names = names.build();
assert_eq!(
names.get("chart"),
Some(&Binding::Pending("@kit/charts".into()))
);
assert_eq!(
names.get("card"),
Some(&Binding::Component(ComponentId(0), Level::Local))
);
}
}