const CYAN: &str = "\x1b[36m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const RESET: &str = "\x1b[0m";
#[derive(Debug)]
pub(super) struct Greeter<'a> {
pub(super) server_name: &'a str,
pub(super) server_version: &'a str,
pub(super) neva_version: &'a str,
pub(super) transport_label: &'a str,
pub(super) tools: &'a [String],
pub(super) prompts: &'a [String],
pub(super) resource_templates: &'a [String],
pub(super) use_color: bool,
}
impl<'a> Greeter<'a> {
pub(super) fn render(&self) -> String {
let server_line = format!("{} v{}", self.server_name, self.server_version);
let neva_line = format!("powered by neva v{}", self.neva_version);
let transport_line = format!("Transport: {}", self.transport_label);
let text_width = [
server_line.chars().count(),
neva_line.chars().count(),
transport_line.chars().count(),
]
.into_iter()
.max()
.unwrap_or(0);
let inner_width = text_width + 4;
let mut out = String::new();
out.push('╔');
out.push_str(&"═".repeat(inner_width));
out.push_str("╗\n");
for text in &[&server_line, &neva_line] {
let padding = inner_width - 2 - text.chars().count();
out.push_str("║ ");
out.push_str(text);
out.push_str(&" ".repeat(padding));
out.push_str("║\n");
}
out.push('║');
out.push_str(&" ".repeat(inner_width));
out.push_str("║\n");
let padding = inner_width - 2 - transport_line.chars().count();
out.push_str("║ ");
out.push_str(&transport_line);
out.push_str(&" ".repeat(padding));
out.push_str("║\n");
out.push('╚');
out.push_str(&"═".repeat(inner_width));
out.push_str("╝\n");
let sections: &[(&str, &str, &[String])] = &[
(CYAN, "Tools", self.tools),
(GREEN, "Prompts", self.prompts),
(YELLOW, "Resource Templates", self.resource_templates),
];
for (color, header, items) in sections {
if items.is_empty() {
continue;
}
out.push('\n');
if self.use_color {
out.push_str(color);
out.push_str(header);
out.push_str(RESET);
} else {
out.push_str(header);
}
out.push('\n');
for item in *items {
out.push_str(" \u{2022} ");
out.push_str(item);
out.push('\n');
}
}
out.push('\n');
out
}
pub(super) fn print(&self) {
eprint!("{}", self.render());
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_greeter<'a>(
server_name: &'a str,
tools: &'a [String],
prompts: &'a [String],
resource_templates: &'a [String],
use_color: bool,
) -> Greeter<'a> {
Greeter {
server_name,
server_version: "1.0.0",
neva_version: "0.2.1",
transport_label: "stdio",
tools,
prompts,
resource_templates,
use_color,
}
}
#[test]
fn it_renders_box_wide_enough_for_longest_line() {
let long_name = "My Very Long Server Name That Is Long";
let greeter = make_greeter(long_name, &[], &[], &[], false);
let output = greeter.render();
let server_line_len = format!("{} v1.0.0", long_name).chars().count();
let inner_width = server_line_len + 4;
let expected_top = format!("╔{}╗", "═".repeat(inner_width));
assert!(
output.contains(&expected_top),
"Expected top border of width {inner_width} not found in:\n{output}"
);
}
#[test]
fn it_omits_empty_sections() {
let tools = vec!["hello".to_string()];
let greeter = make_greeter("Server", &tools, &[], &[], false);
let output = greeter.render();
assert!(output.contains("Tools"), "Tools section should be present");
assert!(
!output.contains("Prompts"),
"Prompts section should be absent"
);
assert!(
!output.contains("Resource Templates"),
"Resource Templates section should be absent"
);
}
#[test]
fn it_omits_ansi_when_use_color_false() {
let tools = vec!["hello".to_string()];
let greeter = make_greeter("Server", &tools, &[], &[], false);
let output = greeter.render();
assert!(
!output.contains("\x1b["),
"No ANSI codes expected when use_color=false, got:\n{output}"
);
}
#[test]
fn it_includes_ansi_when_use_color_true() {
let tools = vec!["hello".to_string()];
let greeter = make_greeter("Server", &tools, &[], &[], true);
let output = greeter.render();
assert!(
output.contains("\x1b[36m"),
"Expected cyan ANSI code for Tools header, got:\n{output}"
);
}
#[test]
fn it_renders_only_box_when_all_sections_empty() {
let greeter = make_greeter("Server", &[], &[], &[], false);
let output = greeter.render();
assert!(output.contains("╔"), "Box top-left corner missing");
assert!(output.contains("╝"), "Box bottom-right corner missing");
assert!(!output.contains("Tools"));
assert!(!output.contains("Prompts"));
assert!(!output.contains("Resource Templates"));
assert!(
output.trim_end().ends_with('╝'),
"Output should end with box bottom"
);
}
}