use std::{
fmt::Display,
fs::File,
io::{self, Seek, SeekFrom},
marker::PhantomData,
path::Path,
};
use csv::Writer;
use crate::file::{FileWriter, csv::RowData};
pub struct CsvWriter<T>
where
T: WriteRow,
{
file: Writer<File>,
buf: RowData,
count: u64,
_marker: PhantomData<T>,
}
pub trait WriteRow {
fn as_row(&self, row: &mut RowData);
}
impl<T> FileWriter for CsvWriter<T>
where
T: WriteRow,
{
fn from_path<P>(path: P) -> io::Result<Self>
where
Self: Sized,
P: AsRef<Path>,
{
CsvWriter::from_file(File::create(path)?)
}
fn from_file(file: File) -> io::Result<Self>
where
Self: Sized,
{
let file = Writer::from_writer(file);
let writer = CsvWriter {
file,
buf: RowData::default(),
count: 0,
_marker: PhantomData,
};
Ok(writer)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl<T> CsvWriter<T>
where
T: WriteRow,
{
pub fn set_headers<H>(&mut self, headers: &[H]) -> io::Result<()>
where
H: Display,
{
if self.count > 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"CSV header can only be set on the first row",
));
}
self.buf.data.clear();
self.count += 1;
for header in headers {
self.buf.data.push_field(&header.to_string());
}
self.file
.write_record(&self.buf.data)
.map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"An error occurred when writing CSV file header",
)
})
}
pub fn with_headers<H>(mut self, headers: &[H]) -> io::Result<Self>
where
H: Display,
{
self.set_headers(headers)?;
Ok(self)
}
#[inline]
pub fn write_row(&mut self, object: &T) -> io::Result<()> {
self.buf.data.clear();
self.count += 1;
object.as_row(&mut self.buf);
self.file
.write_record(&self.buf.data)
.map_err(|_| {
let message = format!(
"An error occurred on row {} when writing CSV file",
self.count,
);
io::Error::new(io::ErrorKind::InvalidData, message)
})
}
}
impl<T> Seek for CsvWriter<T>
where
T: WriteRow,
{
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.file.get_ref().seek(pos)
}
}