b13486e240
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>
68 lines
1.8 KiB
Rust
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");
|
|
}
|
|
}
|