Skip to main content

graphs_tui/
lib.rs

1#![allow(clippy::too_many_arguments)]
2#![allow(clippy::collapsible_else_if)]
3
4//! graphs-tui - Terminal renderer for Mermaid and D2 diagrams
5//!
6//! # Mermaid Flowchart Example
7//! ```
8//! use graphs_tui::{render_mermaid_to_tui, RenderOptions};
9//!
10//! let input = "flowchart LR\nA[Start] --> B[End]";
11//! let result = render_mermaid_to_tui(input, RenderOptions::default()).unwrap();
12//! println!("{}", result.output);
13//! for warning in &result.warnings {
14//!     eprintln!("Warning: {}", warning);
15//! }
16//! ```
17//!
18//! # State Diagram Example
19//! ```
20//! use graphs_tui::{render_state_diagram, RenderOptions};
21//!
22//! let input = "stateDiagram-v2\n    [*] --> Idle\n    Idle --> Running";
23//! let result = render_state_diagram(input, RenderOptions::default()).unwrap();
24//! println!("{}", result.output);
25//! ```
26//!
27//! # Pie Chart Example
28//! ```
29//! use graphs_tui::{render_pie_chart, RenderOptions};
30//!
31//! let input = "pie\n    \"Chrome\" : 65\n    \"Firefox\" : 35";
32//! let result = render_pie_chart(input, RenderOptions::default()).unwrap();
33//! println!("{}", result.output);
34//! ```
35//!
36//! # D2 Example
37//! ```
38//! use graphs_tui::{render_d2_to_tui, RenderOptions};
39//!
40//! let input = "A -> B: connection";
41//! let result = render_d2_to_tui(input, RenderOptions::default()).unwrap();
42//! println!("{}", result.output);
43//! ```
44//!
45//! # Sequence Diagram Example
46//! ```
47//! use graphs_tui::{render_sequence_diagram, RenderOptions};
48//!
49//! let input = "sequenceDiagram\n    Alice->>Bob: Hello\n    Bob-->>Alice: Hi!";
50//! let result = render_sequence_diagram(input, RenderOptions::default()).unwrap();
51//! println!("{}", result.output);
52//! ```
53//!
54//! # Auto-detect Format
55//! ```
56//! use graphs_tui::{render_diagram, RenderOptions};
57//!
58//! let mermaid_input = "flowchart LR\nA --> B";
59//! let d2_input = "A -> B";
60//!
61//! // Automatically detects format
62//! let _ = render_diagram(mermaid_input, RenderOptions::default());
63//! let _ = render_diagram(d2_input, RenderOptions::default());
64//! ```
65
66mod d2_parser;
67mod error;
68mod grid;
69mod layout;
70mod parser;
71mod pie_parser;
72mod renderer;
73mod seq_parser;
74mod state_parser;
75mod timeline_parser;
76mod types;
77
78pub use error::MermaidError;
79pub use types::{
80    Direction, Edge, EdgeStyle, Graph, Node, NodeId, NodeShape, RenderOptions, RenderResult,
81    Subgraph,
82};
83
84use d2_parser::parse_d2;
85use layout::compute_layout;
86use parser::parse_mermaid;
87use pie_parser::{parse_pie_chart as parse_pie, render_pie_chart as render_pie};
88use renderer::render_graph;
89use seq_parser::{parse_sequence_diagram as parse_seq, render_sequence_diagram as render_seq};
90use state_parser::parse_state_diagram;
91use timeline_parser::{parse_timeline as parse_tl, render_timeline as render_tl};
92
93/// Diagram format
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum DiagramFormat {
96    /// Mermaid flowchart syntax
97    Mermaid,
98    /// Mermaid state diagram
99    StateDiagram,
100    /// Mermaid sequence diagram
101    SequenceDiagram,
102    /// Mermaid pie chart
103    PieChart,
104    /// Mermaid timeline
105    Timeline,
106    /// D2 diagram language
107    D2,
108}
109
110/// Detect the diagram format from input
111pub fn detect_format(input: &str) -> DiagramFormat {
112    let trimmed = input.trim();
113    let lower = trimmed.to_lowercase();
114
115    // Check for specific diagram types first
116    if lower.starts_with("sequencediagram") {
117        return DiagramFormat::SequenceDiagram;
118    }
119    if lower.starts_with("statediagram") {
120        return DiagramFormat::StateDiagram;
121    }
122    if lower.starts_with("pie") {
123        return DiagramFormat::PieChart;
124    }
125    if lower.starts_with("timeline") {
126        return DiagramFormat::Timeline;
127    }
128
129    // Mermaid flowchart indicators
130    if trimmed.starts_with("flowchart")
131        || trimmed.starts_with("graph ")
132        || trimmed.contains("-->")
133        || trimmed.contains("-.-")
134        || trimmed.contains("==>")
135    {
136        return DiagramFormat::Mermaid;
137    }
138
139    // D2 uses different arrow syntax
140    // D2: ->, <-, <->, --
141    // Mermaid: -->, <--, <-->, ---
142
143    DiagramFormat::D2
144}
145
146/// Render diagram with auto-detection of format
147///
148/// # Arguments
149/// * `input` - Diagram syntax string (Mermaid, State, Pie, or D2)
150/// * `options` - Rendering options
151///
152/// # Returns
153/// * `Ok(RenderResult)` - Rendered diagram with any warnings
154/// * `Err(MermaidError)` - Parse or layout error
155pub fn render_diagram(input: &str, options: RenderOptions) -> Result<RenderResult, MermaidError> {
156    match detect_format(input) {
157        DiagramFormat::Mermaid => render_mermaid_to_tui(input, options),
158        DiagramFormat::StateDiagram => render_state_diagram(input, options),
159        DiagramFormat::SequenceDiagram => render_sequence_diagram(input, options),
160        DiagramFormat::PieChart => render_pie_chart(input, options),
161        DiagramFormat::Timeline => render_timeline(input, options),
162        DiagramFormat::D2 => render_d2_to_tui(input, options),
163    }
164}
165
166/// Render mermaid flowchart syntax to terminal-displayable text
167///
168/// # Arguments
169/// * `input` - Mermaid flowchart syntax string
170/// * `options` - Rendering options (ASCII mode, max width)
171///
172/// # Returns
173/// * `Ok(RenderResult)` - Rendered diagram with any warnings
174/// * `Err(MermaidError)` - Parse or layout error
175pub fn render_mermaid_to_tui(
176    input: &str,
177    options: RenderOptions,
178) -> Result<RenderResult, MermaidError> {
179    let mut graph = parse_mermaid(input)?;
180    let warnings = compute_layout(&mut graph);
181    Ok(RenderResult {
182        output: render_graph(&graph, &options),
183        warnings,
184    })
185}
186
187/// Render mermaid state diagram to terminal-displayable text
188///
189/// # Arguments
190/// * `input` - Mermaid state diagram syntax string
191/// * `options` - Rendering options (ASCII mode, max width)
192///
193/// # Returns
194/// * `Ok(RenderResult)` - Rendered diagram with any warnings
195/// * `Err(MermaidError)` - Parse or layout error
196pub fn render_state_diagram(
197    input: &str,
198    options: RenderOptions,
199) -> Result<RenderResult, MermaidError> {
200    let mut graph = parse_state_diagram(input)?;
201    let warnings = compute_layout(&mut graph);
202    Ok(RenderResult {
203        output: render_graph(&graph, &options),
204        warnings,
205    })
206}
207
208/// Render mermaid pie chart to terminal-displayable text
209///
210/// Pie charts are rendered as horizontal bar charts in terminal.
211///
212/// # Arguments
213/// * `input` - Mermaid pie chart syntax string
214/// * `options` - Rendering options
215///
216/// # Returns
217/// * `Ok(RenderResult)` - Rendered chart with any warnings
218/// * `Err(MermaidError)` - Parse error
219pub fn render_pie_chart(input: &str, options: RenderOptions) -> Result<RenderResult, MermaidError> {
220    let chart = parse_pie(input)?;
221    Ok(RenderResult {
222        output: render_pie(&chart, &options),
223        warnings: Vec::new(),
224    })
225}
226
227/// Render D2 diagram syntax to terminal-displayable text
228///
229/// # Arguments
230/// * `input` - D2 diagram syntax string
231/// * `options` - Rendering options (ASCII mode, max width)
232///
233/// # Returns
234/// * `Ok(RenderResult)` - Rendered diagram with any warnings
235/// * `Err(MermaidError)` - Parse or layout error
236pub fn render_d2_to_tui(
237    input: &str,
238    options: RenderOptions,
239) -> Result<RenderResult, MermaidError> {
240    let mut graph = parse_d2(input)?;
241    let warnings = compute_layout(&mut graph);
242    Ok(RenderResult {
243        output: render_graph(&graph, &options),
244        warnings,
245    })
246}
247
248/// Render mermaid sequence diagram to terminal-displayable text
249///
250/// # Arguments
251/// * `input` - Mermaid sequence diagram syntax string
252/// * `options` - Rendering options (ASCII mode, max width)
253///
254/// # Returns
255/// * `Ok(RenderResult)` - Rendered diagram with any warnings
256/// * `Err(MermaidError)` - Parse error
257pub fn render_sequence_diagram(
258    input: &str,
259    options: RenderOptions,
260) -> Result<RenderResult, MermaidError> {
261    let diagram = parse_seq(input)?;
262    Ok(RenderResult {
263        output: render_seq(&diagram, &options),
264        warnings: Vec::new(),
265    })
266}
267
268/// Render mermaid timeline diagram to terminal-displayable text
269///
270/// # Arguments
271/// * `input` - Mermaid timeline syntax string
272/// * `options` - Rendering options (ASCII mode, max width)
273///
274/// # Returns
275/// * `Ok(RenderResult)` - Rendered diagram with any warnings
276/// * `Err(MermaidError)` - Parse error
277pub fn render_timeline(input: &str, options: RenderOptions) -> Result<RenderResult, MermaidError> {
278    let timeline = parse_tl(input)?;
279    Ok(RenderResult {
280        output: render_tl(&timeline, &options),
281        warnings: Vec::new(),
282    })
283}