use core::fmt; use core::str; use memchr::memchr; use crate::{QuoteStyle, Terminator}; /// A builder for configuring a CSV writer. /// /// This builder permits specifying the CSV delimiter, terminator, quoting /// style and more. #[derive(Debug)] pub struct WriterBuilder { wtr: Writer, } impl WriterBuilder { /// Create a new builder for configuring a CSV writer. pub fn new() -> WriterBuilder { let wtr = Writer { state: WriterState::default(), requires_quotes: [false; 256], delimiter: b',', term: Terminator::Any(b'\n'), style: QuoteStyle::default(), quote: b'"', escape: b'\\', double_quote: true, comment: None, }; WriterBuilder { wtr: wtr } } /// Builder a CSV writer from this configuration. pub fn build(&self) -> Writer { use crate::Terminator::*; let mut wtr = self.wtr.clone(); wtr.requires_quotes[self.wtr.delimiter as usize] = true; wtr.requires_quotes[self.wtr.quote as usize] = true; if !self.wtr.double_quote { // We only need to quote the escape character if the escape // character is used for escaping quotes. wtr.requires_quotes[self.wtr.escape as usize] = true; } match self.wtr.term { CRLF | Any(b'\n') | Any(b'\r') => { // This is a bit hokey. By default, the record terminator // is '\n', but we still need to quote '\r' (even if our // terminator is only `\n`) because the reader interprets '\r' // as a record terminator by default. wtr.requires_quotes[b'\r' as usize] = true; wtr.requires_quotes[b'\n' as usize] = true; } Any(b) => { wtr.requires_quotes[b as usize] = true; } _ => unreachable!(), } // If the first field of a row starts with a comment character, // it needs to be quoted, or the row will not be readable later. // As requires_quotes is calculated in advance, we force quotes // when a comment character is encountered anywhere in the field. if let Some(comment) = self.wtr.comment { wtr.requires_quotes[comment as usize] = true; } wtr } /// The field delimiter to use when writing CSV. /// /// The default is `b','`. pub fn delimiter(&mut self, delimiter: u8) -> &mut WriterBuilder { self.wtr.delimiter = delimiter; self } /// The record terminator to use when writing CSV. /// /// A record terminator can be any single byte. The default is `\n`. /// /// Note that RFC 4180 specifies that record terminators should be `\r\n`. /// To use `\r\n`, use the special `Terminator::CRLF` value. pub fn terminator(&mut self, term: Terminator) -> &mut WriterBuilder { self.wtr.term = term; self } /// The quoting style to use when writing CSV. /// /// By default, this is set to `QuoteStyle::Necessary`, which will only /// use quotes when they are necessary to preserve the integrity of data. /// /// Note that unless the quote style is set to `Never`, an empty field is /// quoted if it is the only field in a record. pub fn quote_style(&mut self, style: QuoteStyle) -> &mut WriterBuilder { self.wtr.style = style; self } /// The quote character to use when writing CSV. /// /// The default value is `b'"'`. pub fn quote(&mut self, quote: u8) -> &mut WriterBuilder { self.wtr.quote = quote; self } /// The escape character to use when writing CSV. /// /// This is only used when `double_quote` is set to `false`. /// /// The default value is `b'\\'`. pub fn escape(&mut self, escape: u8) -> &mut WriterBuilder { self.wtr.escape = escape; self } /// The quoting escape mechanism to use when writing CSV. /// /// When enabled (which is the default), quotes are escaped by doubling /// them. e.g., `"` escapes to `""`. /// /// When disabled, quotes are escaped with the escape character (which /// is `\\` by default). pub fn double_quote(&mut self, yes: bool) -> &mut WriterBuilder { self.wtr.double_quote = yes; self } /// The comment character that will be used when later reading the file. /// /// If `quote_style` is set to `QuoteStyle::Necessary`, a field will /// be quoted if the comment character is detected anywhere in the field. /// /// The default value is None. pub fn comment(&mut self, comment: Option) -> &mut WriterBuilder { self.wtr.comment = comment; self } } impl Default for WriterBuilder { fn default() -> WriterBuilder { WriterBuilder::new() } } /// The result of writing CSV data. /// /// A value of this type is returned from every interaction with `Writer`. It /// informs the caller how to proceed, namely, by indicating whether more /// input should be given (`InputEmpty`) or if a bigger output buffer is needed /// (`OutputFull`). #[derive(Clone, Debug, Eq, PartialEq)] pub enum WriteResult { /// This result occurs when all of the bytes from the given input have /// been processed. InputEmpty, /// This result occurs when the output buffer was too small to process /// all of the input bytes. Generally, this means the caller must call /// the corresponding method again with the rest of the input and more /// room in the output buffer. OutputFull, } /// A writer for CSV data. /// /// # RFC 4180 /// /// This writer conforms to RFC 4180 with one exception: it doesn't guarantee /// that all records written are of the same length. Instead, the onus is on /// the caller to ensure that all records written are of the same length. /// /// Note that the default configuration of a `Writer` uses `\n` for record /// terminators instead of `\r\n` as specified by RFC 4180. Use the /// `terminator` method on `WriterBuilder` to set the terminator to `\r\n` if /// it's desired. pub struct Writer { state: WriterState, requires_quotes: [bool; 256], delimiter: u8, term: Terminator, style: QuoteStyle, quote: u8, escape: u8, double_quote: bool, comment: Option, } impl Clone for Writer { fn clone(&self) -> Writer { let mut requires_quotes = [false; 256]; for i in 0..256 { requires_quotes[i] = self.requires_quotes[i]; } Writer { state: self.state.clone(), requires_quotes: requires_quotes, delimiter: self.delimiter, term: self.term, style: self.style, quote: self.quote, escape: self.escape, double_quote: self.double_quote, comment: self.comment, } } } impl fmt::Debug for Writer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Writer") .field("state", &self.state) .field("delimiter", &self.delimiter) .field("term", &self.term) .field("style", &self.style) .field("quote", &self.quote) .field("escape", &self.escape) .field("double_quote", &self.double_quote) .finish() } } #[derive(Clone, Debug)] struct WriterState { /// This is set whenever we've begun writing the contents of a field, even /// if the contents are empty. We use it to avoid re-computing whether /// quotes are necessary. in_field: bool, /// This is set whenever we've started writing a field that is enclosed in /// quotes. When the writer is finished, or if a delimiter or terminator /// are written, then a closing quote is inserted when this is true. quoting: bool, /// The number of total bytes written for the current record. /// /// If the writer is finished or a terminator is written when this is `0`, /// then an empty field is added as a pair of adjacent quotes. record_bytes: u64, } impl Writer { /// Creates a new CSV writer with the default configuration. pub fn new() -> Writer { Writer::default() } /// Finish writing CSV data to `output`. /// /// This must be called when one is done writing CSV data to `output`. /// In particular, it will write closing quotes if necessary. pub fn finish(&mut self, mut output: &mut [u8]) -> (WriteResult, usize) { let mut nout = 0; if self.state.record_bytes == 0 && self.state.in_field { assert!(!self.state.quoting); let (res, o) = self.write(&[self.quote, self.quote], output); if o == 0 { return (res, 0); } output = &mut moving(output)[o..]; nout += o; self.state.record_bytes += o as u64; } if !self.state.quoting { return (WriteResult::InputEmpty, nout); } let (res, o) = self.write(&[self.quote], output); if o == 0 { return (res, nout); } nout += o; self.state.record_bytes = 0; self.state.in_field = false; self.state.quoting = false; (res, nout) } /// Write a single CSV field from `input` to `output` while employing this /// writer's quoting style. /// /// This returns the result of writing field data, in addition to the /// number of bytes consumed from `input` and the number of bytes /// written to `output`. /// /// The result of writing field data is either `WriteResult::InputEmpty` /// or `WriteResult::OutputFull`. The former occurs when all bytes in /// `input` were copied to `output`, while the latter occurs when `output` /// is too small to fit everything from `input`. The maximum number of /// bytes that can be written to `output` is `2 + (2 * input.len())` /// because of quoting. (The worst case is a field consisting entirely /// of quotes.) /// /// Multiple successive calls to `field` will write more data to the same /// field. Subsequent fields can be written by calling either `delimiter` /// or `terminator` first. /// /// If this writer's quoting style is `QuoteStyle::Necessary`, then `input` /// should contain the *entire* field. Otherwise, whether the field needs /// to be quoted or not cannot be determined. pub fn field( &mut self, input: &[u8], mut output: &mut [u8], ) -> (WriteResult, usize, usize) { let (mut nin, mut nout) = (0, 0); if !self.state.in_field { self.state.quoting = self.should_quote(input); if self.state.quoting { let (res, o) = self.write(&[self.quote], output); if o == 0 { return (res, 0, 0); } output = &mut moving(output)[o..]; nout += o; self.state.record_bytes += o as u64; } self.state.in_field = true; } let (res, i, o) = if self.state.quoting { quote(input, output, self.quote, self.escape, self.double_quote) } else { write_optimistic(input, output) }; nin += i; nout += o; self.state.record_bytes += o as u64; (res, nin, nout) } /// Write the configured field delimiter to `output`. /// /// If the output buffer does not have enough room to fit /// a field delimiter, then nothing is written to `output` /// and `WriteResult::OutputFull` is returned. Otherwise, /// `WriteResult::InputEmpty` is returned along with the number of bytes /// written to `output` (which is `1` in case of an unquoted /// field, or `2` in case of an end quote and a field separator). pub fn delimiter( &mut self, mut output: &mut [u8], ) -> (WriteResult, usize) { let mut nout = 0; if self.state.quoting { let (res, o) = self.write(&[self.quote], output); if o == 0 { return (res, o); } output = &mut moving(output)[o..]; nout += o; self.state.record_bytes += o as u64; self.state.quoting = false; } let (res, o) = self.write(&[self.delimiter], output); if o == 0 { return (res, nout); } nout += o; self.state.record_bytes += o as u64; self.state.in_field = false; (res, nout) } /// Write the configured record terminator to `output`. /// /// If the output buffer does not have enough room to fit a record /// terminator, then no part of the terminator is written and /// `WriteResult::OutputFull` is returned. Otherwise, /// `WriteResult::InputEmpty` is returned along with the number of bytes /// written to `output` (which is always `1` or `2`). pub fn terminator( &mut self, mut output: &mut [u8], ) -> (WriteResult, usize) { let mut nout = 0; if self.state.record_bytes == 0 { assert!(!self.state.quoting); let (res, o) = self.write(&[self.quote, self.quote], output); if o == 0 { return (res, 0); } output = &mut moving(output)[o..]; nout += o; self.state.record_bytes += o as u64; } if self.state.quoting { let (res, o) = self.write(&[self.quote], output); if o == 0 { return (res, o); } output = &mut moving(output)[o..]; nout += o; self.state.record_bytes += o as u64; self.state.quoting = false; } let (res, o) = match self.term { Terminator::CRLF => write_pessimistic(&[b'\r', b'\n'], output), Terminator::Any(b) => write_pessimistic(&[b], output), _ => unreachable!(), }; if o == 0 { return (res, nout); } nout += o; self.state.record_bytes = 0; self.state.in_field = false; (res, nout) } /// Returns true if and only if the given input field *requires* quotes to /// preserve the integrity of `input` while taking into account the current /// configuration of this writer (except for the configured quoting style). #[inline] fn needs_quotes(&self, mut input: &[u8]) -> bool { let mut needs = false; while !needs && input.len() >= 8 { needs = self.requires_quotes[input[0] as usize] || self.requires_quotes[input[1] as usize] || self.requires_quotes[input[2] as usize] || self.requires_quotes[input[3] as usize] || self.requires_quotes[input[4] as usize] || self.requires_quotes[input[5] as usize] || self.requires_quotes[input[6] as usize] || self.requires_quotes[input[7] as usize]; input = &input[8..]; } needs || input.iter().any(|&b| self.is_special_byte(b)) } /// Returns true if and only if the given byte corresponds to a special /// byte in this CSV writer's configuration. /// /// Note that this does **not** take into account this writer's quoting /// style. #[inline] pub fn is_special_byte(&self, b: u8) -> bool { self.requires_quotes[b as usize] } /// Returns true if and only if we should put the given field data /// in quotes. This takes the quoting style into account. #[inline] pub fn should_quote(&self, input: &[u8]) -> bool { match self.style { QuoteStyle::Always => true, QuoteStyle::Never => false, QuoteStyle::NonNumeric => is_non_numeric(input), QuoteStyle::Necessary => self.needs_quotes(input), _ => unreachable!(), } } /// Return the delimiter used for this writer. #[inline] pub fn get_delimiter(&self) -> u8 { self.delimiter } /// Return the terminator used for this writer. #[inline] pub fn get_terminator(&self) -> Terminator { self.term } /// Return the quoting style used for this writer. #[inline] pub fn get_quote_style(&self) -> QuoteStyle { self.style } /// Return the quote character used for this writer. #[inline] pub fn get_quote(&self) -> u8 { self.quote } /// Return the escape character used for this writer. #[inline] pub fn get_escape(&self) -> u8 { self.escape } /// Return whether this writer doubles quotes or not. When the writer /// does not double quotes, it will escape them using the escape character. #[inline] pub fn get_double_quote(&self) -> bool { self.double_quote } fn write(&self, data: &[u8], output: &mut [u8]) -> (WriteResult, usize) { if data.len() > output.len() { (WriteResult::OutputFull, 0) } else { output[..data.len()].copy_from_slice(data); (WriteResult::InputEmpty, data.len()) } } } impl Default for Writer { fn default() -> Writer { WriterBuilder::new().build() } } impl Default for WriterState { fn default() -> WriterState { WriterState { in_field: false, quoting: false, record_bytes: 0 } } } /// Returns true if and only if the given input is non-numeric. pub fn is_non_numeric(input: &[u8]) -> bool { let s = match str::from_utf8(input) { Err(_) => return true, Ok(s) => s, }; // I suppose this could be faster if we wrote validators of numbers instead // of using the actual parser, but that's probably a lot of work for a bit // of a niche feature. !s.parse::().is_ok() && !s.parse::().is_ok() } /// Escape quotes `input` and writes the result to `output`. /// /// If `input` does not have a `quote`, then the contents of `input` are /// copied verbatim to `output`. /// /// If `output` is not big enough to store the fully quoted contents of /// `input`, then `WriteResult::OutputFull` is returned. The `output` buffer /// will require a maximum of storage of `2 * input.len()` in the worst case /// (where every byte is a quote). /// /// In streaming contexts, `quote` should be called in a loop until /// `WriteResult::InputEmpty` is returned. It is possible to write an infinite /// loop if your output buffer is less than 2 bytes in length (the minimum /// storage space required to store an escaped quote). /// /// In addition to the `WriteResult`, the number of consumed bytes from `input` /// and the number of bytes written to `output` are also returned. /// /// `quote` is the quote byte and `escape` is the escape byte. If /// `double_quote` is true, then quotes are escaped by doubling them, /// otherwise, quotes are escaped with the `escape` byte. /// /// N.B. This function is provided for low level usage. It is called /// automatically if you're using a `Writer`. pub fn quote( mut input: &[u8], mut output: &mut [u8], quote: u8, escape: u8, double_quote: bool, ) -> (WriteResult, usize, usize) { let (mut nin, mut nout) = (0, 0); loop { match memchr(quote, input) { None => { let (res, i, o) = write_optimistic(input, output); nin += i; nout += o; return (res, nin, nout); } Some(next_quote) => { let (res, i, o) = write_optimistic(&input[..next_quote], output); input = &input[i..]; output = &mut moving(output)[o..]; nin += i; nout += o; if let WriteResult::OutputFull = res { return (res, nin, nout); } if double_quote { let (res, o) = write_pessimistic(&[quote, quote], output); if let WriteResult::OutputFull = res { return (res, nin, nout); } nout += o; output = &mut moving(output)[o..]; } else { let (res, o) = write_pessimistic(&[escape, quote], output); if let WriteResult::OutputFull = res { return (res, nin, nout); } nout += o; output = &mut moving(output)[o..]; } nin += 1; input = &input[1..]; } } } } /// Copy the bytes from `input` to `output`. If `output` is too small to fit /// everything from `input`, then copy `output.len()` bytes from `input`. /// Otherwise, copy everything from `input` into `output`. /// /// In the first case (`output` is too small), `WriteResult::OutputFull` is /// returned, in addition to the number of bytes consumed from `input` and /// the number of bytes written to `output`. /// /// In the second case (`input` is no bigger than `output`), /// `WriteResult::InputEmpty` is returned, in addition to the number of bytes /// consumed from `input` and the number of bytes written to `output`. fn write_optimistic( input: &[u8], output: &mut [u8], ) -> (WriteResult, usize, usize) { if input.len() > output.len() { let input = &input[..output.len()]; output.copy_from_slice(input); (WriteResult::OutputFull, output.len(), output.len()) } else { output[..input.len()].copy_from_slice(input); (WriteResult::InputEmpty, input.len(), input.len()) } } /// Copy the bytes from `input` to `output` only if `input` is no bigger than /// `output`. If `input` is bigger than `output`, then return /// `WriteResult::OutputFull` and copy nothing into `output`. Otherwise, /// return `WriteResult::InputEmpty` and the number of bytes copied into /// `output`. fn write_pessimistic(input: &[u8], output: &mut [u8]) -> (WriteResult, usize) { if input.len() > output.len() { (WriteResult::OutputFull, 0) } else { output[..input.len()].copy_from_slice(input); (WriteResult::InputEmpty, input.len()) } } /// This avoids reborrowing. /// See: https://bluss.github.io/rust/fun/2015/10/11/stuff-the-identity-function-does/ fn moving(x: T) -> T { x } #[cfg(test)] mod tests { use crate::writer::WriteResult::*; use crate::writer::{quote, QuoteStyle, Writer, WriterBuilder}; // OMG I HATE BYTE STRING LITERALS SO MUCH. fn b(s: &str) -> &[u8] { s.as_bytes() } fn s(b: &[u8]) -> &str { ::core::str::from_utf8(b).unwrap() } macro_rules! assert_field { ( $wtr:expr, $inp:expr, $out:expr, $expect_in:expr, $expect_out:expr, $expect_res:expr, $expect_data:expr ) => {{ let (res, i, o) = $wtr.field($inp, $out); assert_eq!($expect_res, res, "result"); assert_eq!($expect_in, i, "input"); assert_eq!($expect_out, o, "output"); assert_eq!($expect_data, s(&$out[..o]), "data"); }}; } macro_rules! assert_write { ( $wtr:expr, $which:ident, $out:expr, $expect_out:expr, $expect_res:expr, $expect_data:expr ) => {{ let (res, o) = $wtr.$which($out); assert_eq!($expect_res, res, "result"); assert_eq!($expect_out, o, "output"); assert_eq!($expect_data, s(&$out[..o]), "data"); }}; } #[test] fn writer_one_field() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!(wtr, b("abc"), &mut out[n..], 3, 3, InputEmpty, "abc"); n += 3; assert_write!(wtr, finish, &mut out[n..], 0, InputEmpty, ""); } #[test] fn writer_one_empty_field_terminator() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; assert_field!(wtr, b(""), &mut out[..], 0, 0, InputEmpty, ""); assert_write!(wtr, terminator, &mut out[..], 3, InputEmpty, "\"\"\n"); assert_write!(wtr, finish, &mut out[..], 0, InputEmpty, ""); } #[test] fn writer_one_empty_field_finish() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; assert_field!(wtr, b(""), &mut out[..], 0, 0, InputEmpty, ""); assert_write!(wtr, finish, &mut out[..], 2, InputEmpty, "\"\""); } #[test] fn writer_many_one_empty_field_finish() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; assert_field!(wtr, b(""), &mut out[..], 0, 0, InputEmpty, ""); assert_write!(wtr, terminator, &mut out[..], 3, InputEmpty, "\"\"\n"); assert_field!(wtr, b(""), &mut out[..], 0, 0, InputEmpty, ""); assert_write!(wtr, finish, &mut out[..], 2, InputEmpty, "\"\""); } #[test] fn writer_many_one_empty_field_terminator() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; assert_field!(wtr, b(""), &mut out[..], 0, 0, InputEmpty, ""); assert_write!(wtr, terminator, &mut out[..], 3, InputEmpty, "\"\"\n"); assert_field!(wtr, b(""), &mut out[..], 0, 0, InputEmpty, ""); assert_write!(wtr, terminator, &mut out[..], 3, InputEmpty, "\"\"\n"); assert_write!(wtr, finish, &mut out[..], 0, InputEmpty, ""); } #[test] fn writer_one_field_quote() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!( wtr, b("a\"bc"), &mut out[n..], 4, 6, InputEmpty, "\"a\"\"bc" ); n += 6; assert_write!(wtr, finish, &mut out[n..], 1, InputEmpty, "\""); } #[test] fn writer_one_field_stream() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!(wtr, b("abc"), &mut out[n..], 3, 3, InputEmpty, "abc"); n += 3; assert_field!(wtr, b("x"), &mut out[n..], 1, 1, InputEmpty, "x"); n += 1; assert_write!(wtr, finish, &mut out[n..], 0, InputEmpty, ""); } #[test] fn writer_one_field_stream_quote() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!( wtr, b("abc\""), &mut out[n..], 4, 6, InputEmpty, "\"abc\"\"" ); n += 6; assert_field!(wtr, b("x"), &mut out[n..], 1, 1, InputEmpty, "x"); n += 1; assert_write!(wtr, finish, &mut out[n..], 1, InputEmpty, "\""); } #[test] fn writer_one_field_stream_quote_partial() { let mut wtr = Writer::new(); let out = &mut [0; 4]; assert_field!(wtr, b("ab\"xyz"), out, 2, 3, OutputFull, "\"ab"); assert_field!(wtr, b("\"xyz"), out, 3, 4, OutputFull, "\"\"xy"); assert_field!(wtr, b("z"), out, 1, 1, InputEmpty, "z"); assert_write!(wtr, finish, out, 1, InputEmpty, "\""); } #[test] fn writer_two_fields() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!(wtr, b("abc"), &mut out[n..], 3, 3, InputEmpty, "abc"); n += 3; assert_write!(wtr, delimiter, &mut out[n..], 1, InputEmpty, ","); n += 1; assert_field!(wtr, b("yz"), &mut out[n..], 2, 2, InputEmpty, "yz"); n += 2; assert_write!(wtr, finish, &mut out[n..], 0, InputEmpty, ""); assert_eq!("abc,yz", s(&out[..n])); } #[test] fn writer_two_fields_non_numeric() { let mut wtr = WriterBuilder::new().quote_style(QuoteStyle::NonNumeric).build(); let out = &mut [0; 1024]; let mut n = 0; assert_field!(wtr, b("abc"), &mut out[n..], 3, 4, InputEmpty, "\"abc"); n += 4; assert_write!(wtr, delimiter, &mut out[n..], 2, InputEmpty, "\","); n += 2; assert_field!(wtr, b("5.2"), &mut out[n..], 3, 3, InputEmpty, "5.2"); n += 3; assert_write!(wtr, delimiter, &mut out[n..], 1, InputEmpty, ","); n += 1; assert_field!(wtr, b("98"), &mut out[n..], 2, 2, InputEmpty, "98"); n += 2; assert_write!(wtr, finish, &mut out[n..], 0, InputEmpty, ""); assert_eq!("\"abc\",5.2,98", s(&out[..n])); } #[test] fn writer_two_fields_quote() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!( wtr, b("a,bc"), &mut out[n..], 4, 5, InputEmpty, "\"a,bc" ); n += 5; assert_write!(wtr, delimiter, &mut out[n..], 2, InputEmpty, "\","); n += 2; assert_field!(wtr, b("\nz"), &mut out[n..], 2, 3, InputEmpty, "\"\nz"); n += 3; assert_write!(wtr, finish, &mut out[n..], 1, InputEmpty, "\""); n += 1; assert_eq!("\"a,bc\",\"\nz\"", s(&out[..n])); } #[test] fn writer_two_fields_two_records() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!(wtr, b("abc"), &mut out[n..], 3, 3, InputEmpty, "abc"); n += 3; assert_write!(wtr, delimiter, &mut out[n..], 1, InputEmpty, ","); n += 1; assert_field!(wtr, b("yz"), &mut out[n..], 2, 2, InputEmpty, "yz"); n += 2; assert_write!(wtr, terminator, &mut out[n..], 1, InputEmpty, "\n"); n += 1; assert_field!(wtr, b("foo"), &mut out[n..], 3, 3, InputEmpty, "foo"); n += 3; assert_write!(wtr, delimiter, &mut out[n..], 1, InputEmpty, ","); n += 1; assert_field!(wtr, b("quux"), &mut out[n..], 4, 4, InputEmpty, "quux"); n += 4; assert_write!(wtr, finish, &mut out[n..], 0, InputEmpty, ""); assert_eq!("abc,yz\nfoo,quux", s(&out[..n])); } #[test] fn writer_two_fields_two_records_quote() { let mut wtr = Writer::new(); let out = &mut [0; 1024]; let mut n = 0; assert_field!( wtr, b("a,bc"), &mut out[n..], 4, 5, InputEmpty, "\"a,bc" ); n += 5; assert_write!(wtr, delimiter, &mut out[n..], 2, InputEmpty, "\","); n += 2; assert_field!(wtr, b("\nz"), &mut out[n..], 2, 3, InputEmpty, "\"\nz"); n += 3; assert_write!(wtr, terminator, &mut out[n..], 2, InputEmpty, "\"\n"); n += 2; assert_field!( wtr, b("f\"oo"), &mut out[n..], 4, 6, InputEmpty, "\"f\"\"oo" ); n += 6; assert_write!(wtr, delimiter, &mut out[n..], 2, InputEmpty, "\","); n += 2; assert_field!( wtr, b("quux,"), &mut out[n..], 5, 6, InputEmpty, "\"quux," ); n += 6; assert_write!(wtr, finish, &mut out[n..], 1, InputEmpty, "\""); n += 1; assert_eq!("\"a,bc\",\"\nz\"\n\"f\"\"oo\",\"quux,\"", s(&out[..n])); } macro_rules! assert_quote { ( $inp:expr, $out:expr, $expect_in:expr, $expect_out:expr, $expect_res:expr, $expect_data:expr ) => { assert_quote!( $inp, $out, $expect_in, $expect_out, $expect_res, $expect_data, true ); }; ( $inp:expr, $out:expr, $expect_in:expr, $expect_out:expr, $expect_res:expr, $expect_data:expr, $double_quote:expr ) => {{ let (res, i, o) = quote($inp, $out, b'"', b'\\', $double_quote); assert_eq!($expect_res, res, "result"); assert_eq!($expect_in, i, "input"); assert_eq!($expect_out, o, "output"); assert_eq!(b($expect_data), &$out[..o], "data"); }}; } #[test] fn quote_empty() { let inp = b(""); let out = &mut [0; 1024]; assert_quote!(inp, out, 0, 0, InputEmpty, ""); } #[test] fn quote_no_quotes() { let inp = b("foobar"); let out = &mut [0; 1024]; assert_quote!(inp, out, 6, 6, InputEmpty, "foobar"); } #[test] fn quote_one_quote() { let inp = b("\""); let out = &mut [0; 1024]; assert_quote!(inp, out, 1, 2, InputEmpty, r#""""#); } #[test] fn quote_two_quotes() { let inp = b("\"\""); let out = &mut [0; 1024]; assert_quote!(inp, out, 2, 4, InputEmpty, r#""""""#); } #[test] fn quote_escaped_one() { let inp = b("\""); let out = &mut [0; 1024]; assert_quote!(inp, out, 1, 2, InputEmpty, r#"\""#, false); } #[test] fn quote_escaped_two() { let inp = b("\"\""); let out = &mut [0; 1024]; assert_quote!(inp, out, 2, 4, InputEmpty, r#"\"\""#, false); } #[test] fn quote_misc() { let inp = b(r#"foo "bar" baz "quux"?"#); let out = &mut [0; 1024]; assert_quote!( inp, out, 21, 25, InputEmpty, r#"foo ""bar"" baz ""quux""?"# ); } #[test] fn quote_stream_no_quotes() { let mut inp = b("fooba"); let out = &mut [0; 2]; assert_quote!(inp, out, 2, 2, OutputFull, "fo"); inp = &inp[2..]; assert_quote!(inp, out, 2, 2, OutputFull, "ob"); inp = &inp[2..]; assert_quote!(inp, out, 1, 1, InputEmpty, "a"); } #[test] fn quote_stream_quotes() { let mut inp = b(r#"a"bc"d""#); let out = &mut [0; 2]; assert_quote!(inp, out, 1, 1, OutputFull, "a"); inp = &inp[1..]; assert_quote!(inp, out, 1, 2, OutputFull, r#""""#); inp = &inp[1..]; assert_quote!(inp, out, 2, 2, OutputFull, "bc"); inp = &inp[2..]; assert_quote!(inp, out, 1, 2, OutputFull, r#""""#); inp = &inp[1..]; assert_quote!(inp, out, 1, 1, OutputFull, "d"); inp = &inp[1..]; assert_quote!(inp, out, 1, 2, InputEmpty, r#""""#); } #[test] fn comment_char_is_automatically_quoted() { let mut wtr = WriterBuilder::new().comment(Some(b'#')).build(); let out = &mut [0; 1024]; assert_field!( wtr, b("# abc"), &mut out[..], 5, 6, InputEmpty, "\"# abc" ); assert_write!(wtr, finish, &mut out[..], 1, InputEmpty, "\""); } }