Решение на CSV Filter от Теодор Тошков

Обратно към всички решения

Към профила на Теодор Тошков

Резултати

  • 15 точки от тестове
  • 0 бонус точки
  • 15 точки общо
  • 15 успешни тест(а)
  • 0 неуспешни тест(а)

Код

pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut chars = input.chars();
match chars.next() {
Some(c) if c == target => Some(chars.as_str()),
_ => None,
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
match input.find(target) {
Some(position) => (&input[..position], &input[position..]),
None => (input, ""),
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (first, second) = take_until(input, target);
let second = skip_next(second, target)?;
Some((first, second))
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
impl From<std::io::Error> for CsvError {
fn from(error: std::io::Error) -> Self {
CsvError::IO(error)
}
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
type SelectionResult = Result<bool, CsvError>;
use std::io::BufRead;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Box<dyn Fn(&Row) -> SelectionResult>,
}
use std::io::Write;
fn has_duplicates<T: Eq + std::hash::Hash>(v: &[T]) -> bool {
use std::collections::HashSet;
let mut items = HashSet::new();
for item in v {
if items.contains(item) {
return true;
}
items.insert(item);
}
false
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header_line = String::new();
let bytes = reader.read_line(&mut header_line)?;
if bytes == 0 {
return Err(CsvError::InvalidHeader(String::from("Missing Header")));
}
let columns = header_line
.split(',')
.map(|header| String::from(header.trim()))
.collect::<Vec<_>>();
if has_duplicates(&columns) {
return Err(CsvError::InvalidHeader(String::from("Duplicate Columns")));
}
Ok(Self {
columns,
reader,
selection: Box::new(|_row| Ok(true)),
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let mut line = line;
let mut skip_comma = false;
for column in &self.columns {
line = line.trim();
if line.is_empty() {
return Err(CsvError::InvalidRow(String::from(
"Not enough values on row",
)));
}
let result = if skip_comma {
skip_next(line, ',')
} else {
skip_comma = true;
Some(line)
}
.map(|line| line.trim())
.and_then(|line| skip_next(line, '"'))
.and_then(|line| take_and_skip(line, '"'));
match result {
None => return Err(CsvError::InvalidRow(String::from("Missing quoted value"))),
Some((value, rest)) => {
row.insert(column.to_string(), value.to_string());
line = rest;
}
}
}
if line.is_empty() {
Ok(row)
} else {
Err(CsvError::InvalidRow(String::from("Too many values on row")))
}
}
pub fn apply_selection<F>(&mut self, callback: F)
where
F: Fn(&Row) -> Result<bool, CsvError> + 'static,
{
self.selection = Box::new(callback);
}
#[allow(unused_mut)]
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
let columns = self.columns.clone();
writer.write_fmt(format_args!("{}", columns.join(", ")))?;
for row_result in self {
match row_result {
Err(err) => return Err(err),
Ok(row) => write_row(&mut writer, row, &columns)?,
}
}
Ok(())
}
}
fn write_row<W: Write>(writer: &mut W, row: Row, columns: &[String]) -> Result<(), CsvError> {
writer.write_fmt(format_args!(
"\n{}",
columns
.iter()
.map(|column| format!("\"{}\"", row[column].clone()))
.collect::<Vec<_>>()
.join(", ")
))?;
Ok(())
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut line = String::new();
let bytes = self.reader.read_line(&mut line);
match bytes {
Err(err) => return Some(Err(CsvError::IO(err))),
Ok(0) => return None,
Ok(_) => (),
};
let row = self.parse_line(&line);
match row {
Err(err) => Some(Err(err)),
Ok(row) => match (self.selection)(&row) {
Err(err) => Some(Err(err)),
Ok(true) => Some(Ok(row)),
Ok(false) => self.next(),
},
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::BufReader;
macro_rules! assert_err {
($expression:expr, $error:path) => {
assert!($expression.is_err());
match $expression {
Err($error(_)) => assert!(true),
_ => assert!(false, "Mismatched Error Types"),
};
};
}
#[test]
fn test_skip_next() {
assert_eq!(skip_next("[test]", '['), Some("test]"));
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("(foo", ')'), None);
assert_eq!(skip_next("", ')'), None);
assert_eq!(skip_next("ятест]", 'я'), Some("тест]"));
assert_eq!(skip_next("яфуу", 'я'), Some("фуу"));
assert_eq!(skip_next("яфуу", 'ю'), None);
assert_eq!(skip_next("", 'ю'), None);
}
#[test]
fn test_take_until() {
assert_eq!(take_until("one/two", '/'), ("one", "/two"));
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("是的и公雞", 'и'), ("是的", "и公雞"));
assert_eq!(take_until("яяяююю", 'ю'), ("яяя", "ююю"));
assert_eq!(take_until("中文", 'o'), ("中文", ""));
}
#[test]
fn test_take_and_skip() {
assert_eq!(take_and_skip("one/two", '/'), Some(("one", "two")));
assert_eq!(take_and_skip(" foo/bar ", '/'), Some((" foo", "bar ")));
assert_eq!(take_and_skip("foobar", '/'), None);
assert_eq!(take_and_skip("是的 公雞", ' '), Some(("是的", "公雞")));
assert_eq!(take_and_skip(" едноХдве ", 'Х'), Some((" едно", "две ")));
assert_eq!(take_and_skip("тест", 'я'), None);
}
#[test]
fn test_csv_new_missing_headers() {
let data = r#""#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new_dupe_columns() {
let data = r#"
name, age, birth date, name
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new() {
let data = r#"
name , age , birth date
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_eq!(csv.is_err(), false);
assert_eq!(
csv.unwrap().columns,
vec![
"name".to_string(),
"age".to_string(),
"birth date".to_string()
]
);
}
#[test]
fn test_csv_new_empty_column_name() {
let data = r#"
name , , birth date
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_eq!(csv.is_err(), false);
assert_eq!(
csv.unwrap().columns,
vec!["name".to_string(), "".to_string(), "birth date".to_string()]
);
}
#[test]
fn test_parse_line() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_nested_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv
.parse_line(r#""Basic Name, the 3rd","13","2020-01-01""#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_trailing_whitespace() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv
.parse_line(r#" "Basic Name, the 3rd" , "13" , "2020-01-01" "#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_empty() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv.parse_line(r#""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv.parse_line(r#""13","13" "13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_quote() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv.parse_line(r#""13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
let row = csv.parse_line(r#"13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_field() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv.parse_line(r#""13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_header() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let row = csv.parse_line(r#""13","13","13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_write_to() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Gen Z. Person, the 2nd", "20", "2000-01-01"
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data)).unwrap();
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
"\"Gen Z. Person, the 2nd\", \"20\", \"2000-01-01\"",
]
);
}
#[test]
fn test_iterator() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Gen Z. Person, the 2nd", "19", "2001-01-01"
"Gen Z. Person, the 3rd", "18", "2002-01-01"
"#
.trim()
.as_bytes();
// Normal
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let names = csv
.map(|row| row.unwrap()["name"].clone())
.collect::<Vec<_>>();
assert_eq!(
names,
&[
"Gen Z. Person",
"Gen Z. Person, the 2nd",
"Gen Z. Person, the 3rd"
]
);
// Error in selection
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Err(CsvError::ParseError(String::from("kappa"))));
assert!(csv.all(|row| row.is_err()));
// Pickier selection
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|row| {
if row["age"] != "19" {
Ok(true)
} else {
Ok(false)
}
});
let names = csv
.map(|row| row.unwrap()["name"].clone())
.collect::<Vec<_>>();
assert_eq!(names, &["Gen Z. Person", "Gen Z. Person, the 3rd"]);
// No selection
let csv = Csv::new(BufReader::new(data)).unwrap();
let names = csv
.map(|row| row.unwrap()["name"].clone())
.collect::<Vec<_>>();
assert_eq!(
names,
&[
"Gen Z. Person",
"Gen Z. Person, the 2nd",
"Gen Z. Person, the 3rd"
]
);
}
// TODO: remove this and add more
#[test]
fn test_basic_csv() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
// Parse line:
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
// Iteration:
let filtered_names = csv
.map(|row| row.unwrap()["name"].clone())
.collect::<Vec<_>>();
assert_eq!(filtered_names, &["Gen Z. Person"]);
// Writing:
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
]
);
}
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20210111-1538662-idgzdt/solution)
    Finished test [unoptimized + debuginfo] target(s) in 4.36s
     Running target/debug/deps/solution_test-8916805fc40a2dab

running 15 tests
test solution_test::test_csv_basic ... ok
test solution_test::test_csv_duplicate_columns ... ok
test solution_test::test_csv_empty ... ok
test solution_test::test_csv_iterating_with_a_selection ... ok
test solution_test::test_csv_iterating_with_no_selection ... ok
test solution_test::test_csv_parse_line ... ok
test solution_test::test_csv_parse_line_with_commas ... ok
test solution_test::test_csv_selection_and_writing ... ok
test solution_test::test_csv_single_column_no_data ... ok
test solution_test::test_csv_writing_without_a_selection ... ok
test solution_test::test_csv_writing_without_any_rows ... ok
test solution_test::test_parsing_helpers_for_unicode ... ok
test solution_test::test_skip_next ... ok
test solution_test::test_take_and_skip ... ok
test solution_test::test_take_until ... ok

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

История (4 версии и 0 коментара)

Теодор качи първо решение на 21.12.2020 23:57 (преди почти 5 години)

Теодор качи решение на 22.12.2020 00:08 (преди почти 5 години)

pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut chars = input.chars();
match chars.next() {
Some(c) if c == target => Some(chars.as_str()),
_ => None,
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
match input.find(target) {
Some(position) => (&input[..position], &input[position..]),
None => (input, ""),
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (first, second) = take_until(input, target);
let second = skip_next(second, target)?;
Some((first, second))
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
impl From<std::io::Error> for CsvError {
fn from(error: std::io::Error) -> Self {
CsvError::IO(error)
}
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
type SelectionResult = Result<bool, CsvError>;
use std::io::BufRead;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> SelectionResult>>,
}
use std::io::Write;
fn has_duplicates<T: Eq + std::hash::Hash>(v: &[T]) -> bool {
use std::collections::HashSet;
let mut items = HashSet::new();
for item in v {
if items.contains(item) {
return true;
}
items.insert(item);
}
false
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header_line = String::new();
let bytes = reader.read_line(&mut header_line)?;
if bytes == 0 {
return Err(CsvError::InvalidHeader(String::from("Missing Header")));
}
let columns = header_line
.split(',')
.map(|header| String::from(header.trim()))
.collect::<Vec<_>>();
if has_duplicates(&columns) {
return Err(CsvError::InvalidHeader(String::from("Duplicate Columns")));
}
Ok(Self {
columns,
reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let mut line = line;
let mut skip_comma = false;
for column in &self.columns {
line = line.trim();
if line.is_empty() {
return Err(CsvError::InvalidRow(String::from(
"Not enough values on row",
)));
}
let result = if skip_comma {
skip_next(line, ',')
} else {
skip_comma = true;
Some(line)
}
.map(|line| line.trim())
.and_then(|line| skip_next(line, '"'))
.and_then(|line| take_and_skip(line, '"'));
match result {
None => return Err(CsvError::InvalidRow(String::from("Missing quoted value"))),
Some((value, rest)) => {
row.insert(column.to_string(), value.to_string());
line = rest;
}
}
}
if line.is_empty() {
Ok(row)
} else {
Err(CsvError::InvalidRow(String::from("Too many values on row")))
}
}
pub fn apply_selection<F>(&mut self, callback: F)
where
F: Fn(&Row) -> Result<bool, CsvError> + 'static,
{
self.selection = Some(Box::new(callback));
}
- // TODO: Added &, was the signature wrong?
- pub fn write_to<W: Write>(&mut self, mut writer: W) -> Result<(), CsvError> {
+ #[allow(unused_mut)]
+ pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
let columns = self.columns.clone();
writer.write_fmt(format_args!("{}", columns.join(", ")))?;
for row_result in self.into_iter() {
match row_result {
Err(err) => return Err(err),
Ok(row) => {
writer.write_all("\n".as_bytes())?;
let mut add_comma = false;
for column in &columns {
if !add_comma {
add_comma = true;
} else {
writer.write_all(", ".as_bytes())?;
}
writer.write_fmt(format_args!("\"{}\"", row[column]))?;
}
}
}
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut line = String::new();
let bytes = self.reader.read_line(&mut line);
match bytes {
Err(err) => return Some(Err(CsvError::IO(err))),
Ok(0) => return None,
Ok(_) => (),
};
let row = self.parse_line(&line);
match row {
Err(err) => Some(Err(err)),
Ok(row) => match &self.selection {
None => Some(Ok(row)),
Some(selection) => {
let selected = selection(&row);
match selected {
Err(err) => Some(Err(err)),
Ok(true) => Some(Ok(row)),
Ok(false) => self.next(),
}
}
},
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::BufReader;
macro_rules! assert_err {
($expression:expr, $error:path) => {
assert!($expression.is_err());
match $expression {
Err($error(_)) => assert!(true),
_ => assert!(false, "Mismatched Error Types"),
};
};
}
#[test]
fn test_skip_next() {
assert_eq!(skip_next("[test]", '['), Some("test]"));
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("(foo", ')'), None);
assert_eq!(skip_next("", ')'), None);
assert_eq!(skip_next("ятест]", 'я'), Some("тест]"));
assert_eq!(skip_next("яфуу", 'я'), Some("фуу"));
assert_eq!(skip_next("яфуу", 'ю'), None);
assert_eq!(skip_next("", 'ю'), None);
}
#[test]
fn test_take_until() {
assert_eq!(take_until("one/two", '/'), ("one", "/two"));
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("是的и公雞", 'и'), ("是的", "и公雞"));
assert_eq!(take_until("яяяююю", 'ю'), ("яяя", "ююю"));
assert_eq!(take_until("中文", 'o'), ("中文", ""));
}
#[test]
fn test_take_and_skip() {
assert_eq!(take_and_skip("one/two", '/'), Some(("one", "two")));
assert_eq!(take_and_skip(" foo/bar ", '/'), Some((" foo", "bar ")));
assert_eq!(take_and_skip("foobar", '/'), None);
assert_eq!(take_and_skip("是的 公雞", ' '), Some(("是的", "公雞")));
assert_eq!(take_and_skip(" едноХдве ", 'Х'), Some((" едно", "две ")));
assert_eq!(take_and_skip("тест", 'я'), None);
}
#[test]
fn test_csv_new_missing_headers() {
let data = r#""#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new_dupe_columns() {
let data = r#"
name, age, birth date, name
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new() {
let data = r#"
name , age , birth date
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_eq!(csv.is_err(), false);
assert_eq!(
csv.unwrap().columns,
vec![
"name".to_string(),
"age".to_string(),
"birth date".to_string()
]
);
}
#[test]
fn test_parse_line() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_nested_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv
.parse_line(r#""Basic Name, the 3rd","13","2020-01-01""#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_trailing_whitespace() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv
.parse_line(r#" "Basic Name, the 3rd" , "13" , "2020-01-01" "#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_empty() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13" "13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_quote() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
let row = csv.parse_line(r#"13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_field() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_header() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13","13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_write_to() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Gen Z. Person, the 2nd", "20", "2000-01-01"
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
"\"Gen Z. Person, the 2nd\", \"20\", \"2000-01-01\"",
]
);
}
// TODO: remove this and add more
#[test]
fn test_basic_csv() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
// Parse line:
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
// Iteration:
let filtered_names = csv
.map(|row| row.unwrap()["name"].clone())
.collect::<Vec<_>>();
assert_eq!(filtered_names, &["Gen Z. Person"]);
// Writing:
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
]
);
}
}

Теодор качи решение на 22.12.2020 01:41 (преди почти 5 години)

pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut chars = input.chars();
match chars.next() {
Some(c) if c == target => Some(chars.as_str()),
_ => None,
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
match input.find(target) {
Some(position) => (&input[..position], &input[position..]),
None => (input, ""),
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (first, second) = take_until(input, target);
let second = skip_next(second, target)?;
Some((first, second))
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
impl From<std::io::Error> for CsvError {
fn from(error: std::io::Error) -> Self {
CsvError::IO(error)
}
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
type SelectionResult = Result<bool, CsvError>;
use std::io::BufRead;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> SelectionResult>>,
}
use std::io::Write;
fn has_duplicates<T: Eq + std::hash::Hash>(v: &[T]) -> bool {
use std::collections::HashSet;
let mut items = HashSet::new();
for item in v {
if items.contains(item) {
return true;
}
items.insert(item);
}
false
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header_line = String::new();
let bytes = reader.read_line(&mut header_line)?;
if bytes == 0 {
return Err(CsvError::InvalidHeader(String::from("Missing Header")));
}
let columns = header_line
.split(',')
.map(|header| String::from(header.trim()))
.collect::<Vec<_>>();
if has_duplicates(&columns) {
return Err(CsvError::InvalidHeader(String::from("Duplicate Columns")));
}
Ok(Self {
columns,
reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let mut line = line;
let mut skip_comma = false;
for column in &self.columns {
line = line.trim();
if line.is_empty() {
return Err(CsvError::InvalidRow(String::from(
"Not enough values on row",
)));
}
let result = if skip_comma {
skip_next(line, ',')
} else {
skip_comma = true;
Some(line)
}
.map(|line| line.trim())
.and_then(|line| skip_next(line, '"'))
.and_then(|line| take_and_skip(line, '"'));
match result {
None => return Err(CsvError::InvalidRow(String::from("Missing quoted value"))),
Some((value, rest)) => {
row.insert(column.to_string(), value.to_string());
line = rest;
}
}
}
if line.is_empty() {
Ok(row)
} else {
Err(CsvError::InvalidRow(String::from("Too many values on row")))
}
}
pub fn apply_selection<F>(&mut self, callback: F)
where
F: Fn(&Row) -> Result<bool, CsvError> + 'static,
{
self.selection = Some(Box::new(callback));
}
#[allow(unused_mut)]
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
let columns = self.columns.clone();
writer.write_fmt(format_args!("{}", columns.join(", ")))?;
- for row_result in self.into_iter() {
+ for row_result in self {
match row_result {
Err(err) => return Err(err),
- Ok(row) => {
- writer.write_all("\n".as_bytes())?;
- let mut add_comma = false;
- for column in &columns {
- if !add_comma {
- add_comma = true;
- } else {
- writer.write_all(", ".as_bytes())?;
- }
- writer.write_fmt(format_args!("\"{}\"", row[column]))?;
- }
- }
+ Ok(row) => write_row(&mut writer, row, &columns)?,
}
}
Ok(())
}
}
+fn write_row<W: Write>(writer: &mut W, row: Row, columns: &[String]) -> Result<(), CsvError> {
+ writer.write_fmt(format_args!(
+ "\n{}",
+ columns
+ .iter()
+ .map(|column| format!("\"{}\"", row[column].clone()))
+ .collect::<Vec<_>>()
+ .join(", ")
+ ))?;
+ Ok(())
+}
+
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut line = String::new();
let bytes = self.reader.read_line(&mut line);
match bytes {
Err(err) => return Some(Err(CsvError::IO(err))),
Ok(0) => return None,
Ok(_) => (),
};
let row = self.parse_line(&line);
match row {
Err(err) => Some(Err(err)),
Ok(row) => match &self.selection {
None => Some(Ok(row)),
Some(selection) => {
- let selected = selection(&row);
- match selected {
+ match selection(&row) {
Err(err) => Some(Err(err)),
Ok(true) => Some(Ok(row)),
Ok(false) => self.next(),
}
}
},
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::BufReader;
macro_rules! assert_err {
($expression:expr, $error:path) => {
assert!($expression.is_err());
match $expression {
Err($error(_)) => assert!(true),
_ => assert!(false, "Mismatched Error Types"),
};
};
}
#[test]
fn test_skip_next() {
assert_eq!(skip_next("[test]", '['), Some("test]"));
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("(foo", ')'), None);
assert_eq!(skip_next("", ')'), None);
assert_eq!(skip_next("ятест]", 'я'), Some("тест]"));
assert_eq!(skip_next("яфуу", 'я'), Some("фуу"));
assert_eq!(skip_next("яфуу", 'ю'), None);
assert_eq!(skip_next("", 'ю'), None);
}
#[test]
fn test_take_until() {
assert_eq!(take_until("one/two", '/'), ("one", "/two"));
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("是的и公雞", 'и'), ("是的", "и公雞"));
assert_eq!(take_until("яяяююю", 'ю'), ("яяя", "ююю"));
assert_eq!(take_until("中文", 'o'), ("中文", ""));
}
#[test]
fn test_take_and_skip() {
assert_eq!(take_and_skip("one/two", '/'), Some(("one", "two")));
assert_eq!(take_and_skip(" foo/bar ", '/'), Some((" foo", "bar ")));
assert_eq!(take_and_skip("foobar", '/'), None);
assert_eq!(take_and_skip("是的 公雞", ' '), Some(("是的", "公雞")));
assert_eq!(take_and_skip(" едноХдве ", 'Х'), Some((" едно", "две ")));
assert_eq!(take_and_skip("тест", 'я'), None);
}
#[test]
fn test_csv_new_missing_headers() {
let data = r#""#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new_dupe_columns() {
let data = r#"
name, age, birth date, name
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new() {
let data = r#"
name , age , birth date
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_eq!(csv.is_err(), false);
assert_eq!(
csv.unwrap().columns,
vec![
"name".to_string(),
"age".to_string(),
"birth date".to_string()
]
);
}
#[test]
fn test_parse_line() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_nested_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv
.parse_line(r#""Basic Name, the 3rd","13","2020-01-01""#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_trailing_whitespace() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv
.parse_line(r#" "Basic Name, the 3rd" , "13" , "2020-01-01" "#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_empty() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13" "13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_quote() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
let row = csv.parse_line(r#"13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_field() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_header() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13","13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_write_to() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Gen Z. Person, the 2nd", "20", "2000-01-01"
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
"\"Gen Z. Person, the 2nd\", \"20\", \"2000-01-01\"",
]
);
}
// TODO: remove this and add more
#[test]
fn test_basic_csv() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
// Parse line:
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
// Iteration:
let filtered_names = csv
.map(|row| row.unwrap()["name"].clone())
.collect::<Vec<_>>();
assert_eq!(filtered_names, &["Gen Z. Person"]);
// Writing:
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
]
);
}
}

Теодор качи решение на 09.01.2021 21:20 (преди почти 5 години)

pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut chars = input.chars();
match chars.next() {
Some(c) if c == target => Some(chars.as_str()),
_ => None,
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
match input.find(target) {
Some(position) => (&input[..position], &input[position..]),
None => (input, ""),
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (first, second) = take_until(input, target);
let second = skip_next(second, target)?;
Some((first, second))
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
impl From<std::io::Error> for CsvError {
fn from(error: std::io::Error) -> Self {
CsvError::IO(error)
}
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
type SelectionResult = Result<bool, CsvError>;
use std::io::BufRead;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
- selection: Option<Box<dyn Fn(&Row) -> SelectionResult>>,
+ selection: Box<dyn Fn(&Row) -> SelectionResult>,
}
use std::io::Write;
fn has_duplicates<T: Eq + std::hash::Hash>(v: &[T]) -> bool {
use std::collections::HashSet;
let mut items = HashSet::new();
for item in v {
if items.contains(item) {
return true;
}
items.insert(item);
}
false
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut header_line = String::new();
let bytes = reader.read_line(&mut header_line)?;
if bytes == 0 {
return Err(CsvError::InvalidHeader(String::from("Missing Header")));
}
let columns = header_line
.split(',')
.map(|header| String::from(header.trim()))
.collect::<Vec<_>>();
if has_duplicates(&columns) {
return Err(CsvError::InvalidHeader(String::from("Duplicate Columns")));
}
Ok(Self {
columns,
reader,
- selection: None,
+ selection: Box::new(|_row| Ok(true)),
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let mut line = line;
let mut skip_comma = false;
for column in &self.columns {
line = line.trim();
if line.is_empty() {
return Err(CsvError::InvalidRow(String::from(
"Not enough values on row",
)));
}
let result = if skip_comma {
skip_next(line, ',')
} else {
skip_comma = true;
Some(line)
}
.map(|line| line.trim())
.and_then(|line| skip_next(line, '"'))
.and_then(|line| take_and_skip(line, '"'));
match result {
None => return Err(CsvError::InvalidRow(String::from("Missing quoted value"))),
Some((value, rest)) => {
row.insert(column.to_string(), value.to_string());
line = rest;
}
}
}
if line.is_empty() {
Ok(row)
} else {
Err(CsvError::InvalidRow(String::from("Too many values on row")))
}
}
pub fn apply_selection<F>(&mut self, callback: F)
where
F: Fn(&Row) -> Result<bool, CsvError> + 'static,
{
- self.selection = Some(Box::new(callback));
+ self.selection = Box::new(callback);
}
#[allow(unused_mut)]
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
let columns = self.columns.clone();
writer.write_fmt(format_args!("{}", columns.join(", ")))?;
for row_result in self {
match row_result {
Err(err) => return Err(err),
Ok(row) => write_row(&mut writer, row, &columns)?,
}
}
Ok(())
}
}
fn write_row<W: Write>(writer: &mut W, row: Row, columns: &[String]) -> Result<(), CsvError> {
writer.write_fmt(format_args!(
"\n{}",
columns
.iter()
.map(|column| format!("\"{}\"", row[column].clone()))
.collect::<Vec<_>>()
.join(", ")
))?;
Ok(())
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut line = String::new();
let bytes = self.reader.read_line(&mut line);
match bytes {
Err(err) => return Some(Err(CsvError::IO(err))),
Ok(0) => return None,
Ok(_) => (),
};
let row = self.parse_line(&line);
match row {
Err(err) => Some(Err(err)),
- Ok(row) => match &self.selection {
- None => Some(Ok(row)),
- Some(selection) => {
- match selection(&row) {
- Err(err) => Some(Err(err)),
- Ok(true) => Some(Ok(row)),
- Ok(false) => self.next(),
- }
- }
+ Ok(row) => match (self.selection)(&row) {
+ Err(err) => Some(Err(err)),
+ Ok(true) => Some(Ok(row)),
+ Ok(false) => self.next(),
},
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::io::BufReader;
macro_rules! assert_err {
($expression:expr, $error:path) => {
assert!($expression.is_err());
match $expression {
Err($error(_)) => assert!(true),
_ => assert!(false, "Mismatched Error Types"),
};
};
}
#[test]
fn test_skip_next() {
assert_eq!(skip_next("[test]", '['), Some("test]"));
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("(foo", ')'), None);
assert_eq!(skip_next("", ')'), None);
assert_eq!(skip_next("ятест]", 'я'), Some("тест]"));
assert_eq!(skip_next("яфуу", 'я'), Some("фуу"));
assert_eq!(skip_next("яфуу", 'ю'), None);
assert_eq!(skip_next("", 'ю'), None);
}
#[test]
fn test_take_until() {
assert_eq!(take_until("one/two", '/'), ("one", "/two"));
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("是的и公雞", 'и'), ("是的", "и公雞"));
assert_eq!(take_until("яяяююю", 'ю'), ("яяя", "ююю"));
assert_eq!(take_until("中文", 'o'), ("中文", ""));
}
#[test]
fn test_take_and_skip() {
assert_eq!(take_and_skip("one/two", '/'), Some(("one", "two")));
assert_eq!(take_and_skip(" foo/bar ", '/'), Some((" foo", "bar ")));
assert_eq!(take_and_skip("foobar", '/'), None);
assert_eq!(take_and_skip("是的 公雞", ' '), Some(("是的", "公雞")));
assert_eq!(take_and_skip(" едноХдве ", 'Х'), Some((" едно", "две ")));
assert_eq!(take_and_skip("тест", 'я'), None);
}
#[test]
fn test_csv_new_missing_headers() {
let data = r#""#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new_dupe_columns() {
let data = r#"
name, age, birth date, name
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_err!(csv, CsvError::InvalidHeader);
}
#[test]
fn test_csv_new() {
let data = r#"
name , age , birth date
"#
.trim()
.as_bytes();
let csv = Csv::new(BufReader::new(data));
assert_eq!(csv.is_err(), false);
assert_eq!(
csv.unwrap().columns,
vec![
"name".to_string(),
"age".to_string(),
"birth date".to_string()
]
);
}
#[test]
+ fn test_csv_new_empty_column_name() {
+ let data = r#"
+ name , , birth date
+ "#
+ .trim()
+ .as_bytes();
+
+ let csv = Csv::new(BufReader::new(data));
+
+ assert_eq!(csv.is_err(), false);
+
+ assert_eq!(
+ csv.unwrap().columns,
+ vec!["name".to_string(), "".to_string(), "birth date".to_string()]
+ );
+ }
+
+ #[test]
fn test_parse_line() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_nested_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv
.parse_line(r#""Basic Name, the 3rd","13","2020-01-01""#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_trailing_whitespace() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv
.parse_line(r#" "Basic Name, the 3rd" , "13" , "2020-01-01" "#)
.unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name, the 3rd", "13", "2020-01-01"),
};
}
#[test]
fn test_parse_line_empty() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_comma() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13" "13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_quote() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
let row = csv.parse_line(r#"13","13", "13"#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_field() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_parse_missing_header() {
let data = r#"
name, age, birth date
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
let row = csv.parse_line(r#""13","13","13","13""#);
assert_err!(row, CsvError::InvalidRow);
}
#[test]
fn test_write_to() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Gen Z. Person, the 2nd", "20", "2000-01-01"
"#
.trim()
.as_bytes();
- let mut csv = Csv::new(BufReader::new(data)).unwrap();
- csv.apply_selection(|_row| Ok(true));
+ let csv = Csv::new(BufReader::new(data)).unwrap();
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
"\"Gen Z. Person, the 2nd\", \"20\", \"2000-01-01\"",
+ ]
+ );
+ }
+
+ #[test]
+ fn test_iterator() {
+ let data = r#"
+ name, age, birth date
+ "Gen Z. Person", "20", "2000-01-01"
+ "Gen Z. Person, the 2nd", "19", "2001-01-01"
+ "Gen Z. Person, the 3rd", "18", "2002-01-01"
+ "#
+ .trim()
+ .as_bytes();
+
+ // Normal
+ let mut csv = Csv::new(BufReader::new(data)).unwrap();
+ csv.apply_selection(|_row| Ok(true));
+
+ let names = csv
+ .map(|row| row.unwrap()["name"].clone())
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ names,
+ &[
+ "Gen Z. Person",
+ "Gen Z. Person, the 2nd",
+ "Gen Z. Person, the 3rd"
+ ]
+ );
+
+ // Error in selection
+ let mut csv = Csv::new(BufReader::new(data)).unwrap();
+ csv.apply_selection(|_row| Err(CsvError::ParseError(String::from("kappa"))));
+ assert!(csv.all(|row| row.is_err()));
+
+ // Pickier selection
+ let mut csv = Csv::new(BufReader::new(data)).unwrap();
+ csv.apply_selection(|row| {
+ if row["age"] != "19" {
+ Ok(true)
+ } else {
+ Ok(false)
+ }
+ });
+
+ let names = csv
+ .map(|row| row.unwrap()["name"].clone())
+ .collect::<Vec<_>>();
+
+ assert_eq!(names, &["Gen Z. Person", "Gen Z. Person, the 3rd"]);
+
+ // No selection
+ let csv = Csv::new(BufReader::new(data)).unwrap();
+
+ let names = csv
+ .map(|row| row.unwrap()["name"].clone())
+ .collect::<Vec<_>>();
+
+ assert_eq!(
+ names,
+ &[
+ "Gen Z. Person",
+ "Gen Z. Person, the 2nd",
+ "Gen Z. Person, the 3rd"
]
);
}
// TODO: remove this and add more
#[test]
fn test_basic_csv() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"#
.trim()
.as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
// Parse line:
let row = csv.parse_line(r#""Basic Name","13","2020-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Basic Name", "13", "2020-01-01"),
};
// Iteration:
let filtered_names = csv
.map(|row| row.unwrap()["name"].clone())
.collect::<Vec<_>>();
assert_eq!(filtered_names, &["Gen Z. Person"]);
// Writing:
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let output_lines = output.lines().map(Result::unwrap).collect::<Vec<String>>();
assert_eq!(
output_lines,
&[
"name, age, birth date",
"\"Gen Z. Person\", \"20\", \"2000-01-01\"",
]
);
}
}