1use graphviz_rust::{
2 cmd::{CommandArg, Format, Layout},
3 dot_structures::{Attribute, Graph, Id},
4 printer::PrinterContext,
5};
6use nom::{Finish, error::Error as NomError};
7use thiserror::Error;
8
9mod graph_ext;
10
11use self::graph_ext::{Elem, GraphExt};
12use super::{
13 ATTR_NAMES,
14 xdot_parse::{ShapeDraw, parse},
15};
16
17#[derive(Error, Debug)]
19pub enum LayoutError {
20 #[error("failed to run xdot")]
21 Layout(#[from] std::io::Error),
22 #[error("failed to parse dot")]
23 Decode(std::string::FromUtf8Error),
24 #[error("failed to parse dot")]
25 ParseDot(String),
26 #[error("failed to parse xdot attributes")]
27 ParseXDot(#[from] NomError<String>),
28}
29impl From<NomError<&str>> for LayoutError {
30 fn from(e: NomError<&str>) -> Self {
31 nom2owned(e).into()
32 }
33}
34fn nom2owned(e: NomError<&str>) -> NomError<String> {
35 NomError {
36 input: e.input.to_owned(),
37 code: e.code,
38 }
39}
40
41pub fn layout_and_draw_graph(graph: Graph) -> Result<Vec<ShapeDraw>, LayoutError> {
43 let layed_out = layout_graph(graph)?;
44 Ok(draw_graph(layed_out)?)
45}
46
47fn layout_graph(graph: Graph) -> Result<Graph, LayoutError> {
48 let mut ctx = PrinterContext::default();
49 let layed_out = graphviz_rust::exec(
50 graph,
51 &mut ctx,
52 vec![
53 CommandArg::Layout(Layout::Dot),
54 CommandArg::Format(Format::Xdot),
55 ],
56 )?;
57 let layed_out = String::from_utf8(layed_out).map_err(LayoutError::Decode)?;
58 graphviz_rust::parse(&layed_out).map_err(LayoutError::ParseDot)
60}
61
62pub fn draw_graph(graph: Graph) -> Result<Vec<ShapeDraw>, NomError<String>> {
64 Ok(graph
65 .iter_elems()
66 .map(handle_elem)
67 .collect::<Result<Vec<_>, _>>()
68 .map_err(nom2owned)?
69 .into_iter()
70 .flatten()
71 .collect::<Vec<_>>())
72}
73
74fn handle_elem(elem: Elem) -> Result<Vec<ShapeDraw>, NomError<&str>> {
75 let attributes: &[Attribute] = match elem {
76 Elem::Edge(edge) => edge.attributes.as_ref(),
77 Elem::Node(node) => node.attributes.as_ref(),
78 };
79 let mut shapes = vec![];
80 for attr in attributes.iter() {
81 if let Id::Plain(ref attr_name) = attr.0 {
82 if !ATTR_NAMES.contains(&attr_name.as_str()) {
83 continue;
84 }
85 if let Id::Escaped(ref attr_val_raw) = attr.1 {
86 let attr_val = dot_unescape(attr_val_raw)?;
87 dbg!(&attr_name, &attr_val);
88 let mut new = parse(attr_val)?;
89 shapes.append(&mut new);
90 }
91 }
92 }
93 Ok(shapes)
94}
95
96fn dot_unescape(input: &str) -> Result<&str, NomError<&str>> {
97 use nom::{
98 bytes::complete::{tag, take_while},
99 combinator::eof,
100 sequence::{delimited, terminated},
101 };
102 let (_, inner) = terminated(
104 delimited(tag("\""), take_while(|c| c != '\\' && c != '\"'), tag("\"")),
105 eof,
106 )(input)
107 .finish()?;
108 Ok(inner)
109}
110
111#[test]
112fn test_dot_unescape() {
113 assert_eq!(dot_unescape("\"\""), Ok(""));
114 assert_eq!(dot_unescape("\"xy\""), Ok("xy"));
115 assert!(dot_unescape("\"\"\"").is_err());
116 assert!(dot_unescape("\"\\\"").is_err()); }