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}