mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
## What changed Add `codex-mermaid`, a standalone crate that renders supported subsets of flowchart, sequence, state, class, and ER diagrams as Unicode text. Expose `render` for plain text and `render_spans` for semantic node, edge, and text spans that callers can style. Enforce source, diagram, canvas, and display-width limits. Return errors for unsupported input or exceeded limits without producing partial diagrams, leaving source fallback to callers. Include documentation and a stdin rendering example. ## Testing Add snapshots for each diagram family, checks for relationship endpoints, Unicode labels, semantic spans, truncated input, and size limits, plus edge reconstruction for all 512 directed three-node graphs in all four layout directions. GitOrigin-RevId: 5b4e65d9152abce5d833e4e6b12113a5a21f700e
70 lines
1.7 KiB
Rust
70 lines
1.7 KiB
Rust
//! Semantic spans for theme-independent diagram output; drawing cells never contain ANSI escapes.
|
|
|
|
/// The diagram element a caller can style with its own theme.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum Role {
|
|
Node,
|
|
Edge,
|
|
Text,
|
|
}
|
|
|
|
/// Adjacent characters with one semantic role, independent of terminal styling.
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct Span {
|
|
pub text: String,
|
|
pub role: Role,
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub(super) struct Cell {
|
|
pub symbol: char,
|
|
pub role: Role,
|
|
}
|
|
|
|
impl Cell {
|
|
pub(super) fn edge(symbol: char) -> Self {
|
|
Self {
|
|
symbol,
|
|
role: Role::Edge,
|
|
}
|
|
}
|
|
|
|
pub(super) fn node(symbol: char) -> Self {
|
|
Self {
|
|
symbol,
|
|
role: Role::Node,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn finish(rows: Vec<Vec<Cell>>) -> Vec<Vec<Span>> {
|
|
rows.into_iter()
|
|
.map(|row| {
|
|
let mut spans: Vec<Span> = Vec::new();
|
|
for Cell { symbol, role } in row {
|
|
if symbol == '\0' {
|
|
continue;
|
|
}
|
|
if let Some(last) = spans.last_mut()
|
|
&& last.role == role
|
|
{
|
|
last.text.push(symbol);
|
|
} else {
|
|
spans.push(Span {
|
|
text: symbol.to_string(),
|
|
role,
|
|
});
|
|
}
|
|
}
|
|
while let Some(last) = spans.last_mut() {
|
|
last.text.truncate(last.text.trim_end().len());
|
|
if !last.text.is_empty() {
|
|
break;
|
|
}
|
|
spans.pop();
|
|
}
|
|
spans
|
|
})
|
|
.collect()
|
|
}
|