Files
brahman/crates/modules/nahual/libs/meta-runtime/src/csv.rs
T
sergio b13486e240 feat(nakui): Fase 6 del ERP — export CSV de listas
Toda vista de lista gana un botón «⬇ CSV» que exporta las filas
filtradas/ordenadas (con refs resueltas y montos formateados) a un
archivo <entity>-<timestamp>.csv. Serializador to_csv (RFC 4180, con
escape) en el módulo nuevo meta-runtime/csv.rs. Refactor:
list_filtered_sorted extraído como helper compartido entre el render
de la lista y el export.

Tests de to_csv; meta-runtime 70 + meta-form 8 verdes, clippy limpio.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 19:32:48 +00:00

68 lines
1.8 KiB
Rust

//! Serialización de filas a CSV (RFC 4180) para exportar listas.
/// Arma un documento CSV: una línea de headers + una por fila. Cada
/// celda se escapa si contiene coma, comilla o salto de línea.
pub fn to_csv(headers: &[String], rows: &[Vec<String>]) -> String {
let mut out = String::new();
push_csv_line(&mut out, headers);
for row in rows {
push_csv_line(&mut out, row);
}
out
}
/// Agrega una línea CSV (celdas separadas por coma + `\n` final).
fn push_csv_line(out: &mut String, cells: &[String]) {
for (i, cell) in cells.iter().enumerate() {
if i > 0 {
out.push(',');
}
out.push_str(&csv_escape(cell));
}
out.push('\n');
}
/// Escapa una celda: la envuelve en comillas y duplica las comillas
/// internas si contiene coma, comilla, CR o LF. Si no, va tal cual.
fn csv_escape(s: &str) -> String {
if s.contains([',', '"', '\n', '\r']) {
format!("\"{}\"", s.replace('"', "\"\""))
} else {
s.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plain_cells_unquoted() {
let csv = to_csv(
&["Nombre".into(), "Edad".into()],
&[vec!["Ana".into(), "30".into()]],
);
assert_eq!(csv, "Nombre,Edad\nAna,30\n");
}
#[test]
fn cells_with_comma_or_quote_are_escaped() {
let csv = to_csv(
&["a".into(), "b".into()],
&[vec!["x,y".into(), "dijo \"hola\"".into()]],
);
assert_eq!(csv, "a,b\n\"x,y\",\"dijo \"\"hola\"\"\"\n");
}
#[test]
fn newline_in_cell_is_quoted() {
let csv = to_csv(&["n".into()], &[vec!["línea1\nlínea2".into()]]);
assert_eq!(csv, "n\n\"línea1\nlínea2\"\n");
}
#[test]
fn empty_rows_yields_just_header() {
assert_eq!(to_csv(&["x".into()], &[]), "x\n");
}
}