use std::io::Write;
use narwhal_core::{ColumnHeader, Row, Value};
use super::error::ExportError;
use super::format::MarkdownOptions;
pub(super) fn write_markdown<W: Write>(
writer: &mut W,
columns: &[ColumnHeader],
rows: &[Row],
options: MarkdownOptions,
) -> Result<(), ExportError> {
if columns.is_empty() {
writer.write_all(b"_no result to export_\n")?;
return Ok(());
}
let alignments: Vec<Alignment> = columns.iter().map(infer_alignment).collect();
writer.write_all(b"|")?;
for column in columns {
writer.write_all(b" ")?;
write_escaped(writer, &column.name)?;
writer.write_all(b" |")?;
}
writer.write_all(b"\n")?;
writer.write_all(b"|")?;
for align in &alignments {
match align {
Alignment::Left => writer.write_all(b" :--- |")?,
Alignment::Right => writer.write_all(b" ---: |")?,
}
}
writer.write_all(b"\n")?;
let (visible, truncated) = match options.row_limit {
Some(limit) if rows.len() > limit => (&rows[..limit], Some(rows.len() - limit)),
_ => (rows, None),
};
for row in visible {
writer.write_all(b"|")?;
for value in &row.0 {
writer.write_all(b" ")?;
write_cell(writer, value)?;
writer.write_all(b" |")?;
}
writer.write_all(b"\n")?;
}
if let Some(omitted) = truncated {
writeln!(
writer,
"\n_…{omitted} more row{s} truncated_",
s = if omitted == 1 { "" } else { "s" }
)?;
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Alignment {
Left,
Right,
}
fn infer_alignment(column: &ColumnHeader) -> Alignment {
let lower = column.data_type.to_ascii_lowercase();
const NUMERIC_HINTS: &[&str] = &[
"int", "decimal", "numeric", "real", "float", "double", "money", "serial",
];
if NUMERIC_HINTS.iter().any(|hint| lower.contains(hint)) {
Alignment::Right
} else {
Alignment::Left
}
}
fn write_cell<W: Write>(writer: &mut W, value: &Value) -> Result<(), ExportError> {
match value {
Value::Null => {
writer.write_all(b"(null)")?;
}
Value::Bytes(b) => {
write!(writer, "<{} bytes>", b.len())?;
}
other => write_escaped(writer, &other.render())?,
}
Ok(())
}
fn write_escaped<W: Write>(writer: &mut W, text: &str) -> Result<(), ExportError> {
for ch in text.chars() {
match ch {
'\\' => writer.write_all(br"\\")?,
'|' => writer.write_all(br"\|")?,
'\n' | '\r' => writer.write_all(b"<br>")?,
other => {
let mut buf = [0u8; 4];
writer.write_all(other.encode_utf8(&mut buf).as_bytes())?;
}
}
}
Ok(())
}