use tracing::debug;
fn qf(s: &str) -> String {
if s.is_empty() {
return "".to_string();
}
format!("\"{}\"", s.replace('"', "\"\""))
}
pub struct CsvWriter {
output_str: String,
}
impl CsvWriter {
pub fn new(include_bom: bool) -> Self {
let mut output_str = String::new();
if include_bom {
debug!("Including UTF-8 BOM in output");
output_str.push('\u{FEFF}'); }
Self { output_str }
}
pub fn row(&mut self, fields: impl IntoIterator<Item = impl AsRef<str>>) {
let fields_data = fields
.into_iter()
.map(|f| qf(f.as_ref()))
.collect::<Vec<_>>();
debug!("Emitting row with {} fields", fields_data.len());
let row = fields_data.join(";");
self.output_str.push_str(&row);
self.output_str.push('\n');
}
pub fn empty_row(&mut self) {
debug!("Adding empty row");
self.output_str.push('\n');
}
pub fn into_string(mut self, trailing_newline: bool) -> String {
if !trailing_newline && self.output_str.ends_with('\n') {
debug!("Removing trailing newline");
self.output_str.pop();
} else if trailing_newline && !self.output_str.ends_with('\n') {
debug!("Adding trailing newline");
self.output_str.push('\n');
}
self.output_str
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_csv_writer() {
let mut writer = super::CsvWriter::new(true);
writer.row(["field1", "field;2", "field\"3"]);
writer.empty_row();
writer.row(["field4", "", "field5"]);
let output = writer.into_string(false);
assert_eq!(
output,
concat!(
"\u{feff}",
r#""field1";"field;2";"field""3""#,
"\n\n",
r#""field4";;"field5""#
)
);
}
#[test]
fn test_csv_writer_no_bom() {
let mut writer = super::CsvWriter::new(false);
writer.row(["field1", "field;2", "field\"3"]);
let output = writer.into_string(false);
assert_eq!(output, r#""field1";"field;2";"field""3""#);
}
#[test]
fn test_csv_writer_trailing_newline() {
let mut writer = super::CsvWriter::new(false);
writer.row(["field1", "field;2", "field\"3"]);
let output = writer.into_string(true);
assert_eq!(output, concat!(r#""field1";"field;2";"field""3""#, "\n"));
}
}