use std::error::Error;
use std::fmt;
use std::sync::Arc;
use indexmap::IndexMap;
use serde_json::Value;
use super::flatten::FlatNode;
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ComposedNode {
pub path: String,
pub attributes: IndexMap<String, Arc<Value>>,
pub children: IndexMap<String, Arc<ComposedNode>>,
}
impl ComposedNode {
pub fn descendant(self: &Arc<Self>, path: &str) -> Option<&Arc<ComposedNode>> {
let mut node = self;
let mut rest = path;
loop {
if rest.is_empty() {
return Some(node);
}
let (name, tail) = match rest.split_once('/') {
Some((name, tail)) => (name, Some(tail)),
None => (rest, None),
};
node = node.children.get(name)?;
match tail {
Some(tail) => rest = tail,
None => return Some(node),
}
}
}
}
impl Drop for ComposedNode {
fn drop(&mut self) {
let mut stack: Vec<Arc<ComposedNode>> =
self.children.drain(..).map(|(_, child)| child).collect();
while let Some(child) = stack.pop() {
if let Some(mut child) = Arc::into_inner(child) {
stack.extend(child.children.drain(..).map(|(_, c)| c));
}
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ComposeError {
Cycle { cycle: Vec<String> },
UnknownReference { path: String, reference: String },
}
impl fmt::Display for ComposeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Cycle { cycle } => {
write!(f, "reference cycle: ")?;
for path in cycle {
write!(f, "{path} -> ")?;
}
write!(f, "{}", cycle.first().map_or("", String::as_str))
}
Self::UnknownReference { path, reference } => {
write!(f, "node {path} references unknown node {reference}")
}
}
}
}
impl Error for ComposeError {}
#[derive(Debug, Clone)]
pub struct Composition {
heads: IndexMap<String, Arc<ComposedNode>>,
roots: Vec<String>,
}
impl Composition {
pub fn roots(&self) -> &[String] {
&self.roots
}
pub fn get(&self, path: &str) -> Option<&Arc<ComposedNode>> {
let (head, tail) = split_head(path);
self.heads.get(head)?.descendant(tail)
}
pub fn root(&self) -> ComposedNode {
ComposedNode {
path: String::new(),
attributes: IndexMap::new(),
children: self
.roots
.iter()
.map(|root| (root.clone(), self.heads[root.as_str()].clone()))
.collect(),
}
}
}
fn split_head(path: &str) -> (&str, &str) {
path.split_once('/').unwrap_or((path, ""))
}
struct Head<'a> {
node: Option<&'a FlatNode>,
edits: Vec<(&'a str, &'a FlatNode)>,
deps: Vec<usize>,
}
impl<'a> Head<'a> {
fn nodes(&self) -> impl Iterator<Item = &'a FlatNode> + '_ {
self.node.into_iter().chain(self.edits.iter().map(|e| e.1))
}
}
fn references(node: &FlatNode) -> impl Iterator<Item = &str> {
node.inherits
.values()
.chain(node.children.values().flatten())
.map(String::as_str)
}
pub fn compose(flat: &IndexMap<String, FlatNode>) -> Result<Composition, ComposeError> {
let mut heads: IndexMap<&str, Head<'_>> = IndexMap::new();
for (path, node) in flat {
let (head, _) = split_head(path);
let entry = heads.entry(head).or_insert_with(|| Head {
node: None,
edits: Vec::new(),
deps: Vec::new(),
});
if head.len() == path.len() {
entry.node = Some(node);
} else {
entry.edits.push((path.as_str(), node));
}
}
let mut referenced = vec![false; heads.len()];
for index in 0..heads.len() {
let mut deps: Vec<usize> = heads[index]
.nodes()
.flat_map(references)
.filter_map(|reference| heads.get_index_of(split_head(reference).0))
.collect();
deps.sort_unstable();
deps.dedup();
for &dep in &deps {
referenced[dep] = true;
}
heads[index].deps = deps;
}
let order = topological_order(&heads)?;
let mut composed: Vec<Option<Arc<ComposedNode>>> = vec![None; heads.len()];
for index in order {
let (path, head) = heads.get_index(index).expect("index from heads");
let node = compose_head(path, head, &heads, &composed)?;
composed[index] = Some(Arc::new(node));
}
let roots = heads
.iter()
.enumerate()
.filter(|(index, (_, head))| head.node.is_some() && !referenced[*index])
.map(|(_, (path, _))| (*path).to_owned())
.collect();
let heads = heads
.keys()
.zip(composed)
.map(|(path, node)| ((*path).to_owned(), node.expect("every head composed")))
.collect();
Ok(Composition { heads, roots })
}
fn topological_order(heads: &IndexMap<&str, Head<'_>>) -> Result<Vec<usize>, ComposeError> {
const NEW: u8 = 0;
const ACTIVE: u8 = 1;
const DONE: u8 = 2;
let mut state = vec![NEW; heads.len()];
let mut order = Vec::with_capacity(heads.len());
let mut stack: Vec<(usize, usize)> = Vec::new();
for start in 0..heads.len() {
if state[start] != NEW {
continue;
}
state[start] = ACTIVE;
stack.push((start, 0));
while let Some((index, next)) = stack.last_mut() {
let deps = &heads[*index].deps;
if let Some(&dep) = deps.get(*next) {
*next += 1;
match state[dep] {
NEW => {
state[dep] = ACTIVE;
stack.push((dep, 0));
}
ACTIVE => {
let from = stack.iter().position(|(i, _)| *i == dep).expect("active");
let cycle = stack[from..]
.iter()
.map(|(i, _)| (*heads.get_index(*i).expect("index").0).to_owned())
.collect();
return Err(ComposeError::Cycle { cycle });
}
_ => {}
}
} else {
state[*index] = DONE;
order.push(*index);
stack.pop();
}
}
}
Ok(order)
}
fn compose_head(
path: &str,
head: &Head<'_>,
heads: &IndexMap<&str, Head<'_>>,
composed: &[Option<Arc<ComposedNode>>],
) -> Result<ComposedNode, ComposeError> {
let mut node = ComposedNode {
path: path.to_owned(),
attributes: IndexMap::new(),
children: IndexMap::new(),
};
if let Some(flat) = head.node {
add_data(&mut node, path, flat, heads, composed)?;
}
let mut edits: Vec<&(&str, &FlatNode)> = head.edits.iter().collect();
edits.sort_by_key(|(edit_path, _)| edit_path.matches('/').count());
for (edit_path, flat) in edits {
let names = split_head(edit_path).1;
let mut target = &node;
let mut found = true;
for name in names.split('/') {
match target.children.get(name) {
Some(child) => target = child,
None => {
found = false;
break;
}
}
}
if !found {
continue;
}
let mut target = &mut node;
for name in names.split('/') {
let child = target.children.get_mut(name).expect("checked above");
target = Arc::make_mut(child);
}
add_data(target, edit_path, flat, heads, composed)?;
}
Ok(node)
}
fn add_data(
node: &mut ComposedNode,
path: &str,
flat: &FlatNode,
heads: &IndexMap<&str, Head<'_>>,
composed: &[Option<Arc<ComposedNode>>],
) -> Result<(), ComposeError> {
let resolve = |reference: &str| -> Result<&Arc<ComposedNode>, ComposeError> {
let (head, tail) = split_head(reference);
heads
.get_index_of(head)
.and_then(|index| composed[index].as_ref())
.and_then(|head| head.descendant(tail))
.ok_or_else(|| ComposeError::UnknownReference {
path: path.to_owned(),
reference: reference.to_owned(),
})
};
for reference in flat.inherits.values() {
let inherited = resolve(reference)?;
for (name, child) in &inherited.children {
node.children.insert(name.clone(), child.clone());
}
for (name, value) in &inherited.attributes {
node.attributes.insert(name.clone(), value.clone());
}
}
for (name, child) in &flat.children {
match child {
Some(reference) => {
let child = resolve(reference)?.clone();
node.children.insert(name.clone(), child);
}
None => {
node.children.shift_remove(name);
}
}
}
for (name, value) in &flat.attributes {
node.attributes.insert(name.clone(), value.clone());
}
Ok(())
}