use std::ffi::{CStr, CString};
use std::marker::PhantomData;
use std::ptr;
use std::iter::Iterator;
use std::collections::HashMap;
use graphviz_sys as sys;
use crate::error::GraphvizError;
use crate::attr::AttributeContainer;
pub struct Graph {
pub(crate) inner: *mut sys::Agraph_t,
owned: bool,
}
pub struct Node<'a> {
pub(crate) inner: *mut sys::Agnode_t,
_phantom: PhantomData<&'a Graph>,
}
pub struct Edge<'a> {
pub(crate) inner: *mut sys::Agedge_t,
_phantom: PhantomData<&'a Graph>,
}
pub struct NodeIter<'a> {
graph: &'a Graph,
next: *mut sys::Agnode_t,
}
pub struct EdgeIter<'a> {
graph: &'a Graph,
node: Option<&'a Node<'a>>,
next: *mut sys::Agedge_t,
current_node: *mut sys::Agnode_t,
outgoing: bool,
}
pub struct NodeBuilder<'a> {
graph: &'a Graph,
name: String,
attributes: HashMap<String, String>,
}
pub struct EdgeBuilder<'a> {
graph: &'a Graph,
from: &'a Node<'a>,
to: &'a Node<'a>,
name: Option<String>,
attributes: HashMap<String, String>,
}
pub struct GraphBuilder {
name: String,
directed: bool,
strict: bool,
attributes: HashMap<String, String>,
}
impl Graph {
pub fn new(name: &str, directed: bool) -> Result<Self, GraphvizError> {
let name = CString::new(name)?;
let desc = if directed {
unsafe { sys::Agdirected }
} else {
unsafe { sys::Agundirected }
};
let inner = unsafe {
sys::agopen(name.as_ptr() as *mut _, desc, ptr::null_mut())
};
if inner.is_null() {
return Err(GraphvizError::GraphCreationFailed);
}
Ok(Graph { inner, owned: true })
}
pub fn new_with_strictness(name: &str, directed: bool, strict: bool) -> Result<Self, GraphvizError> {
let name = CString::new(name)?;
let desc = match (directed, strict) {
(true, true) => unsafe { sys::Agstrictdirected },
(true, false) => unsafe { sys::Agdirected },
(false, true) => unsafe { sys::Agstrictundirected },
(false, false) => unsafe { sys::Agundirected },
};
let inner = unsafe {
sys::agopen(name.as_ptr() as *mut _, desc, ptr::null_mut())
};
if inner.is_null() {
return Err(GraphvizError::GraphCreationFailed);
}
Ok(Graph { inner, owned: true })
}
pub fn builder(name: &str) -> GraphBuilder {
GraphBuilder::new(name)
}
pub fn add_node(&self, name: &str) -> Result<Node, GraphvizError> {
let name = CString::new(name)?;
let inner = unsafe {
sys::agnode(self.inner, name.as_ptr() as *mut _, 1)
};
if inner.is_null() {
return Err(GraphvizError::NodeCreationFailed);
}
Ok(Node { inner, _phantom: PhantomData })
}
pub fn create_node(&self, name: &str) -> NodeBuilder {
NodeBuilder::new(self, name)
}
pub fn add_edge(&self, from: &Node, to: &Node, name: Option<&str>) -> Result<Edge, GraphvizError> {
let name_cstr = name.map(CString::new).transpose()?;
let name_ptr = name_cstr.as_ref()
.map_or(ptr::null_mut(), |cs| cs.as_ptr() as *mut _);
let inner = unsafe {
sys::agedge(self.inner, from.inner, to.inner, name_ptr, 1)
};
if inner.is_null() {
return Err(GraphvizError::EdgeCreationFailed);
}
Ok(Edge { inner, _phantom: PhantomData })
}
pub fn create_edge<'a>(&'a self, from: &'a Node, to: &'a Node, name: Option<&str>) -> EdgeBuilder<'a> {
EdgeBuilder::new(self, from, to, name)
}
pub fn get_node(&self, name: &str) -> Result<Option<Node>, GraphvizError> {
let name = CString::new(name)?;
let inner = unsafe {
sys::agnode(self.inner, name.as_ptr() as *mut _, 0)
};
if inner.is_null() {
Ok(None)
} else {
Ok(Some(Node { inner, _phantom: PhantomData }))
}
}
pub fn find_edge(&self, from: &Node, to: &Node) -> Option<Edge> {
let inner = unsafe {
sys::agedge(self.inner, from.inner, to.inner, ptr::null_mut(), 0)
};
if inner.is_null() {
None
} else {
Some(Edge { inner, _phantom: PhantomData })
}
}
pub fn nodes(&self) -> NodeIter {
NodeIter {
graph: self,
next: unsafe { sys::agfstnode(self.inner) },
}
}
pub fn edges(&self) -> EdgeIter {
let first_node = unsafe { sys::agfstnode(self.inner) };
let first_edge = if !first_node.is_null() {
unsafe { sys::agfstedge(self.inner, first_node) }
} else {
ptr::null_mut()
};
EdgeIter {
graph: self,
node: None,
next: first_edge,
current_node: first_node,
outgoing: true,
}
}
pub fn out_edges<'a>(&'a self, node: &'a Node) -> EdgeIter<'a> {
EdgeIter {
graph: self,
node: Some(node),
next: unsafe { sys::agfstout(self.inner, node.inner) },
current_node: node.inner,
outgoing: true,
}
}
pub fn in_edges<'a>(&'a self, node: &'a Node) -> EdgeIter<'a> {
EdgeIter {
graph: self,
node: Some(node),
next: unsafe { sys::agfstin(self.inner, node.inner) },
current_node: node.inner,
outgoing: false,
}
}
pub fn node_count(&self) -> i32 {
unsafe { sys::agnnodes(self.inner) }
}
pub fn edge_count(&self) -> i32 {
unsafe { sys::agnedges(self.inner) }
}
pub fn set_attribute(&self, name: &str, value: &str) -> Result<(), GraphvizError> {
let name = CString::new(name)?;
let value = CString::new(value)?;
let sym = unsafe {
sys::agattr(
self.inner,
sys::AGRAPH as i32,
name.as_ptr() as *mut _,
value.as_ptr() as *mut _,
)
};
if sym.is_null() {
return Err(GraphvizError::AttributeSetFailed);
}
let result = unsafe {
sys::agxset(self.inner as *mut _, sym, value.as_ptr() as *mut _)
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::AttributeSetFailed)
}
}
pub fn get_attribute(&self, name: &str) -> Result<Option<String>, GraphvizError> {
let name = CString::new(name)?;
let value = unsafe {
sys::agget(self.inner as *mut _, name.as_ptr() as *mut _)
};
if value.is_null() {
return Ok(None);
}
let c_str = unsafe { CStr::from_ptr(value) };
let value_str = c_str.to_str()
.map_err(|_| GraphvizError::InvalidUtf8)?
.to_owned();
Ok(Some(value_str))
}
pub fn remove_node(&self, node: Node) -> Result<(), GraphvizError> {
let result = unsafe {
sys::agdelnode(self.inner, node.inner)
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::NodeCreationFailed)
}
}
pub fn remove_edge(&self, edge: Edge) -> Result<(), GraphvizError> {
let result = unsafe {
sys::agdeledge(self.inner, edge.inner)
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::EdgeCreationFailed)
}
}
pub fn name(&self) -> Result<String, GraphvizError> {
let name = unsafe {
sys::agnameof(self.inner as *mut _)
};
if name.is_null() {
return Err(GraphvizError::NullPointer("Graph name is null"));
}
let c_str = unsafe { CStr::from_ptr(name) };
let name_str = c_str.to_str()
.map_err(|_| GraphvizError::InvalidUtf8)?
.to_owned();
Ok(name_str)
}
pub fn is_directed(&self) -> bool {
unsafe { sys::agisdirected(self.inner) != 0 }
}
pub fn is_strict(&self) -> bool {
unsafe { sys::agisstrict(self.inner) != 0 }
}
}
impl<'a> Iterator for NodeIter<'a> {
type Item = Node<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.next.is_null() {
return None;
}
let current = self.next;
self.next = unsafe { sys::agnxtnode(self.graph.inner, current) };
Some(Node {
inner: current,
_phantom: PhantomData,
})
}
}
impl<'a> Iterator for EdgeIter<'a> {
type Item = Edge<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.next.is_null() {
if self.node.is_none() && !self.current_node.is_null() {
self.current_node = unsafe { sys::agnxtnode(self.graph.inner, self.current_node) };
if self.current_node.is_null() {
return None;
}
self.next = unsafe { sys::agfstedge(self.graph.inner, self.current_node) };
if self.next.is_null() {
return self.next(); }
} else {
return None;
}
}
let current = self.next;
self.next = if let Some(_node) = self.node {
if self.outgoing {
unsafe { sys::agnxtout(self.graph.inner, current) }
} else {
unsafe { sys::agnxtin(self.graph.inner, current) }
}
} else {
unsafe { sys::agnxtedge(self.graph.inner, current, self.current_node) }
};
Some(Edge {
inner: current,
_phantom: PhantomData,
})
}
}
impl<'a> NodeBuilder<'a> {
pub fn new(graph: &'a Graph, name: &str) -> Self {
NodeBuilder {
graph,
name: name.to_owned(),
attributes: HashMap::new(),
}
}
pub fn attribute(mut self, name: &str, value: &str) -> Self {
self.attributes.insert(name.to_owned(), value.to_owned());
self
}
pub fn build(self) -> Result<Node<'a>, GraphvizError> {
let node = self.graph.add_node(&self.name)?;
for (name, value) in self.attributes {
node.set_attribute(&name, &value)?;
}
Ok(node)
}
}
impl<'a> EdgeBuilder<'a> {
pub fn new(graph: &'a Graph, from: &'a Node, to: &'a Node, name: Option<&str>) -> Self {
EdgeBuilder {
graph,
from,
to,
name: name.map(String::from),
attributes: HashMap::new(),
}
}
pub fn attribute(mut self, name: &str, value: &str) -> Self {
self.attributes.insert(name.to_owned(), value.to_owned());
self
}
pub fn build(self) -> Result<Edge<'a>, GraphvizError> {
let name_ref = self.name.as_deref();
let edge = self.graph.add_edge(self.from, self.to, name_ref)?;
for (name, value) in self.attributes {
edge.set_attribute(&name, &value)?;
}
Ok(edge)
}
}
impl GraphBuilder {
pub fn new(name: &str) -> Self {
GraphBuilder {
name: name.to_owned(),
directed: true,
strict: false,
attributes: HashMap::new(),
}
}
pub fn directed(mut self, directed: bool) -> Self {
self.directed = directed;
self
}
pub fn strict(mut self, strict: bool) -> Self {
self.strict = strict;
self
}
pub fn attribute(mut self, name: &str, value: &str) -> Self {
self.attributes.insert(name.to_owned(), value.to_owned());
self
}
pub fn build(self) -> Result<Graph, GraphvizError> {
let graph = Graph::new_with_strictness(&self.name, self.directed, self.strict)?;
for (name, value) in self.attributes {
graph.set_attribute(&name, &value)?;
}
Ok(graph)
}
}
impl Drop for Graph {
fn drop(&mut self) {
if self.owned && !self.inner.is_null() {
unsafe { sys::agclose(self.inner) };
}
}
}
impl<'a> Node<'a> {
pub fn name(&self) -> Result<String, GraphvizError> {
let name = unsafe { sys::agnameof(self.inner as *mut _) };
if name.is_null() {
return Err(GraphvizError::NullPointer("Node name is null"));
}
let c_str = unsafe { CStr::from_ptr(name) };
let name_str = c_str.to_str()
.map_err(|_| GraphvizError::InvalidUtf8)?
.to_owned();
Ok(name_str)
}
pub fn graph(&self) -> Graph {
let graph_ptr = unsafe { sys::agraphof(self.inner as *mut _) };
Graph {
inner: graph_ptr,
owned: false, }
}
}
impl<'a> Edge<'a> {
pub fn from_node(&self) -> Node<'a> {
let graph_ptr = unsafe { sys::agraphof(self.inner as *mut _) };
let is_directed = unsafe { sys::agisdirected(graph_ptr) != 0 };
if is_directed {
let edge_type = unsafe { (*self.inner).base.tag.objtype() };
if edge_type == sys::AGOUTEDGE as u32 {
unsafe {
let source_node = self.determine_source_through_graph_traversal(graph_ptr);
if !source_node.is_null() {
return Node {
inner: source_node,
_phantom: PhantomData,
};
}
let node_ref = self.get_opposite_node((*self.inner).node, graph_ptr);
Node {
inner: node_ref,
_phantom: PhantomData,
}
}
} else if edge_type == sys::AGINEDGE as u32 {
unsafe {
Node {
inner: (*self.inner).node,
_phantom: PhantomData,
}
}
} else {
unsafe {
Node {
inner: (*self.inner).node,
_phantom: PhantomData,
}
}
}
} else {
unsafe {
let potential_source = self.determine_source_for_undirected(graph_ptr);
if !potential_source.is_null() {
Node {
inner: potential_source,
_phantom: PhantomData,
}
} else {
Node {
inner: (*self.inner).node,
_phantom: PhantomData,
}
}
}
}
}
unsafe fn determine_source_through_graph_traversal(&self, graph: *mut sys::Agraph_t) -> *mut sys::Agnode_t {
let target_node = (*self.inner).node;
let mut current_node = sys::agfstnode(graph);
while !current_node.is_null() {
if current_node != target_node {
let edge = sys::agedge(graph, current_node, target_node, std::ptr::null_mut(), 0);
if !edge.is_null() && edge == self.inner {
return current_node;
}
}
current_node = sys::agnxtnode(graph, current_node);
}
std::ptr::null_mut()
}
unsafe fn determine_source_for_undirected(&self, graph: *mut sys::Agraph_t) -> *mut sys::Agnode_t {
let node_target = (*self.inner).node;
let mut node_iter = sys::agfstnode(graph);
while !node_iter.is_null() {
if node_iter != node_target {
let edge_check = sys::agedge(graph, node_iter, node_target, std::ptr::null_mut(), 0);
if edge_check == self.inner {
return node_iter;
}
}
node_iter = sys::agnxtnode(graph, node_iter);
}
std::ptr::null_mut()
}
unsafe fn get_opposite_node(&self, known_node: *mut sys::Agnode_t, graph: *mut sys::Agraph_t) -> *mut sys::Agnode_t {
let mut node_scan = sys::agfstnode(graph);
while !node_scan.is_null() {
if node_scan != known_node {
let test_edge = sys::agedge(graph, node_scan, known_node, std::ptr::null_mut(), 0);
if test_edge == self.inner ||
test_edge == self.inner.cast::<sys::Agedgepair_s>().offset(1).cast() {
return node_scan;
}
}
node_scan = sys::agnxtnode(graph, node_scan);
}
known_node
}
}
impl AttributeContainer for Graph {
fn set_attribute(&self, name: &str, value: &str) -> Result<(), GraphvizError> {
self.set_attribute(name, value)
}
fn get_attribute(&self, name: &str) -> Result<Option<String>, GraphvizError> {
self.get_attribute(name)
}
}
impl<'a> AttributeContainer for Node<'a> {
fn set_attribute(&self, name: &str, value: &str) -> Result<(), GraphvizError> {
let graph = unsafe { sys::agraphof(self.inner as *mut _) };
let name_cstr = CString::new(name)?;
let value_cstr = CString::new(value)?;
let empty_str = CString::new("")?;
let sym = unsafe {
sys::agattr(
graph,
sys::AGNODE as i32,
name_cstr.as_ptr() as *mut _,
empty_str.as_ptr()
)
};
if sym.is_null() {
return Err(GraphvizError::AttributeSetFailed);
}
let result = unsafe {
sys::agxset(self.inner as *mut _, sym, value_cstr.as_ptr() as *mut _)
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::AttributeSetFailed)
}
}
fn get_attribute(&self, name: &str) -> Result<Option<String>, GraphvizError> {
let name = CString::new(name)?;
let value = unsafe { sys::agget(self.inner as *mut _, name.as_ptr() as *mut _) };
if value.is_null() {
return Ok(None);
}
let c_str = unsafe { CStr::from_ptr(value) };
let value_str = c_str.to_str()
.map_err(|_| GraphvizError::InvalidUtf8)?
.to_owned();
Ok(Some(value_str))
}
}
impl<'a> AttributeContainer for Edge<'a> {
fn set_attribute(&self, name: &str, value: &str) -> Result<(), GraphvizError> {
let graph = unsafe { sys::agraphof(self.inner as *mut _) };
let name_cstr = CString::new(name)?;
let value_cstr = CString::new(value)?;
let empty_str = CString::new("")?;
let sym = unsafe {
sys::agattr(
graph,
sys::AGEDGE as i32,
name_cstr.as_ptr() as *mut _,
empty_str.as_ptr()
)
};
if sym.is_null() {
return Err(GraphvizError::AttributeSetFailed);
}
let result = unsafe {
sys::agxset(self.inner as *mut _, sym, value_cstr.as_ptr() as *mut _)
};
if result == 0 {
Ok(())
} else {
Err(GraphvizError::AttributeSetFailed)
}
}
fn get_attribute(&self, name: &str) -> Result<Option<String>, GraphvizError> {
let name = CString::new(name)?;
let value = unsafe { sys::agget(self.inner as *mut _, name.as_ptr() as *mut _) };
if value.is_null() {
return Ok(None);
}
let c_str = unsafe { CStr::from_ptr(value) };
let value_str = c_str.to_str()
.map_err(|_| GraphvizError::InvalidUtf8)?
.to_owned();
Ok(Some(value_str))
}
}