use std::fs::File;
use std::io;
use std::path::Path;
use std::result;
use csv_core::{
Writer as CoreWriter, WriterBuilder as CoreWriterBuilder,
QuoteStyle, Terminator, WriteResult,
};
use serde::Serialize;
use error::{Error, Result, IntoInnerError, new_into_inner_error};
use serializer::serialize;
#[derive(Debug)]
pub struct WriterBuilder {
builder: CoreWriterBuilder,
capacity: usize,
flexible: bool,
has_headers: bool,
}
impl Default for WriterBuilder {
fn default() -> WriterBuilder {
WriterBuilder {
builder: CoreWriterBuilder::default(),
capacity: 8 * (1<<10),
flexible: false,
has_headers: true,
}
}
}
impl WriterBuilder {
pub fn new() -> WriterBuilder {
WriterBuilder::default()
}
pub fn from_path<P: AsRef<Path>>(&self, path: P) -> Result<Writer<File>> {
Ok(Writer::new(self, File::create(path)?))
}
pub fn from_writer<W: io::Write>(&self, wtr: W) -> Writer<W> {
Writer::new(self, wtr)
}
pub fn delimiter(&mut self, delimiter: u8) -> &mut WriterBuilder {
self.builder.delimiter(delimiter);
self
}
pub fn has_headers(&mut self, yes: bool) -> &mut WriterBuilder {
self.has_headers = yes;
self
}
pub fn flexible(&mut self, yes: bool) -> &mut WriterBuilder {
self.flexible = yes;
self
}
pub fn terminator(
&mut self,
term: Terminator,
) -> &mut WriterBuilder {
self.builder.terminator(term);
self
}
pub fn quote_style(&mut self, style: QuoteStyle) -> &mut WriterBuilder {
self.builder.quote_style(style);
self
}
pub fn quote(&mut self, quote: u8) -> &mut WriterBuilder {
self.builder.quote(quote);
self
}
pub fn double_quote(&mut self, yes: bool) -> &mut WriterBuilder {
self.builder.double_quote(yes);
self
}
pub fn escape(&mut self, escape: u8) -> &mut WriterBuilder {
self.builder.escape(escape);
self
}
pub fn buffer_capacity(&mut self, capacity: usize) -> &mut WriterBuilder {
self.capacity = capacity;
self
}
}
#[derive(Debug)]
pub struct Writer<W: io::Write> {
core: CoreWriter,
wtr: Option<W>,
buf: Buffer,
state: WriterState,
}
#[derive(Debug)]
struct WriterState {
header: HeaderState,
flexible: bool,
first_field_count: Option<u64>,
fields_written: u64,
panicked: bool,
}
#[derive(Debug)]
enum HeaderState {
Write,
DidWrite,
DidNotWrite,
None,
}
#[derive(Debug)]
struct Buffer {
buf: Vec<u8>,
len: usize,
}
impl<W: io::Write> Drop for Writer<W> {
fn drop(&mut self) {
if self.wtr.is_some() && !self.state.panicked {
let _ = self.flush();
}
}
}
impl Writer<File> {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Writer<File>> {
WriterBuilder::new().from_path(path)
}
}
impl<W: io::Write> Writer<W> {
fn new(builder: &WriterBuilder, wtr: W) -> Writer<W> {
let header_state =
if builder.has_headers {
HeaderState::Write
} else {
HeaderState::None
};
Writer {
core: builder.builder.build(),
wtr: Some(wtr),
buf: Buffer {
buf: vec![0; builder.capacity],
len: 0,
},
state: WriterState {
header: header_state,
flexible: builder.flexible,
first_field_count: None,
fields_written: 0,
panicked: false,
},
}
}
pub fn from_writer(wtr: W) -> Writer<W> {
WriterBuilder::new().from_writer(wtr)
}
pub fn serialize<S: Serialize>(&mut self, mut record: S) -> Result<()> {
match self.state.header {
HeaderState::None | HeaderState::DidNotWrite => {
serialize(self, record, false, false)?;
self.write_terminator()?;
}
HeaderState::DidWrite => {
serialize(self, record, false, true)?;
self.write_terminator()?;
}
HeaderState::Write => {
let did = serialize(self, &mut record, true, false)?;
self.state.header =
if did {
HeaderState::DidWrite
} else {
HeaderState::DidNotWrite
};
self.write_terminator()?;
if did {
serialize(self, record, false, true)?;
self.write_terminator()?;
}
}
}
Ok(())
}
pub fn write_record<I, T>(&mut self, record: I) -> Result<()>
where I: IntoIterator<Item=T>, T: AsRef<[u8]>
{
for field in record.into_iter() {
self.write_field(field)?;
}
self.write_terminator()
}
pub fn write_field<T: AsRef<[u8]>>(&mut self, field: T) -> Result<()> {
if self.state.fields_written > 0 {
self.write_delimiter()?;
}
let mut field = field.as_ref();
loop {
let (res, nin, nout) = self.core.field(field, self.buf.writable());
field = &field[nin..];
self.buf.written(nout);
match res {
WriteResult::InputEmpty => {
self.state.fields_written += 1;
return Ok(());
}
WriteResult::OutputFull => self.flush()?,
}
}
}
pub fn flush(&mut self) -> io::Result<()> {
self.state.panicked = true;
let result = self.wtr.as_mut().unwrap().write_all(self.buf.readable());
self.state.panicked = false;
result?;
self.buf.clear();
self.wtr.as_mut().unwrap().flush()?;
Ok(())
}
pub fn into_inner(
mut self,
) -> result::Result<W, IntoInnerError<Writer<W>>> {
match self.flush() {
Ok(()) => Ok(self.wtr.take().unwrap()),
Err(err) => Err(new_into_inner_error(self, err)),
}
}
fn write_delimiter(&mut self) -> Result<()> {
loop {
let (res, nout) = self.core.delimiter(self.buf.writable());
self.buf.written(nout);
match res {
WriteResult::InputEmpty => return Ok(()),
WriteResult::OutputFull => self.flush()?,
}
}
}
fn write_terminator(&mut self) -> Result<()> {
if !self.state.flexible {
match self.state.first_field_count {
None => {
self.state.first_field_count =
Some(self.state.fields_written);
}
Some(expected) if expected != self.state.fields_written => {
return Err(Error::UnequalLengths {
pos: None,
expected_len: expected,
len: self.state.fields_written,
})
}
Some(_) => {}
}
}
loop {
let (res, nout) = self.core.terminator(self.buf.writable());
self.buf.written(nout);
match res {
WriteResult::InputEmpty => {
self.state.fields_written = 0;
return Ok(());
}
WriteResult::OutputFull => self.flush()?,
}
}
}
}
impl Buffer {
fn readable(&self) -> &[u8] {
&self.buf[..self.len]
}
fn writable(&mut self) -> &mut [u8] {
&mut self.buf[self.len..]
}
fn written(&mut self, n: usize) {
self.len += n;
}
fn clear(&mut self) {
self.len = 0;
}
}
#[cfg(test)]
mod tests {
use byte_record::ByteRecord;
use error::Error;
use string_record::StringRecord;
use super::{Writer, WriterBuilder};
fn wtr_as_string(wtr: Writer<Vec<u8>>) -> String {
String::from_utf8(wtr.into_inner().unwrap()).unwrap()
}
#[test]
fn one_record() {
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.write_record(&["a", "b", "c"]).unwrap();
assert_eq!(wtr_as_string(wtr), "a,b,c\n");
}
#[test]
fn one_string_record() {
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.write_record(&StringRecord::from(vec!["a", "b", "c"])).unwrap();
assert_eq!(wtr_as_string(wtr), "a,b,c\n");
}
#[test]
fn one_byte_record() {
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.write_record(&ByteRecord::from(vec!["a", "b", "c"])).unwrap();
assert_eq!(wtr_as_string(wtr), "a,b,c\n");
}
#[test]
fn one_empty_record() {
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.write_record(&[""]).unwrap();
assert_eq!(wtr_as_string(wtr), "\"\"\n");
}
#[test]
fn two_empty_records() {
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.write_record(&[""]).unwrap();
wtr.write_record(&[""]).unwrap();
assert_eq!(wtr_as_string(wtr), "\"\"\n\"\"\n");
}
#[test]
fn unequal_records_bad() {
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.write_record(&ByteRecord::from(vec!["a", "b", "c"])).unwrap();
let err = wtr.write_record(&ByteRecord::from(vec!["a"])).unwrap_err();
match err {
Error::UnequalLengths { pos, expected_len, len } => {
assert!(pos.is_none());
assert_eq!(expected_len, 3);
assert_eq!(len, 1);
}
x => panic!("expected UnequalLengths error, but got '{:?}'", x),
}
}
#[test]
fn unequal_records_ok() {
let mut wtr = WriterBuilder::new().flexible(true).from_writer(vec![]);
wtr.write_record(&ByteRecord::from(vec!["a", "b", "c"])).unwrap();
wtr.write_record(&ByteRecord::from(vec!["a"])).unwrap();
assert_eq!(wtr_as_string(wtr), "a,b,c\na\n");
}
#[test]
fn serialize_with_headers() {
#[derive(Serialize)]
struct Row {
foo: i32,
bar: f64,
baz: bool,
}
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.serialize(Row { foo: 42, bar: 42.5, baz: true }).unwrap();
assert_eq!(wtr_as_string(wtr), "foo,bar,baz\n42,42.5,true\n");
}
#[test]
fn serialize_no_headers() {
#[derive(Serialize)]
struct Row {
foo: i32,
bar: f64,
baz: bool,
}
let mut wtr = WriterBuilder::new()
.has_headers(false)
.from_writer(vec![]);
wtr.serialize(Row { foo: 42, bar: 42.5, baz: true }).unwrap();
assert_eq!(wtr_as_string(wtr), "42,42.5,true\n");
}
#[test]
fn serialize_tuple() {
let mut wtr = WriterBuilder::new().from_writer(vec![]);
wtr.serialize((true, 1.3, "hi")).unwrap();
assert_eq!(wtr_as_string(wtr), "true,1.3,hi\n");
}
}