wolf-graph-dot 0.1.0

Adds support for generating Graphviz DOT files from wolf-graph graphs.
Documentation
use wolf_graph::prelude::*;
use wolf_graph_dot::prelude::*;
use indoc::indoc;

#[test]
fn test1() {
    // Make the initial graph
    let mut compound = make_test_compound();

    // Add the groups
    compound.add_node(nid!(1), None::<NodeID>, eid!("r1")).unwrap();
    compound.add_node(nid!(2), None::<NodeID>, eid!("r2")).unwrap();
    compound.add_node(nid!(3), Some(nid!(1)), eid!("13")).unwrap();

    // Move nodes into the groups
    for node in ["J", "A"] {
        compound.move_node(nid!(node), Some(nid!(1))).unwrap();
    }
    for node in ["G", "3"] {
        compound.move_node(nid!(node), Some(nid!(2))).unwrap();
    }
    for node in ["I", "C", "K"] {
        compound.move_node(nid!(node), Some(nid!(3))).unwrap();
    }

    let dot = indoc! {r#"
    digraph G {
        subgraph cluster_1 {
            label="1" color="red"
            A [label="A"]
            J [label="J"]
        }
        subgraph cluster_2 {
            label="2" color="green"
            subgraph cluster_3 {
                label="3" color="blue"
                C [label="C"]
                I [label="I"]
                K [label="K"]
            }
            G [label="G"]
        }
        B [label="B"]
        D [label="D"]
        E [label="E"]
        F [label="F"]
        H [label="H"]
        A -> C [label="AC"]
        A -> D [label="AD"]
        A -> E [label="AE"]
        B -> A [label="BA"]
        B -> C [label="BC"]
        B -> G [label="BG"]
        C -> D [label="CD"]
        E -> D [label="ED"]
        F -> D [label="FD"]
        F -> E [label="FE"]
        G -> I [label="GI"]
        H -> J [label="HJ"]
        I -> B [label="IB"]
        I -> C [label="IC"]
        I -> K [label="IK"]
        J -> A [label="JA"]
        J -> E [label="JE"]
        J -> F [label="JF"]
    }
    "#};
    assert_eq!(compound.dot_format(), dot.trim());
}

// Workaround for the orphan rule
#[derive(Debug, Clone, PartialEq, Default)]
struct Empty;

type EmptyGraph = Graph<Empty, Empty, Empty>;
type TestCompound = Compound<EmptyGraph>;

impl DotEncodable<Empty, Empty, Empty> for TestCompound {
    fn dot_edge_attributes(&self, edge: &EdgeID) -> Option<EdgeAttributes> {
        Some(EdgeAttributes::new(Some(edge.to_string()), None, None, None, None, None, None))
    }

    fn dot_node_attributes(&self, node: &NodeID) -> Option<NodeAttributes> {
        Some(NodeAttributes::new(Some(node.to_string()), None, None, None))
    }

    fn dot_is_compound(&self) -> bool {
        true
    }

    fn dot_children(&self, node: Option<&NodeID>) -> Nodes {
        self.children(node).unwrap()
    }

    fn dot_group_attributes(&self, group: &NodeID) -> Option<GroupAttributes> {
        let color = match group.as_str() {
            "1" => Some(Color::RED),
            "2" => Some(Color::GREEN),
            "3" => Some(Color::BLUE),
            _ => None,
        };
        Some(GroupAttributes::new(Some(group.to_string()), color))
    }
}

fn make_test_compound() -> TestCompound {
    let graph = make_test_compound_graph(graph_edges());
    let mut forest = BlankForest::new();
    for node in graph.all_nodes().iter() {
        forest.add_node(node, None::<NodeID>, eid!(format!("r{}", node))).unwrap();
    }
    TestCompound::new_with_graph_and_forest(graph, forest).unwrap()
}

fn make_test_compound_graph(edges: Vec<(EdgeID, NodeID, NodeID)>) -> EmptyGraph {
    let mut graph = EmptyGraph::default();

    for (label, source, target) in edges.iter() {
        if !graph.has_node(source) {
            graph.add_node(source).unwrap();
        }
        if !graph.has_node(target) {
            graph.add_node(target).unwrap();
        }
        graph.add_edge(label, source, target).unwrap();
    }

    graph
}

fn graph_edges() -> Vec<(EdgeID, NodeID, NodeID)> {
    let v = vec![
        ("AC", "A", "C"),
        ("AD", "A", "D"),
        ("AE", "A", "E"),
        ("BA", "B", "A"),
        ("BC", "B", "C"),
        ("BG", "B", "G"),
        ("CD", "C", "D"),
        ("ED", "E", "D"),
        ("FD", "F", "D"),
        ("FE", "F", "E"),
        ("HJ", "H", "J"),
        ("IC", "I", "C"),
        ("IK", "I", "K"),
        ("JA", "J", "A"),
        ("JE", "J", "E"),
        ("JF", "J", "F"),
        ("GI", "G", "I"), // back edge
        ("IB", "I", "B"), // back edge
    ];
    v.into_iter().map(|(a, b, c)| (eid!(a), nid!(b), nid!(c))).collect()
}