use anstyle::Style;
use textwrap::core::display_width;
use textwrap::{Options, WordSplitter};
fn base_opts() -> Options<'static> {
Options::new(INNER).word_splitter(WordSplitter::NoHyphenation)
}
pub(crate) const WIDTH: usize = 88;
const INNER: usize = WIDTH - 4;
pub(crate) struct Card {
rows: Vec<Row>,
}
struct Row {
styled: String,
width: usize,
}
impl Card {
pub(crate) fn new() -> Self {
Self { rows: Vec::new() }
}
pub(crate) fn blank(&mut self) {
self.rows.push(Row {
styled: String::new(),
width: 0,
});
}
pub(crate) fn line(&mut self, text: &str, style: Style, indent: usize) {
let pad = " ".repeat(indent);
self.rows.push(Row {
styled: format!("{pad}{style}{text}{style:#}"),
width: indent + display_width(text),
});
}
pub(crate) fn raw(&mut self, styled: String, plain: &str) {
self.rows.push(Row {
styled,
width: display_width(plain),
});
}
pub(crate) fn raw_indented(&mut self, styled: String, plain: &str, indent: usize) {
let pad = " ".repeat(indent);
self.rows.push(Row {
styled: format!("{pad}{styled}"),
width: indent + display_width(plain),
});
}
pub(crate) fn wrap(&mut self, text: &str, style: Style, indent: usize) {
let ind = " ".repeat(indent);
let opts = base_opts().initial_indent(&ind).subsequent_indent(&ind);
self.push_wrapped(text, style, &opts);
}
pub(crate) fn wrap_hang(&mut self, label: &str, text: &str, style: Style) {
let subs = " ".repeat(display_width(label));
let opts = base_opts().initial_indent(label).subsequent_indent(&subs);
self.push_wrapped(text, style, &opts);
}
fn push_wrapped(&mut self, text: &str, style: Style, opts: &textwrap::Options<'_>) {
for line in textwrap::wrap(text, opts) {
let width = display_width(&line);
self.rows.push(Row {
styled: format!("{style}{line}{style:#}"),
width,
});
}
}
pub(crate) fn render(&self) -> String {
let rule = "─".repeat(INNER + 2);
let mut out = format!("┌{rule}┐\n");
for row in &self.rows {
let pad = " ".repeat(INNER.saturating_sub(row.width));
out.push_str(&format!("│ {}{pad} │\n", row.styled));
}
out.push_str(&format!("└{rule}┘\n"));
out
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_rendered_line_fits_the_width_when_plain() {
let mut c = Card::new();
c.line("✗ PROPERTY VIOLATED some_canon_id", Style::new(), 0);
c.blank();
c.wrap(
"The WAL initialized flag is set true only after a successful sync of the wal-header, \
which means a non-durable header that merely exists on disk must not read as present.",
Style::new(),
0,
);
c.wrap_hang(
" why ",
"turso reports initialized from the file-existence proxy, so a header write that \
landed before the sync failed still reads as present even though it is not durable.",
Style::new(),
);
let rendered = c.render();
for line in rendered.lines() {
assert_eq!(
display_width(line),
WIDTH,
"every card line must be exactly {WIDTH} cols; got {:?}",
line
);
}
assert!(rendered.contains("wal-header"));
assert!(rendered.contains("file-existence"));
}
#[test]
fn borders_align_for_styled_and_plain_rows() {
let bold = Style::new().bold();
let mut a = Card::new();
a.line("initialized = false", bold, 2);
let mut b = Card::new();
b.line("initialized = false", Style::new(), 2);
let a_line = a.render().lines().nth(1).unwrap().to_string();
let b_line = b.render().lines().nth(1).unwrap().to_string();
assert_eq!(display_width(&a_line), display_width(&b_line));
assert_eq!(display_width(&b_line), WIDTH);
}
}