#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub(crate) struct Peek {
pub titles: Vec<String>,
pub samples: Vec<Vec<String>>,
}
pub(crate) const SAMPLE_ROWS: usize = 3;
pub(crate) const PEEK_BYTES: usize = 64 * 1024;
pub(crate) fn sniff_delimiter(bytes: &[u8]) -> u8 {
let first = bytes.split(|b| *b == b'\n').next().unwrap_or_default();
let count = |d: u8| first.iter().filter(|b| **b == d).count();
let (comma, semi, tab) = (count(b','), count(b';'), count(b'\t'));
if semi > comma && semi >= tab {
b';'
} else if tab > comma {
b'\t'
} else {
b','
}
}
pub(crate) fn peek(bytes: &[u8], delimiter: u8, titles: bool) -> Peek {
let mut reader = csv::ReaderBuilder::new()
.delimiter(delimiter)
.has_headers(titles)
.flexible(true)
.from_reader(bytes);
let mut out = Peek::default();
if titles {
if let Ok(headers) = reader.headers() {
out.titles = headers.iter().map(str::to_string).collect();
}
}
for record in reader.records().take(SAMPLE_ROWS).flatten() {
if record.iter().all(|v| v.trim().is_empty()) {
continue;
}
out.samples
.push(record.iter().map(str::to_string).collect());
}
if !titles {
let width = out.samples.iter().map(Vec::len).max().unwrap_or(0);
out.titles = (1..=width).map(|n| format!("Column {n}")).collect();
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn titles_come_from_the_first_row() {
let p = peek(
b"code,name\nlaunch,Launch\n\nrelaunch,Relaunch\n",
b',',
true,
);
assert_eq!(p.titles, ["code", "name"]);
assert_eq!(
p.samples,
[vec!["launch", "Launch"], vec!["relaunch", "Relaunch"]]
);
}
#[test]
fn a_file_without_titles_is_numbered_by_its_widest_row() {
let p = peek(b"a;b\nc;d;e\n", b';', false);
assert_eq!(p.titles, ["Column 1", "Column 2", "Column 3"]);
assert_eq!(p.samples.len(), 2);
}
#[test]
fn the_delimiter_is_read_off_the_first_line() {
assert_eq!(sniff_delimiter(b"code;name\nx;y\n"), b';');
assert_eq!(sniff_delimiter(b"code\tname\n"), b'\t');
assert_eq!(sniff_delimiter(b"code,name;x\n"), b',');
assert_eq!(sniff_delimiter(b"code\n"), b',');
}
#[test]
fn a_row_cut_off_at_the_end_still_reads() {
let p = peek(b"code,name\nlaunch,Launch\nrelaunch,Rel", b',', true);
assert_eq!(
p.samples,
[vec!["launch", "Launch"], vec!["relaunch", "Rel"]]
);
}
}