use std::collections::BTreeSet;
use crate::{Anchor, Edge, Graph, Layout, Node, NodeName, PinRef, Trivia};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RenameError {
#[error("there is no node named `{0}`")]
NoSuchNode(String),
#[error("a node named `{0}` already exists")]
NameTaken(String),
#[error("`{0}` is not a name the format can write")]
NotAnIdentifier(String),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Document {
pub graph: Graph,
pub trivia: Trivia,
pub layout: Layout,
}
impl Document {
pub fn new(graph: Graph, trivia: Trivia, layout: Layout) -> Self {
Self {
graph,
trivia,
layout,
}
}
pub fn remove_node(&mut self, name: &str) -> Option<Node> {
let key = NodeName::new(name);
let node = self.graph.nodes.remove(&key)?;
let name = key.as_str();
let touching: Vec<Edge> = self
.graph
.edges
.iter()
.filter(|e| e.from().node() == name || e.to().node() == name)
.cloned()
.collect();
for edge in touching {
self.graph.edges.remove(&edge);
self.trivia.0.remove(&Anchor::Edge(edge));
}
self.trivia
.0
.retain(|anchor, _| anchor.node() != Some(&key));
self.layout.0.remove(&key);
Some(node)
}
pub fn remove_edge(&mut self, edge: &Edge) -> bool {
self.trivia.0.remove(&Anchor::Edge(edge.clone()));
self.graph.edges.remove(edge)
}
pub fn rename_node(&mut self, from: &str, to: &str) -> Result<(), RenameError> {
let old = NodeName::new(from);
let new = NodeName::new(to);
if !self.graph.nodes.contains_key(&old) {
return Err(RenameError::NoSuchNode(from.to_owned()));
}
if !is_identifier(new.as_str()) {
return Err(RenameError::NotAnIdentifier(to.to_owned()));
}
if old != new && self.graph.nodes.contains_key(&new) {
return Err(RenameError::NameTaken(to.to_owned()));
}
if old == new {
return Ok(());
}
let mut node = self.graph.nodes.remove(&old).expect("checked above");
node.name = new.clone();
self.graph.nodes.insert(new.clone(), node);
let rewrite = |pin: &PinRef| -> PinRef {
if pin.node == old {
PinRef::new(new.clone(), pin.pin.clone())
} else {
pin.clone()
}
};
let mut moved = BTreeSet::new();
for edge in std::mem::take(&mut self.graph.edges) {
let next = Edge::new(edge.kind, rewrite(edge.from()), rewrite(edge.to()));
if let Some(comments) = self.trivia.0.remove(&Anchor::Edge(edge)) {
self.trivia
.0
.entry(Anchor::Edge(next.clone()))
.or_default()
.extend(comments);
}
moved.insert(next);
}
self.graph.edges = moved;
let rekeyed: Vec<(Anchor, Vec<String>)> = self
.trivia
.0
.extract_if(.., |anchor, _| anchor.node() == Some(&old))
.map(|(anchor, comments)| (anchor.with_node(new.clone()), comments))
.collect();
for (anchor, comments) in rekeyed {
self.trivia.0.entry(anchor).or_default().extend(comments);
}
if let Some(at) = self.layout.0.remove(&old) {
self.layout.0.insert(new, at);
}
Ok(())
}
}
pub fn is_identifier(name: &str) -> bool {
let mut chars = name.chars();
let Some(first) = chars.next() else {
return false;
};
(first == '_' || unicode_ident::is_xid_start(first))
&& chars.all(unicode_ident::is_xid_continue)
}