Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 33 additions & 14 deletions crates/cli/src/banner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@

use std::io::IsTerminal;

/// Filled-block NeMo Relay figlet with a per-row right shift so the letters lean italic. Six
/// content rows; the renderer prepends one blank row above and appends one below for spacing
/// and the docked version tag.
/// Filled-block NeMo Relay figlet generated with ANSI Shadow. Six content rows; the renderer
/// prepends one blank row above and appends one below for spacing and the docked version tag.
const BANNER_LINES: &[&str] = &[
" ███╗ ██╗███████╗███╗ ███╗ ██████╗ ███████╗██╗ █████╗ ██╗ ██╗",
" ████╗ ██║██╔════╝████╗ ████║██╔═══██╗ ██╔════╝██║ ██╔══██╗██║ ██║",
" ██╔██╗ ██║█████╗ ██╔████╔██║██║ ██║ █████╗ ██║ ██║ ██║██║ █╗██║",
" ██║╚██╗██║██╔══╝ ██║╚██╔╝██║██║ ██║ ██╔══╝ ██║ ██║ ██║██║██║██║",
" ██║ ╚████║███████╗██║ ╚═╝ ██║╚██████╔╝ ██║ ███████╗╚██████╔╝╚███╔███╔╝",
" ╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚══════╚═════ ╚══╝╚══╝",
"███╗ ██╗███████╗███╗ ███╗ ██████╗ ██████╗ ███████╗██╗ █████╗ ██╗ ██╗",
"████╗ ██║██╔════╝████╗ ████║██╔═══██╗ ██╔══██╗██╔════╝██║ ██╔══██╗╚██╗ ██╔╝",
"██╔██╗ ██║█████╗ ██╔████╔██║██║ ██║ ██████╔╝█████╗ ██║ ███████║ ╚████╔╝",
"██║╚██╗██║██╔══╝ ██║╚██╔╝██║██║ ██║ ██╔══██╗██╔══╝ ██║ ██╔══██╚██╔╝",
"██║ ╚████║███████╗██║ ╚═╝ ██║╚██████╔╝ ██║ ██║███████╗███████╗██║ ██║ ██║",
"╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ ╚═╝",
];

/// Banner geometry (visual rows including the top and bottom spacing rails).
Expand All @@ -32,7 +31,7 @@ const BOTTOM_RAIL: usize = FIGLET_ROWS + 1; // row index of the row below the fi
const TOTAL_ROWS: usize = FIGLET_ROWS + 2; // top rail + 6 figlet rows + bottom rail

/// Version tag position, measured in columns.
const COL_END: usize = 92; // right edge below "Flow"
const COL_END: usize = 92; // version tag dock

const MIN_WIDTH: usize = 105;

Expand Down Expand Up @@ -141,15 +140,35 @@ fn build_grid(width: usize) -> Vec<Vec<char>> {
// Empty top rail, the 6 figlet rows, and an empty bottom rail. Each cell is a single char
// because the figlet's block and box glyphs render as one display column in target terminals.
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)));
grid.extend(
BANNER_LINES
.iter()
.map(|line| padded_row(line, width, start_col)),
);
grid.push(vec![' '; width]);
grid
}

fn padded_row(line: &str, width: usize) -> Vec<char> {
let mut row: Vec<char> = line.chars().collect();
row.resize(width, ' ');
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
}

Expand Down
42 changes: 42 additions & 0 deletions crates/cli/tests/coverage/banner_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,30 @@ fn render_frame_settled_contains_figlet_glyphs() {
let frame = render_frame(false);
// ANSI Shadow figlet uses filled blocks and box-drawing corners.
assert!(frame.contains('█'), "frame missing figlet block glyph");
assert!(
!frame.contains("_/=|") && !frame.contains("|=\\_") && !frame.contains("╔██╗██╗"),
"frame should not include flag columns"
);
assert!(
frame.contains('╗') || frame.contains('╔'),
"frame missing figlet corners"
);
}

#[test]
fn figlet_rows_are_centered_as_one_block() {
let frame = render_frame(false);
let rows = frame_grid_rows(&frame);
let figlet_rows = &rows[1..=FIGLET_ROWS];
let left_edges: Vec<usize> = figlet_rows.iter().map(|row| first_art_col(row)).collect();
let expected_left_edge = (frame_inner_width(&frame) - banner_art_width()) / 2;

assert!(
left_edges.iter().all(|edge| *edge == expected_left_edge),
"figlet rows should share centered left edge {expected_left_edge}; got {left_edges:?}"
);
}

#[test]
fn render_frame_plain_mode_has_no_ansi_escapes() {
let frame = render_frame(false);
Expand Down Expand Up @@ -66,3 +84,27 @@ fn docked_frame_includes_version_tag() {
"docked frame should not include a bullet dot before the version"
);
}

fn frame_grid_rows(frame: &str) -> Vec<&str> {
frame
.lines()
.filter(|line| line.starts_with('│') && line.ends_with('│'))
.map(|line| line.trim_matches('│'))
.collect()
}

fn frame_inner_width(frame: &str) -> usize {
frame
.lines()
.find(|line| line.starts_with('╭'))
.expect("frame should include a top border")
.trim_matches(['╭', '╮'])
.chars()
.count()
}

fn first_art_col(row: &str) -> usize {
row.chars()
.position(|ch| !ch.is_whitespace())
.expect("figlet row should contain art")
}
Loading