use crate::ascii_table::{AsciiColumnFormat, AsciiTable, write};
use crate::bin_table::Value;
use crate::bin_table::to_bin_table::{Error, collect_rows, column_names};
use serde::Serialize;
const DECIMALS: usize = 16;
pub fn to_ascii_table<T: Serialize>(data: &T) -> Result<AsciiTable, Error> {
let rows = collect_rows(data)?;
let names = column_names(&rows)?;
if names.is_empty() {
return Ok(AsciiTable::from_columns(&[]));
}
let columns: Vec<(String, AsciiColumnFormat)> = names
.iter()
.enumerate()
.map(|(index, name)| {
(
(*name).to_string(),
column_format(rows.iter().map(|row| &row[index].1.value)),
)
})
.collect();
let mut table = AsciiTable::from_columns(&columns);
for row in &rows {
let values: Vec<Value> = row.iter().map(|(_, column)| column.value.clone()).collect();
table
.push_row(&values)
.map_err(|error| Error::Custom(error.to_string()))?;
}
Ok(table)
}
fn column_format<'a>(values: impl Iterator<Item = &'a Value> + Clone) -> AsciiColumnFormat {
let is = |kind: fn(&Value) -> bool| values.clone().any(kind);
if is(|value| matches!(value, Value::String(_) | Value::StringArray(_))) {
return AsciiColumnFormat::Character(width(values, AsciiColumnFormat::Character(0)));
}
if is(|value| {
matches!(
value,
Value::F32(_) | Value::F64(_) | Value::C32(_) | Value::M64(_)
)
}) {
if let Some(decimals) = fixed_decimals(values.clone()) {
let candidate = AsciiColumnFormat::Fixed(0, decimals);
return AsciiColumnFormat::Fixed(width(values, candidate), decimals);
}
let candidate = AsciiColumnFormat::Exponential(0, DECIMALS);
return AsciiColumnFormat::Exponential(width(values, candidate), DECIMALS);
}
AsciiColumnFormat::Integer(width(values, AsciiColumnFormat::Integer(0)))
}
fn fixed_decimals<'a>(values: impl Iterator<Item = &'a Value> + Clone) -> Option<usize> {
const MOST: usize = 12;
let numbers: Vec<f64> = values
.flat_map(|value| match value {
Value::F32(values) => values.iter().map(|value| *value as f64).collect(),
Value::F64(values) => values.clone(),
_ => Vec::new(),
})
.collect();
if numbers
.iter()
.any(|number| !number.is_finite() || number.abs() >= 1e15)
{
return None;
}
(0..=MOST).find(|decimals| {
numbers.iter().all(|number| {
format!("{:.*}", decimals, number)
.parse::<f64>()
.is_ok_and(|read| read == *number)
})
})
}
fn width<'a>(values: impl Iterator<Item = &'a Value>, format: AsciiColumnFormat) -> usize {
values
.map(|value| write::render(value, format, None).len())
.max()
.unwrap_or(1)
.max(1)
}