Skip to main content

cognis_graph/viz/
ascii.rs

1//! ASCII renderer — single-pass, line-oriented.
2
3use crate::compiled::CompiledGraph;
4use crate::state::GraphState;
5
6use super::extract_edges;
7
8impl<S: GraphState> CompiledGraph<S> {
9    /// Render the graph as an ASCII summary suitable for terminal output.
10    ///
11    /// Layout:
12    /// ```text
13    /// Graph (start: <name>)
14    ///   nodes:
15    ///     - a
16    ///     - b
17    ///     - c
18    ///   edges:
19    ///     a -> b
20    ///     b -> c
21    /// ```
22    pub fn to_ascii(&self) -> String {
23        let mut out = String::new();
24        match (&self.graph.start, self.version()) {
25            (Some(s), Some(v)) => out.push_str(&format!("Graph (start: {s}, version: {v})\n")),
26            (Some(s), None) => out.push_str(&format!("Graph (start: {s})\n")),
27            (None, Some(v)) => out.push_str(&format!("Graph (start: <unset>, version: {v})\n")),
28            (None, None) => out.push_str("Graph (start: <unset>)\n"),
29        }
30
31        let mut names: Vec<&String> = self.graph.nodes.keys().collect();
32        names.sort();
33        out.push_str("  nodes:\n");
34        for n in names {
35            out.push_str(&format!("    - {n}\n"));
36        }
37
38        let edges = extract_edges(self);
39        if !edges.is_empty() {
40            out.push_str("  edges:\n");
41            for e in edges {
42                out.push_str(&format!("    {} -> {}\n", e.from, e.to));
43            }
44        }
45        out
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52    use crate::builder::Graph;
53    use crate::goto::Goto;
54    use crate::node::{node_fn, NodeOut};
55
56    #[derive(Default, Clone)]
57    struct S;
58    #[derive(Default)]
59    struct SU;
60    impl GraphState for S {
61        type Update = SU;
62        fn apply(&mut self, _: Self::Update) {}
63    }
64
65    #[test]
66    fn ascii_lists_nodes_and_edges() {
67        let g = Graph::<S>::new()
68            .node(
69                "a",
70                node_fn::<S, _, _>("a", |_s, _c| async move {
71                    Ok(NodeOut {
72                        update: SU,
73                        goto: Goto::end(),
74                    })
75                }),
76            )
77            .start_at("a")
78            .compile()
79            .unwrap();
80
81        let s = g.to_ascii();
82        assert!(s.contains("start: a"));
83        assert!(s.contains("- a"));
84    }
85}