use super::types::{ComposedNode, IfcxNode};
use rustc_hash::{FxHashMap, FxHashSet};
pub fn compose_nodes(nodes: &[IfcxNode]) -> FxHashMap<String, ComposedNode> {
let mut path_to_node: FxHashMap<String, ComposedNode> = FxHashMap::default();
let mut child_to_parent: FxHashMap<String, String> = FxHashMap::default();
for node in nodes {
let entry = path_to_node
.entry(node.path.clone())
.or_insert_with(|| ComposedNode {
path: node.path.clone(),
attributes: FxHashMap::default(),
children: Vec::new(),
parent: None,
});
for (key, value) in &node.attributes {
entry.attributes.insert(key.clone(), value.clone());
}
for (name, child_path) in &node.children {
if let Some(path) = child_path {
if !entry.children.contains(path) {
entry.children.push(path.clone());
}
child_to_parent.insert(path.clone(), node.path.clone());
}
let _ = name; }
}
let mut inherits_from: FxHashMap<String, Vec<String>> = FxHashMap::default();
for node in nodes {
for parent in node.inherits.values().flatten() {
inherits_from
.entry(node.path.clone())
.or_default()
.push(parent.clone());
}
}
let mut resolved: FxHashSet<String> = FxHashSet::default();
let paths: Vec<_> = path_to_node.keys().cloned().collect();
for path in &paths {
resolve_inheritance(
path,
&mut path_to_node,
&inherits_from,
&mut resolved,
&mut FxHashSet::default(),
);
}
for (child, parent) in child_to_parent {
if let Some(node) = path_to_node.get_mut(&child) {
node.parent = Some(parent);
}
}
path_to_node
}
fn resolve_inheritance(
path: &str,
nodes: &mut FxHashMap<String, ComposedNode>,
inherits_from: &FxHashMap<String, Vec<String>>,
resolved: &mut FxHashSet<String>,
in_progress: &mut FxHashSet<String>,
) {
if resolved.contains(path) {
return;
}
if in_progress.contains(path) {
return;
}
in_progress.insert(path.to_string());
if let Some(parents) = inherits_from.get(path) {
for parent_path in parents {
resolve_inheritance(parent_path, nodes, inherits_from, resolved, in_progress);
}
let parent_attrs: Vec<_> = parents
.iter()
.filter_map(|p| nodes.get(p))
.flat_map(|n| n.attributes.iter())
.map(|(k, v)| (k.clone(), v.clone()))
.collect();
if let Some(node) = nodes.get_mut(path) {
for (key, value) in parent_attrs {
node.attributes.entry(key).or_insert(value);
}
}
}
in_progress.remove(path);
resolved.insert(path.to_string());
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
use std::collections::HashMap;
fn make_node(path: &str, attrs: serde_json::Value) -> IfcxNode {
IfcxNode {
path: path.to_string(),
children: HashMap::new(),
inherits: HashMap::new(),
attributes: attrs
.as_object()
.map(|o| o.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
.unwrap_or_default(),
}
}
#[test]
fn test_merge_same_path() {
let nodes = vec![
make_node("a", json!({"x": 1})),
make_node("a", json!({"y": 2})),
make_node("a", json!({"x": 3})), ];
let composed = compose_nodes(&nodes);
let a = composed.get("a").unwrap();
assert_eq!(a.attributes.get("x").unwrap(), &json!(3)); assert_eq!(a.attributes.get("y").unwrap(), &json!(2));
}
}