use std::fs;
use std::io::Write;
use std::path::Path;
use crate::{Error, Job, Result};
pub fn csv_to_json(job: &Job) -> Result<()> {
if job.options.split {
return split_rows(job, b',');
}
sv_to_json(job, b',')
}
pub fn tsv_to_json(job: &Job) -> Result<()> {
if job.options.split {
return split_rows(job, b'\t');
}
sv_to_json(job, b'\t')
}
pub fn csv_to_tsv(job: &Job) -> Result<()> {
sv_to_sv(job.input, job.output, b',', b'\t')
}
pub fn tsv_to_csv(job: &Job) -> Result<()> {
sv_to_sv(job.input, job.output, b'\t', b',')
}
pub fn csv_to_html(job: &Job) -> Result<()> {
sv_to_html(job, b',')
}
pub fn tsv_to_html(job: &Job) -> Result<()> {
sv_to_html(job, b'\t')
}
fn sv_to_json(job: &Job, delimiter: u8) -> Result<()> {
let mut reader = csv::ReaderBuilder::new()
.delimiter(delimiter)
.has_headers(true)
.from_path(job.input)?;
let headers = reader.headers()?.clone();
let mut file = fs::File::create(job.output)?;
writeln!(file, "[")?;
let mut first = true;
for record in reader.records() {
let record = record?;
let mut obj = serde_json::Map::with_capacity(headers.len());
for (header, value) in headers.iter().zip(record.iter()) {
obj.insert(
header.to_string(),
serde_json::Value::String(value.to_string()),
);
}
if !first {
writeln!(file, ",")?;
}
first = false;
let line = serde_json::to_string(&serde_json::Value::Object(obj))
.map_err(|e| Error::other(format!("json serialize: {e}")))?;
write!(file, "{line}")?;
}
writeln!(file)?;
writeln!(file, "]")?;
Ok(())
}
fn sv_to_sv(input: &Path, output: &Path, from: u8, to: u8) -> Result<()> {
let mut reader = csv::ReaderBuilder::new()
.delimiter(from)
.has_headers(false)
.from_path(input)?;
let mut writer = csv::WriterBuilder::new().delimiter(to).from_path(output)?;
for record in reader.records() {
let record = record?;
writer.write_record(&record)?;
}
writer.flush()?;
Ok(())
}
fn sv_to_html(job: &Job, delimiter: u8) -> Result<()> {
let mut reader = csv::ReaderBuilder::new()
.delimiter(delimiter)
.has_headers(false)
.from_path(job.input)?;
let mut out = String::from(
"<!doctype html><html><head><title>csv2</title></head><body><table>",
);
for record in reader.records() {
let record = record?;
out.push_str("<tr>");
for cell in record.iter() {
out.push_str("<td>");
push_html_escaped(&mut out, cell);
out.push_str("</td>");
}
out.push_str("</tr>");
}
out.push_str("</table></body></html>");
fs::write(job.output, out)?;
Ok(())
}
fn push_html_escaped(out: &mut String, s: &str) {
for c in s.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
c => out.push(c),
}
}
}
fn split_rows(job: &Job, delimiter: u8) -> Result<()> {
let dir = job
.output
.file_stem()
.map(|s| job.output.with_file_name(s))
.ok_or_else(|| Error::other("output has no file stem"))?;
fs::create_dir_all(&dir)?;
let mut reader = csv::ReaderBuilder::new()
.delimiter(delimiter)
.has_headers(true)
.from_path(job.input)?;
let headers = reader.headers()?.clone();
for (i, record) in reader.records().enumerate() {
let record = record?;
let mut obj = serde_json::Map::with_capacity(headers.len());
for (header, value) in headers.iter().zip(record.iter()) {
obj.insert(
header.to_string(),
serde_json::Value::String(value.to_string()),
);
}
let path = dir.join(format!("{i}.json"));
let json = serde_json::to_string_pretty(&serde_json::Value::Object(obj))
.map_err(|e| Error::other(format!("json serialize: {e}")))?;
fs::write(path, json)?;
}
Ok(())
}