use std::fmt::Write;
use crate::{DxValue, DxData, Schema, TypeHint};
use crate::base62::decode_base62;
pub fn format_human(data: &DxData) -> Result<String, std::fmt::Error> {
let mut output = String::with_capacity(512);
if let Some(obj) = data.objects.first() {
format_object(&mut output, obj, "")?;
}
for (name, items) in &data.arrays {
writeln!(output)?;
format_array(&mut output, name, items)?;
}
for schema in &data.schemas {
writeln!(output)?;
format_table(&mut output, schema, data)?;
}
Ok(output)
}
fn format_object(
output: &mut String,
obj: &std::collections::HashMap<String, DxValue>,
prefix: &str,
) -> Result<(), std::fmt::Error> {
let max_key_len = obj.keys().map(|k| k.len()).max().unwrap_or(0);
for (key, value) in obj {
let full_key = if prefix.is_empty() {
key.clone()
} else {
format!("^{}", key)
};
let padding = " ".repeat(max_key_len - key.len());
match value {
DxValue::String(s) => writeln!(output, "{}{} : {}", full_key, padding, s)?,
DxValue::Int(n) => writeln!(output, "{}{} : {}", full_key, padding, n)?,
DxValue::Float(f) => writeln!(output, "{}{} : {}", full_key, padding, f)?,
DxValue::Bool(b) => writeln!(output, "{}{} : {}", full_key, padding, if *b { "true" } else { "false" })?,
_ => {}
}
}
Ok(())
}
fn format_array(
output: &mut String,
name: &str,
items: &[DxValue],
) -> Result<(), std::fmt::Error> {
write!(output, "{:<15} > ", name)?;
for (i, item) in items.iter().enumerate() {
if i > 0 {
write!(output, " | ")?;
}
match item {
DxValue::String(s) => write!(output, "{}", s)?,
DxValue::Int(n) => write!(output, "{}", n)?,
DxValue::Bool(b) => write!(output, "{}", if *b { "true" } else { "false
DxValue::Bool(b) => write!(output, "{}", if *b { "+" } else { "-" })?,
_ => {}
}
}
writeln!(output)?;
Ok(())
}
/// Format table with aligned columns and header comments
fn format_table(
output: &mut String,
schema: &Schema,
data: &DxData,
) -> Result<(), std::fmt::Error> {
// Count rows and columns
let row_count = data.tables.get(&schema.name).map(|rows| rows.len()).unwrap_or(0);
let col_count = schema.columns.len();
// Write informative comment header
writeln!(output, "# {} TABLE ({} Rows, {} Columns)",
schema.name.to_uppercase(), row_count, col_count)?;
writeln!(output, "# {}", "-".repeat(58))?;
// Calculate column widths
let mut col_widths: Vec<usize> = schema.columns.iter()
.map(|col| col.name.len().max(4))
.collect();
// Scan data to find max widths
if let Some(rows) = data.tables.get(&schema.name) {
for row in rows {
for (i, (col, value)) in schema.columns.iter().zip(row.iter()).enumerate() {
let width = match value {
DxValue::String(s) => s.len(),
DxValue::Int(n) => n.to_string().len(),
DxValue::Float(f) => format!("{:.1}", f).len(),
DxValue::Bool(_) => 1,
_ => 0,
};
col_widths[i] = col_widths[i].max(width);
}
}
}
// Write column headers (capitalized)
for (i, col) in schema.columns.iter().enumerate() {
let name = if col.is_anonymous_auto_increment() {
"ID".to_string()
} else {
capitalize_first(&col.name)
};
write!(output, "{:<width$} ", name, width = col_widths[i])?;
}
writeln!(output)?;
// Write data rows
if let Some(rows) = data.tables.get(&schema.name) {
for row in rows {
for (i, (col, value)) in schema.columns.iter().zip(row.iter()).enumerate() {
let formatted = match value {
DxValue::String(s) => s.clone(),
DxValue::Int(n) => {
// Decode Base62 if column type is Base62
if col.type_hint == TypeHint::Base62 {
n.to_string()
} else {
n.to_string()
}
},
DxValue::Float(f) => format!("{:.1}", f),
// Use full boolean words for maximum human readability
DxValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
_ => "".to_string(),
};
write!(output, "{:<width$} ", formatted, width = col_widths[i])?;
}
writeln!(output)?;
}
}
Ok(())
}
/// Capitalize first letter of string
fn capitalize_first(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().chain(chars).collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_human_hikes() {
// This would test the full formatting
// Implementation depends on having parsed data
}
#[test]
fn test_capitalize() {
assert_eq!(capitalize_first("name"), "Name");
assert_eq!(capitalize_first("id"), "Id");
assert_eq!(capitalize_first(""), "");
}
}