use crate::DiagramTarget;
use pipecircuit::{BindingDescription, DeclarationLocation, PipeDescription};
pub(super) fn render(
board: &str,
pipes: &[PipeDescription],
bindings: Option<&[BindingDescription]>,
target: DiagramTarget,
) -> String {
match target {
DiagramTarget::TextTree | DiagramTarget::AnsiTextTree => text(board, pipes, bindings),
DiagramTarget::MermaidFlowchart => graph(board, pipes, bindings, true),
DiagramTarget::GraphvizDot => graph(board, pipes, bindings, false),
}
}
pub(super) fn source(location: DeclarationLocation) -> String {
format!(
"{}:{}:{} ({})",
location.file, location.line, location.column, location.package
)
}
fn text(board: &str, pipes: &[PipeDescription], bindings: Option<&[BindingDescription]>) -> String {
let mut text = format!(
"Experimental inspection prototype: development review only; not ready for application consumption.\n\nProgram: {}\nSwitchboard: routes messages between the named pipes below.\n\nRead directions from each pipe's perspective:\n emits (Out) -> switchboard -> receives (In)\nThese are message families, not a request/reply ordering guarantee.\n",
short_type(board)
);
for pipe in pipes {
text.push_str(&format!(
"\n{} [{}]\n",
pipe.name,
short_type(pipe.protocol)
));
for line in pipe.documentation {
text.push_str(&format!(" {}\n", line.trim()));
}
text.push_str(&format!(
" emits to switchboard: {}\n receives from switchboard: {}\n declared at: {}\n",
short_type(pipe.output),
short_type(pipe.input),
source(pipe.declaration)
));
if let Some(binding) = bindings.and_then(|all| all.iter().find(|b| b.name == pipe.name)) {
text.push_str(&format!(
" installed with: {}\n",
short_type(binding.driver)
));
for line in binding.documentation {
text.push_str(&format!(" {}\n", line.trim()));
}
text.push_str(&format!(" wired at: {}\n", source(binding.declaration)));
}
}
text.push_str(
"\nScope: declared boundaries; runtime routes and reachability are not inferred.\n",
);
if bindings.is_some() {
text.push_str("Transport occupancy, health, remote placement and compatibility are not measured here.\n");
}
text
}
fn label(value: &str, mermaid: bool) -> String {
if mermaid {
value
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\n', "<br/>")
} else {
value
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
}
}
fn node(output: &mut String, id: &str, value: &str, mermaid: bool) {
let value = label(value, mermaid);
if mermaid {
output.push_str(&format!(" {id}[\"{value}\"]\n"));
} else {
output.push_str(&format!(" {id} [label=\"{value}\"];\n"));
}
}
fn edge(output: &mut String, from: &str, to: &str, value: &str, mermaid: bool) {
if mermaid {
output.push_str(&format!(" {from} -. {value} .-> {to}\n"));
} else {
output.push_str(&format!(
" {from} -> {to} [style=dashed,label=\"{value}\"];\n"
));
}
}
fn graph(
board: &str,
pipes: &[PipeDescription],
bindings: Option<&[BindingDescription]>,
mermaid: bool,
) -> String {
let mut output = if mermaid {
String::from(
"%% Experimental inspection prototype; not ready for application consumption.\nflowchart LR\n",
)
} else {
String::from(
"// Experimental inspection prototype; not ready for application consumption.\ndigraph program {\n",
)
};
node(&mut output, "board", &short_type(board), mermaid);
node(
&mut output,
"scope",
"Declared boundaries only; routes and reachability are runtime decisions",
mermaid,
);
for (index, pipe) in pipes.iter().enumerate() {
let id = format!("pipe{index}");
node(
&mut output,
&id,
&format!(
"{}: {}\n{}\nReceives: {}\nEmits: {}\n{}",
pipe.name,
short_type(pipe.protocol),
pipe.documentation
.iter()
.map(|line| line.trim())
.collect::<Vec<_>>()
.join("\n"),
short_type(pipe.input),
short_type(pipe.output),
source(pipe.declaration)
),
mermaid,
);
edge(&mut output, &id, "board", "emits Out", mermaid);
edge(&mut output, "board", &id, "receives In", mermaid);
}
if let Some(bindings) = bindings {
node(
&mut output,
"physical_scope",
"Types only; occupancy, health, remote placement and compatibility not inspected",
mermaid,
);
for (index, binding) in bindings.iter().enumerate() {
let id = format!("binding{index}");
node(
&mut output,
&id,
&format!(
"{}\n{}\n{}",
binding.name,
short_type(binding.binding),
source(binding.declaration)
),
mermaid,
);
if let Some(index) = pipes.iter().position(|pipe| pipe.name == binding.name) {
edge(
&mut output,
&format!("pipe{index}"),
&id,
"installed as",
mermaid,
);
}
}
}
if !mermaid {
output.push_str("}\n");
}
output
}
pub(super) fn short_type(value: &str) -> String {
let mut rendered = String::new();
let mut path = String::new();
for character in value.chars() {
if character.is_alphanumeric() || character == '_' || character == ':' {
path.push(character);
} else {
rendered.push_str(path.rsplit("::").next().unwrap_or(""));
path.clear();
rendered.push(character);
}
}
rendered.push_str(path.rsplit("::").next().unwrap_or(""));
rendered
}