use std::io::IsTerminal;
const BANNER_LINES: &[&str] = &[
"███╗ ██╗███████╗███╗ ███╗ ██████╗ ██████╗ ███████╗██╗ █████╗ ██╗ ██╗",
"████╗ ██║██╔════╝████╗ ████║██╔═══██╗ ██╔══██╗██╔════╝██║ ██╔══██╗╚██╗ ██╔╝",
"██╔██╗ ██║█████╗ ██╔████╔██║██║ ██║ ██████╔╝█████╗ ██║ ███████║ ╚████╔╝",
"██║╚██╗██║██╔══╝ ██║╚██╔╝██║██║ ██║ ██╔══██╗██╔══╝ ██║ ██╔══██║ ╚██╔╝",
"██║ ╚████║███████╗██║ ╚═╝ ██║╚██████╔╝ ██║ ██║███████╗███████╗██║ ██║ ██║",
"╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝",
];
const FIGLET_ROWS: usize = 6;
const BOTTOM_RAIL: usize = FIGLET_ROWS + 1; const TOTAL_ROWS: usize = FIGLET_ROWS + 2;
const COL_END: usize = 92;
const MIN_WIDTH: usize = 105;
const NVIDIA_GREEN: &str = "\x1b[38;5;112m";
const DOCK_TAG: &str = "\x1b[2;38;5;112m";
const RESET: &str = "\x1b[0m";
const BORDER_TL: char = '╭';
const BORDER_TR: char = '╮';
const BORDER_BL: char = '╰';
const BORDER_BR: char = '╯';
const BORDER_H: char = '─';
const BORDER_V: char = '│';
#[derive(Clone, Copy)]
struct DockTagSpan {
row: usize,
start: usize,
end: usize,
}
enum CellStyle {
DockTag,
Figlet,
Plain,
}
fn supports_banner() -> bool {
let stdout_is_terminal = std::io::stdout().is_terminal();
let ci = std::env::var("CI").ok();
let term = std::env::var("TERM").ok();
supports_banner_for(
stdout_is_terminal,
std::env::var_os("NO_COLOR").is_some(),
ci.as_deref(),
term.as_deref(),
terminal_width(),
)
}
fn supports_banner_for(
stdout_is_terminal: bool,
no_color: bool,
ci: Option<&str>,
term: Option<&str>,
width: Option<usize>,
) -> bool {
if !stdout_is_terminal {
return false;
}
if no_color {
return false;
}
if matches!(ci, Some("true" | "1")) {
return false;
}
if term == Some("dumb") {
return false;
}
width.is_some_and(|w| w >= MIN_WIDTH)
}
fn terminal_width() -> Option<usize> {
terminal_width_for(
std::io::stdout().is_terminal(),
std::env::var("COLUMNS").ok().as_deref(),
)
}
fn terminal_width_for(stdout_is_terminal: bool, columns: Option<&str>) -> Option<usize> {
if !stdout_is_terminal {
return None;
}
columns.and_then(|v| v.parse::<usize>().ok()).or(Some(120))
}
#[cfg(test)]
pub(crate) fn render_frame(color: bool) -> String {
render_frame_inner(color, false)
}
pub(crate) fn render_docked_frame(color: bool) -> String {
render_frame_inner(color, true)
}
fn render_frame_inner(color: bool, docked: bool) -> String {
let mut out = String::with_capacity(BANNER_LINES.iter().map(|l| l.len() + 64).sum());
out.push('\n');
let dock_tag = format!(" v{}", env!("CARGO_PKG_VERSION"));
let max_width = frame_width(&dock_tag);
let mut grid = build_grid(max_width);
let dock_tag_span = docked.then(|| overlay_dock_tag(&mut grid, &dock_tag));
push_border_line(&mut out, BORDER_TL, BORDER_TR, max_width, color);
for (row_idx, row) in grid.iter().enumerate() {
push_grid_row(&mut out, row_idx, row, dock_tag_span, color);
out.push('\n');
}
push_border_line(&mut out, BORDER_BL, BORDER_BR, max_width, color);
out
}
fn frame_width(dock_tag: &str) -> usize {
let dock_width_needed = COL_END + dock_tag.chars().count() + 2;
BANNER_LINES
.iter()
.map(|l| l.chars().count())
.max()
.unwrap_or(0)
.max(dock_width_needed)
}
fn build_grid(width: usize) -> Vec<Vec<char>> {
let mut grid = Vec::with_capacity(TOTAL_ROWS);
let art_width = banner_art_width();
let start_col = width.saturating_sub(art_width) / 2;
grid.push(vec![' '; width]);
grid.extend(
BANNER_LINES
.iter()
.map(|line| padded_row(line, width, start_col)),
);
grid.push(vec![' '; width]);
grid
}
fn banner_art_width() -> usize {
BANNER_LINES
.iter()
.map(|line| line.chars().count())
.max()
.unwrap_or(0)
}
fn padded_row(line: &str, width: usize, start_col: usize) -> Vec<char> {
let mut row = vec![' '; width];
for (index, ch) in line.chars().enumerate() {
if let Some(cell) = row.get_mut(start_col + index) {
*cell = ch;
}
}
row
}
fn overlay_dock_tag(grid: &mut [Vec<char>], dock_tag: &str) -> DockTagSpan {
let span = DockTagSpan {
row: BOTTOM_RAIL,
start: COL_END,
end: COL_END + dock_tag.chars().count(),
};
for (index, ch) in dock_tag.chars().enumerate() {
grid[span.row][span.start + index] = ch;
}
span
}
fn push_grid_row(
out: &mut String,
row_idx: usize,
row: &[char],
dock_tag_span: Option<DockTagSpan>,
color: bool,
) {
push_vertical_border(out, color);
for (col_idx, ch) in row.iter().copied().enumerate() {
push_cell(
out,
ch,
cell_style(ch, row_idx, col_idx, dock_tag_span),
color,
);
}
push_vertical_border(out, color);
}
fn push_vertical_border(out: &mut String, color: bool) {
push_styled_char(out, BORDER_V, Some(NVIDIA_GREEN), color);
}
fn push_cell(out: &mut String, ch: char, style: CellStyle, color: bool) {
match style {
CellStyle::DockTag => push_styled_char(out, ch, Some(DOCK_TAG), color),
CellStyle::Figlet => push_styled_char(out, ch, Some(NVIDIA_GREEN), color),
CellStyle::Plain => out.push(ch),
}
}
fn push_styled_char(out: &mut String, ch: char, style: Option<&str>, color: bool) {
if color && let Some(style) = style {
out.push_str(style);
out.push(ch);
out.push_str(RESET);
} else {
out.push(ch);
}
}
fn cell_style(
ch: char,
row_idx: usize,
col_idx: usize,
dock_tag_span: Option<DockTagSpan>,
) -> CellStyle {
if dock_tag_span.is_some_and(|span| {
row_idx == span.row && col_idx >= span.start && col_idx < span.end && ch != ' '
}) {
CellStyle::DockTag
} else if is_figlet_glyph(ch) {
CellStyle::Figlet
} else {
CellStyle::Plain
}
}
fn push_border_line(out: &mut String, left: char, right: char, inner_width: usize, color: bool) {
if color {
out.push_str(NVIDIA_GREEN);
out.push(left);
for _ in 0..inner_width {
out.push(BORDER_H);
}
out.push(right);
out.push_str(RESET);
} else {
out.push(left);
for _ in 0..inner_width {
out.push(BORDER_H);
}
out.push(right);
}
out.push('\n');
}
fn is_figlet_glyph(ch: char) -> bool {
matches!(ch, '█' | '╗' | '╔' | '╝' | '╚' | '═' | '║')
}
pub(crate) fn print_intro() {
if !supports_banner() {
print_plain_header();
return;
}
print!("{}", render_docked_frame(true));
}
pub(crate) fn print_doctor_header() {
if !supports_banner() {
print_plain_header();
return;
}
print!("{}", render_docked_frame(true));
}
fn print_plain_header() {
let version = env!("CARGO_PKG_VERSION");
println!();
println!(" NeMo Relay v{version}");
println!();
}
#[cfg(test)]
#[path = "../tests/coverage/banner_tests.rs"]
mod tests;