Решение на CSV Filter от Тервел Вълков
Резултати
- 15 точки от тестове
- 0 бонус точки
- 15 точки общо
- 15 успешни тест(а)
- 0 неуспешни тест(а)
Код
pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut input_chars = input.chars();
if let Some(first_char) = input_chars.next() {
if first_char == target {
return Some(&input[first_char.len_utf8()..]);
}
}
None
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
for (pos, current_char) in input.char_indices() {
if current_char == target {
return input.split_at(pos);
}
}
(input, "")
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
for (pos, current_char) in input.char_indices() {
if current_char == target {
return Some((&input[..pos], &input[pos + current_char.len_utf8()..]));
// let (first, second) = take_until(input, target);
// let second_skipped = skip_next(second, target);
// if let Some(second) = second_skipped {
// return Some((first, second));
// } else {
// return None
// }
}
}
None
}
pub fn take_and_skip_outside_of_quotes(input: &str, target: char) -> Option<(&str, &str)> {
let mut inside_quotes = false;
for (pos, current_char) in input.char_indices() {
if current_char == '"' {
inside_quotes = !inside_quotes;
}
if current_char == target && inside_quotes == false {
return Some((&input[..pos], &input[pos + current_char.len_utf8()..]));
}
}
None
}
pub fn split_line(line: &str, delim: char) -> Vec<String> {
let mut words = Vec::<String>::new();
let mut line = line.trim();
while let Some((word, rest)) = take_and_skip_outside_of_quotes(line, delim) {
let word = String::from(word.trim());
words.push(word);
line = rest;
}
words.push(line.trim().to_string());
return words;
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
use std::io::{BufRead, Write};
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut columns = Vec::<String>::new();
let mut first_line = String::new();
match reader.read_line(&mut first_line) {
Err(error) => return Err(CsvError::IO(error)),
Ok(0) => return Err(CsvError::InvalidHeader("Empty!".to_string())),
Ok(_) => {
let column_names = split_line(&first_line, ',');
for column_name in column_names {
if columns.contains(&column_name) {
return Err(CsvError::InvalidHeader(column_name + " was repeated!"));
}
columns.push(column_name);
}
}
}
return Ok(Csv {
columns: columns,
reader: reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let column_values = split_line(line, ',');
if column_values.len() < self.columns.len() {
return Err(CsvError::InvalidRow(line.to_string() + ": not enough values"));
} else if column_values.len() > self.columns.len() {
return Err(CsvError::InvalidRow(line.to_string() + ": too many values"));
}
for i in 0..self.columns.len() {
match skip_next(&column_values[i], '"') {
None => return Err(CsvError::InvalidRow(line.to_string() + ": no opening quote")),
Some(column_value) => {
let (actual_value, closing_quote) = take_until(column_value, '"');
if closing_quote.is_empty() {
return Err(CsvError::InvalidRow(line.to_string() + ": no closing quote"));
} else if closing_quote != "\"" {
return Err(CsvError::InvalidRow(line.to_string() + ": too many closing quotes"));
}
row.insert(self.columns[i].clone(), actual_value.to_string());
}
}
}
return Ok(row);
}
pub fn apply_selection<F>(&mut self, callback: F) where F: Fn(&Row) -> Result<bool, CsvError> + 'static {
self.selection = Some(Box::new(callback));
}
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
// write header
let mut text = String::new();
for i in 0..self.columns.len() {
text += &self.columns[i];
if i != self.columns.len() - 1 {
text += ", ";
}
}
text += "\n";
match writer.write(text.as_bytes()) {
Err(error) => return Err(CsvError::IO(error)),
Ok(_) => ()
}
// write rows
while let Some(row) = self.next() {
match row {
Err(error) => return Err(error),
Ok(row) => {
let mut text = String::new();
for i in 0..self.columns.len() {
text += "\"";
text += &row[&self.columns[i]];
text += "\"";
if i != self.columns.len() - 1 {
text += ", ";
}
}
text += "\n";
match writer.write(text.as_bytes()) {
Err(error) => return Err(CsvError::IO(error)),
Ok(_) => ()
}
}
}
}
return 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();
match self.reader.read_line(&mut line) {
Err(error) => return Some(Err(CsvError::IO(error))),
Ok(0) => return None,
Ok(_) =>
match self.parse_line(&line) {
Err(error) => return Some(Err(error)),
Ok(row) => {
if let Some(selection) = &self.selection {
match selection(&row) {
Err(error) => return Some(Err(error)),
Ok(false) => return self.next(),
Ok(true) => return Some(Ok(row)),
}
} else {
return Some(Ok(row));
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::BufReader;
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
if let $pat = $expr {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", $expr, stringify!($pat));
}
}
}
#[test]
fn skip_next_test() {
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("(foo", ')'), None);
assert_eq!(skip_next("", ')'), None);
assert_eq!(skip_next("фоо", 'ф'), Some("оо"));
}
#[test]
fn take_until_test() {
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("фоо/бар", '/'), ("фоо", "/бар"));
assert_eq!(take_until("фооДбар", 'Д'), ("фоо", "Дбар"));
}
#[test]
fn take_and_skip_test() {
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(("фоо", "бар")));
}
#[test]
fn split_line_test() {
assert_eq!(split_line("foo,bar,zoo", ','), vec!["foo","bar","zoo"]);
assert_eq!(split_line(" foo , bar , zoo ", ','), vec!["foo","bar","zoo"]);
assert_eq!(split_line("foo,,zoo", ','), vec!["foo","","zoo"]);
assert_eq!(split_line(r#""foo","bar","zoo""#, ','), vec![r#""foo""#,r#""bar""#,r#""zoo""#]);
assert_eq!(split_line(r#"" foo ","b,a,r"," z,oo ""#, ','), vec![r#"" foo ""#,r#""b,a,r""#,r#"" z,oo ""#]);
}
#[test]
fn csv_init_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data)).unwrap();
assert_eq!(csv.columns.len(), 3);
assert_eq!(csv.columns[0], "name");
assert_eq!(csv.columns[1], "age");
assert_eq!(csv.columns[2], "birth date");
}
#[test]
fn csv_init_empty_column_name_test() {
let data = r#"
name, , age
"Gen Z. Person", "20", "2000-01-01"
"#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data)).unwrap();
assert_eq!(csv.columns.len(), 3);
assert_eq!(csv.columns[0], "name");
assert_eq!(csv.columns[1], "");
assert_eq!(csv.columns[2], "age");
}
#[test]
fn csv_init_error_empty() {
let data = " ".trim().as_bytes();
assert_match!(Csv::new(BufReader::new(data)).err(), Some(CsvError::InvalidHeader(_)));
}
#[test]
fn csv_init_error_duplicate() {
let data = "name, age, name".trim().as_bytes();
assert_match!(Csv::new(BufReader::new(data)).err(), Some(CsvError::InvalidHeader(_)));
}
#[test]
fn csv_parse_line_test() {
let data = r#"name, age, birth date"#.trim().as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let mut row = csv.parse_line(r#""Gen Z. Person", "20", "2000-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Gen Z. Person", "20", "2000-01-01"),
};
// empty column
row = csv.parse_line(r#""Someone", "", "1990-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Someone", "", "1990-01-01"),
};
// whitespace
row = csv.parse_line(r#"" Someone ", "20", "1990-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
(" Someone ", "20", "1990-01-01"),
};
// commas in column
row = csv.parse_line(r#""Some,one", "20", "1990-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Some,one", "20", "1990-01-01"),
};
}
#[test]
fn csv_iter_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "", "1990-01-01"
"#.trim().as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
assert_eq!(names, &["Gen Z. Person", "Douglas Adams", "Ada Lovelace", "Someone"]);
let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
assert_eq!(ages, &["20", "42", "36", ""]);
let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
assert_eq!(birth_dates, &["2000-01-01", "1952-03-11", "1815-12-10", "1990-01-01"]);
}
#[test]
fn csv_iter_filter_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "25", "1990-01-01"
"#.trim().as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|row| { Ok(row["age"].parse::<u32>().unwrap() < 30) });
let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
assert_eq!(names, &["Gen Z. Person", "Someone"]);
let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
assert_eq!(ages, &["20", "25"]);
let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
assert_eq!(birth_dates, &["2000-01-01", "1990-01-01"]);
}
#[test]
fn csv_iter_empty_filter_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "25", "1990-01-01"
"#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data)).unwrap();
let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
assert_eq!(names, &["Gen Z. Person", "Douglas Adams", "Ada Lovelace", "Someone"]);
let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
assert_eq!(ages, &["20", "42", "36", "25"]);
let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
assert_eq!(birth_dates, &["2000-01-01", "1952-03-11", "1815-12-10", "1990-01-01"]);
}
#[test]
fn csv_iter_filter_site_test() {
let reader = BufReader::new(r#"
name, age, birth date
"Douglas Adams", "42", "1952-03-11"
"Gen Z. Person", "20", "2000-01-01"
"Ada Lovelace", "36", "1815-12-10"
"#.trim().as_bytes());
let mut csv = Csv::new(reader).unwrap();
csv.apply_selection(|row| {
let age = row.get("age").ok_or_else(|| CsvError::InvalidColumn(String::from("age")))?;
let age = age.parse::<u32>().map_err(|_| CsvError::ParseError(String::from(age)))?;
Ok(age > 30)
});
let filtered_names = csv.map(|row| row.unwrap()["name"].clone()).collect::<Vec<_>>();
assert_eq!(filtered_names, &["Douglas Adams", "Ada Lovelace"]);
}
#[test]
fn csv_write_site_test() {
let reader = BufReader::new(r#"
name, age ,birth date
"Douglas Adams","42","1952-03-11"
"Gen Z. Person", "20" , "2000-01-01"
"Ada Lovelace","36","1815-12-10"
"#.trim().as_bytes());
let mut csv = Csv::new(reader).unwrap();
csv.apply_selection(|row| {
Ok(row["age"].parse::<u32>().unwrap() < 40)
});
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let expected_output = concat!("name, age, birth date", "\n",
r#""Gen Z. Person", "20", "2000-01-01""#, "\n",
r#""Ada Lovelace", "36", "1815-12-10""#, "\n");
assert_eq!(String::from_utf8(output).unwrap(), expected_output);
}
#[test]
fn csv_write_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "25", "1990-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 expected_output = concat!("name, age, birth date", "\n",
r#""Gen Z. Person", "20", "2000-01-01""#, "\n",
r#""Douglas Adams", "42", "1952-03-11""#, "\n",
r#""Ada Lovelace", "36", "1815-12-10""#, "\n",
r#""Someone", "25", "1990-01-01""#, "\n");
assert_eq!(String::from_utf8(output).unwrap(), expected_output);
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20210111-1538662-1atteyh/solution) Finished test [unoptimized + debuginfo] target(s) in 4.07s 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
История (3 версии и 0 коментара)
Тервел качи решение на 01.01.2021 17:04 (преди почти 5 години)
pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut input_chars = input.chars();
if let Some(first_char) = input_chars.next() {
if first_char == target {
return Some(&input[first_char.len_utf8()..]);
}
}
None
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
for (pos, current_char) in input.char_indices() {
if current_char == target {
return input.split_at(pos);
}
}
(input, "")
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
for (pos, current_char) in input.char_indices() {
if current_char == target {
return Some((&input[..pos], &input[pos + current_char.len_utf8()..]));
// let (first, second) = take_until(input, target);
// let second_skipped = skip_next(second, target);
// if let Some(second) = second_skipped {
// return Some((first, second));
// } else {
// return None
// }
}
}
None
}
+
+pub fn take_and_skip_outside_of_quotes(input: &str, target: char) -> Option<(&str, &str)> {
+ let mut inside_quotes = false;
+
+ for (pos, current_char) in input.char_indices() {
+ if current_char == '"' {
+ inside_quotes = !inside_quotes;
+ }
+
+ if current_char == target && inside_quotes == false {
+ return Some((&input[..pos], &input[pos + current_char.len_utf8()..]));
+ }
+ }
+
+ None
+}
pub fn split_line(line: &str, delim: char) -> Vec<String> {
let mut words = Vec::<String>::new();
let mut line = line.trim();
- while let Some((word, rest)) = take_and_skip(line, delim) {
+ while let Some((word, rest)) = take_and_skip_outside_of_quotes(line, delim) {
let word = String::from(word.trim());
words.push(word);
line = rest;
}
words.push(line.trim().to_string());
return words;
}
+
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
-use std::io::{BufRead, BufReader, Write};
+use std::io::{BufRead, Write};
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut columns = Vec::<String>::new();
let mut first_line = String::new();
match reader.read_line(&mut first_line) {
Err(error) => return Err(CsvError::IO(error)),
Ok(0) => return Err(CsvError::InvalidHeader("Empty!".to_string())),
Ok(_) => {
let column_names = split_line(&first_line, ',');
for column_name in column_names {
if columns.contains(&column_name) {
return Err(CsvError::InvalidHeader(column_name + " was repeated!"));
}
columns.push(column_name);
}
}
}
return Ok(Csv {
columns: columns,
reader: reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let column_values = split_line(line, ',');
if column_values.len() < self.columns.len() {
return Err(CsvError::InvalidRow(line.to_string() + ": not enough values"));
} else if column_values.len() > self.columns.len() {
return Err(CsvError::InvalidRow(line.to_string() + ": too many values"));
}
for i in 0..self.columns.len() {
match skip_next(&column_values[i], '"') {
None => return Err(CsvError::InvalidRow(line.to_string() + ": no opening quote")),
Some(column_value) => {
let (actual_value, closing_quote) = take_until(column_value, '"');
if closing_quote.is_empty() {
return Err(CsvError::InvalidRow(line.to_string() + ": no closing quote"));
} else if closing_quote != "\"" {
return Err(CsvError::InvalidRow(line.to_string() + ": too many closing quotes"));
}
row.insert(self.columns[i].clone(), actual_value.to_string());
}
}
}
return Ok(row);
}
pub fn apply_selection<F>(&mut self, callback: F) where F: Fn(&Row) -> Result<bool, CsvError> + 'static {
self.selection = Some(Box::new(callback));
}
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
// write header
let mut text = String::new();
for i in 0..self.columns.len() {
text += &self.columns[i];
if i != self.columns.len() - 1 {
text += ", ";
}
}
text += "\n";
match writer.write(text.as_bytes()) {
Err(error) => return Err(CsvError::IO(error)),
Ok(_) => ()
}
// write rows
while let Some(row) = self.next() {
match row {
Err(error) => return Err(error),
Ok(row) => {
let mut text = String::new();
for i in 0..self.columns.len() {
text += "\"";
text += &row[&self.columns[i]];
text += "\"";
if i != self.columns.len() - 1 {
text += ", ";
}
}
text += "\n";
match writer.write(text.as_bytes()) {
Err(error) => return Err(CsvError::IO(error)),
Ok(_) => ()
}
}
}
}
return 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();
match self.reader.read_line(&mut line) {
Err(error) => return Some(Err(CsvError::IO(error))),
Ok(0) => return None,
Ok(_) =>
match self.parse_line(&line) {
Err(error) => return Some(Err(error)),
Ok(row) => {
if let Some(sel) = &self.selection {
match sel(&row) {
Err(error) => return Some(Err(error)),
Ok(false) => return self.next(),
Ok(true) => return Some(Ok(row)),
}
} else {
match (|_row| Ok(true))(&row) {
Err(error) => return Some(Err(error)),
Ok(false) => return self.next(),
Ok(true) => return Some(Ok(row)),
}
}
// ??? ask question - what should actually happen when we have no applied selection?
//match (self.selection.as_ref().unwrap())(&row) {
//match (self.selection.as_ref().unwrap_or(Box::new(|_row| Ok(true))))(&row) {
// match selection(&row) {
// Err(error) => return Some(Err(error)),
// Ok(false) => return self.next(),
// Ok(true) => return Some(Ok(row)),
// }
}
}
}
}
}
-
-
#[cfg(test)]
mod tests {
use super::*;
+ use std::io::BufReader;
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
if let $pat = $expr {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", $expr, stringify!($pat));
}
}
}
#[test]
fn skip_next_test() {
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("(foo", ')'), None);
assert_eq!(skip_next("", ')'), None);
assert_eq!(skip_next("фоо", 'ф'), Some("оо"));
}
#[test]
fn take_until_test() {
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("фоо/бар", '/'), ("фоо", "/бар"));
assert_eq!(take_until("фооДбар", 'Д'), ("фоо", "Дбар"));
}
#[test]
fn take_and_skip_test() {
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(("фоо", "бар")));
}
#[test]
fn split_line_test() {
assert_eq!(split_line("foo,bar,zoo", ','), vec!["foo","bar","zoo"]);
assert_eq!(split_line(" foo , bar , zoo ", ','), vec!["foo","bar","zoo"]);
assert_eq!(split_line("foo,,zoo", ','), vec!["foo","","zoo"]);
+
+ assert_eq!(split_line(r#""foo","bar","zoo""#, ','), vec![r#""foo""#,r#""bar""#,r#""zoo""#]);
+ assert_eq!(split_line(r#"" foo ","b,a,r"," z,oo ""#, ','), vec![r#"" foo ""#,r#""b,a,r""#,r#"" z,oo ""#]);
}
#[test]
fn csv_init_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"#.trim().as_bytes();
- if let Ok(csv) = Csv::new(BufReader::new(data)) {
- assert_eq!(csv.columns.len(), 3);
- assert_eq!(csv.columns[0], "name");
- assert_eq!(csv.columns[1], "age");
- assert_eq!(csv.columns[2], "birth date");
- }
+ let csv = Csv::new(BufReader::new(data)).unwrap();
+ assert_eq!(csv.columns.len(), 3);
+ assert_eq!(csv.columns[0], "name");
+ assert_eq!(csv.columns[1], "age");
+ assert_eq!(csv.columns[2], "birth date");
}
+ #[test]
+ fn csv_init_empty_column_name_test() {
+ let data = r#"
+ name, , age
+ "Gen Z. Person", "20", "2000-01-01"
+ "#.trim().as_bytes();
+
+ let csv = Csv::new(BufReader::new(data)).unwrap();
+ assert_eq!(csv.columns.len(), 3);
+ assert_eq!(csv.columns[0], "name");
+ assert_eq!(csv.columns[1], "");
+ assert_eq!(csv.columns[2], "age");
+ }
+ #[test]
+ fn csv_init_error_empty() {
+ let data = " ".trim().as_bytes();
+ assert_match!(Csv::new(BufReader::new(data)).err(), Some(CsvError::InvalidHeader(_)));
+ }
+ #[test]
+ fn csv_init_error_duplicate() {
+ let data = "name, age, name".trim().as_bytes();
+ assert_match!(Csv::new(BufReader::new(data)).err(), Some(CsvError::InvalidHeader(_)));
+
+ }
+
+ #[test]
+ fn csv_parse_line_test() {
+ let data = r#"name, age, birth date"#.trim().as_bytes();
+
+ let mut csv = Csv::new(BufReader::new(data)).unwrap();
+
+ let mut row = csv.parse_line(r#""Gen Z. Person", "20", "2000-01-01""#).unwrap();
+ assert_eq! {
+ (row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
+ ("Gen Z. Person", "20", "2000-01-01"),
+ };
+
+ // empty column
+ row = csv.parse_line(r#""Someone", "", "1990-01-01""#).unwrap();
+ assert_eq! {
+ (row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
+ ("Someone", "", "1990-01-01"),
+ };
+
+ // whitespace
+ row = csv.parse_line(r#"" Someone ", "20", "1990-01-01""#).unwrap();
+ assert_eq! {
+ (row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
+ (" Someone ", "20", "1990-01-01"),
+ };
+
+ // commas in column
+ row = csv.parse_line(r#""Some,one", "20", "1990-01-01""#).unwrap();
+ assert_eq! {
+ (row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
+ ("Some,one", "20", "1990-01-01"),
+ };
+ }
+
+ #[test]
+ fn csv_iter_test() {
+ let data = r#"
+ name, age, birth date
+ "Gen Z. Person", "20", "2000-01-01"
+ "Douglas Adams", "42", "1952-03-11"
+ "Ada Lovelace", "36", "1815-12-10"
+ "Someone", "", "1990-01-01"
+ "#.trim().as_bytes();
+
+ let mut csv = Csv::new(BufReader::new(data)).unwrap();
+ csv.apply_selection(|_row| Ok(true));
+
+ let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
+ let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
+ assert_eq!(names, &["Gen Z. Person", "Douglas Adams", "Ada Lovelace", "Someone"]);
+
+ let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
+ assert_eq!(ages, &["20", "42", "36", ""]);
+
+ let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
+ assert_eq!(birth_dates, &["2000-01-01", "1952-03-11", "1815-12-10", "1990-01-01"]);
+ }
+ #[test]
+ fn csv_iter_filter_test() {
+ let data = r#"
+ name, age, birth date
+ "Gen Z. Person", "20", "2000-01-01"
+ "Douglas Adams", "42", "1952-03-11"
+ "Ada Lovelace", "36", "1815-12-10"
+ "Someone", "25", "1990-01-01"
+ "#.trim().as_bytes();
+
+ let mut csv = Csv::new(BufReader::new(data)).unwrap();
+ csv.apply_selection(|row| { Ok(row["age"].parse::<u32>().unwrap() < 30) });
+
+ let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
+ let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
+ assert_eq!(names, &["Gen Z. Person", "Someone"]);
- // #[test]
- // fn csv_init_error_empty() {
- // let data = " ".trim().as_bytes();
- // assert_match!(Csv::new(BufReader::new(data)), Err(CsvError::IO(_)));
- // }
+ let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
+ assert_eq!(ages, &["20", "25"]);
+ let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
+ assert_eq!(birth_dates, &["2000-01-01", "1990-01-01"]);
+ }
#[test]
- fn csv_write_test() {
+ fn csv_iter_empty_filter_test() {
+ let data = r#"
+ name, age, birth date
+ "Gen Z. Person", "20", "2000-01-01"
+ "Douglas Adams", "42", "1952-03-11"
+ "Ada Lovelace", "36", "1815-12-10"
+ "Someone", "25", "1990-01-01"
+ "#.trim().as_bytes();
+
+ let csv = Csv::new(BufReader::new(data)).unwrap();
+
+ let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
+ let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
+ assert_eq!(names, &["Gen Z. Person", "Douglas Adams", "Ada Lovelace", "Someone"]);
+
+ let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
+ assert_eq!(ages, &["20", "42", "36", "25"]);
+
+ let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
+ assert_eq!(birth_dates, &["2000-01-01", "1952-03-11", "1815-12-10", "1990-01-01"]);
+ }
+ #[test]
+ fn csv_iter_filter_site_test() {
let reader = BufReader::new(r#"
+ name, age, birth date
+ "Douglas Adams", "42", "1952-03-11"
+ "Gen Z. Person", "20", "2000-01-01"
+ "Ada Lovelace", "36", "1815-12-10"
+ "#.trim().as_bytes());
+
+ let mut csv = Csv::new(reader).unwrap();
+ csv.apply_selection(|row| {
+ let age = row.get("age").ok_or_else(|| CsvError::InvalidColumn(String::from("age")))?;
+ let age = age.parse::<u32>().map_err(|_| CsvError::ParseError(String::from(age)))?;
+
+ Ok(age > 30)
+ });
+
+ let filtered_names = csv.map(|row| row.unwrap()["name"].clone()).collect::<Vec<_>>();
+ assert_eq!(filtered_names, &["Douglas Adams", "Ada Lovelace"]);
+ }
+
+ #[test]
+ fn csv_write_site_test() {
+ let reader = BufReader::new(r#"
name, age ,birth date
"Douglas Adams","42","1952-03-11"
"Gen Z. Person", "20" , "2000-01-01"
"Ada Lovelace","36","1815-12-10"
"#.trim().as_bytes());
let mut csv = Csv::new(reader).unwrap();
csv.apply_selection(|row| {
Ok(row["age"].parse::<u32>().unwrap() < 40)
});
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
-// let expected_output =
-// r#"name, age, birth date
-// "Gen Z. Person", "20", "2000-01-01"
-// "Ada Lovelace", "36", "1815-12-10"
-// "#;
- let expected_output = concat!("name, age, birth date", "\n", r#""Gen Z. Person", "20", "2000-01-01""#, "\n", r#""Ada Lovelace", "36", "1815-12-10""#, "\n");
-
+ let expected_output = concat!("name, age, birth date", "\n",
+ r#""Gen Z. Person", "20", "2000-01-01""#, "\n",
+ r#""Ada Lovelace", "36", "1815-12-10""#, "\n");
assert_eq!(String::from_utf8(output).unwrap(), expected_output);
}
+ #[test]
+ fn csv_write_test() {
+ let data = r#"
+ name, age, birth date
+ "Gen Z. Person", "20", "2000-01-01"
+ "Douglas Adams", "42", "1952-03-11"
+ "Ada Lovelace", "36", "1815-12-10"
+ "Someone", "25", "1990-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 expected_output = concat!("name, age, birth date", "\n",
+ r#""Gen Z. Person", "20", "2000-01-01""#, "\n",
+ r#""Douglas Adams", "42", "1952-03-11""#, "\n",
+ r#""Ada Lovelace", "36", "1815-12-10""#, "\n",
+ r#""Someone", "25", "1990-01-01""#, "\n");
+ assert_eq!(String::from_utf8(output).unwrap(), expected_output);
+ }
}
Тервел качи решение на 02.01.2021 12:59 (преди почти 5 години)
pub fn skip_next(input: &str, target: char) -> Option<&str> {
let mut input_chars = input.chars();
if let Some(first_char) = input_chars.next() {
if first_char == target {
return Some(&input[first_char.len_utf8()..]);
}
}
None
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
for (pos, current_char) in input.char_indices() {
if current_char == target {
return input.split_at(pos);
}
}
(input, "")
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
for (pos, current_char) in input.char_indices() {
if current_char == target {
return Some((&input[..pos], &input[pos + current_char.len_utf8()..]));
// let (first, second) = take_until(input, target);
// let second_skipped = skip_next(second, target);
// if let Some(second) = second_skipped {
// return Some((first, second));
// } else {
// return None
// }
}
}
None
}
pub fn take_and_skip_outside_of_quotes(input: &str, target: char) -> Option<(&str, &str)> {
let mut inside_quotes = false;
for (pos, current_char) in input.char_indices() {
if current_char == '"' {
inside_quotes = !inside_quotes;
}
if current_char == target && inside_quotes == false {
return Some((&input[..pos], &input[pos + current_char.len_utf8()..]));
}
}
None
}
pub fn split_line(line: &str, delim: char) -> Vec<String> {
let mut words = Vec::<String>::new();
let mut line = line.trim();
while let Some((word, rest)) = take_and_skip_outside_of_quotes(line, delim) {
let word = String::from(word.trim());
words.push(word);
line = rest;
}
words.push(line.trim().to_string());
return words;
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
use std::io::{BufRead, Write};
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut columns = Vec::<String>::new();
let mut first_line = String::new();
match reader.read_line(&mut first_line) {
Err(error) => return Err(CsvError::IO(error)),
Ok(0) => return Err(CsvError::InvalidHeader("Empty!".to_string())),
Ok(_) => {
let column_names = split_line(&first_line, ',');
for column_name in column_names {
if columns.contains(&column_name) {
return Err(CsvError::InvalidHeader(column_name + " was repeated!"));
}
columns.push(column_name);
}
}
}
return Ok(Csv {
columns: columns,
reader: reader,
selection: None,
})
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut row = Row::new();
let column_values = split_line(line, ',');
if column_values.len() < self.columns.len() {
return Err(CsvError::InvalidRow(line.to_string() + ": not enough values"));
} else if column_values.len() > self.columns.len() {
return Err(CsvError::InvalidRow(line.to_string() + ": too many values"));
}
for i in 0..self.columns.len() {
match skip_next(&column_values[i], '"') {
None => return Err(CsvError::InvalidRow(line.to_string() + ": no opening quote")),
Some(column_value) => {
let (actual_value, closing_quote) = take_until(column_value, '"');
if closing_quote.is_empty() {
return Err(CsvError::InvalidRow(line.to_string() + ": no closing quote"));
} else if closing_quote != "\"" {
return Err(CsvError::InvalidRow(line.to_string() + ": too many closing quotes"));
}
row.insert(self.columns[i].clone(), actual_value.to_string());
}
}
}
return Ok(row);
}
pub fn apply_selection<F>(&mut self, callback: F) where F: Fn(&Row) -> Result<bool, CsvError> + 'static {
self.selection = Some(Box::new(callback));
}
pub fn write_to<W: Write>(mut self, mut writer: W) -> Result<(), CsvError> {
// write header
let mut text = String::new();
for i in 0..self.columns.len() {
text += &self.columns[i];
if i != self.columns.len() - 1 {
text += ", ";
}
}
text += "\n";
match writer.write(text.as_bytes()) {
Err(error) => return Err(CsvError::IO(error)),
Ok(_) => ()
}
// write rows
while let Some(row) = self.next() {
match row {
Err(error) => return Err(error),
Ok(row) => {
let mut text = String::new();
for i in 0..self.columns.len() {
text += "\"";
text += &row[&self.columns[i]];
text += "\"";
if i != self.columns.len() - 1 {
text += ", ";
}
}
text += "\n";
match writer.write(text.as_bytes()) {
Err(error) => return Err(CsvError::IO(error)),
Ok(_) => ()
}
}
}
}
return 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();
match self.reader.read_line(&mut line) {
Err(error) => return Some(Err(CsvError::IO(error))),
Ok(0) => return None,
Ok(_) =>
match self.parse_line(&line) {
Err(error) => return Some(Err(error)),
Ok(row) => {
- if let Some(sel) = &self.selection {
- match sel(&row) {
+ if let Some(selection) = &self.selection {
+ match selection(&row) {
Err(error) => return Some(Err(error)),
Ok(false) => return self.next(),
Ok(true) => return Some(Ok(row)),
}
} else {
- match (|_row| Ok(true))(&row) {
- Err(error) => return Some(Err(error)),
- Ok(false) => return self.next(),
- Ok(true) => return Some(Ok(row)),
- }
+ return Some(Ok(row));
}
-
- // ??? ask question - what should actually happen when we have no applied selection?
-
- //match (self.selection.as_ref().unwrap())(&row) {
- //match (self.selection.as_ref().unwrap_or(Box::new(|_row| Ok(true))))(&row) {
- // match selection(&row) {
- // Err(error) => return Some(Err(error)),
- // Ok(false) => return self.next(),
- // Ok(true) => return Some(Ok(row)),
- // }
-
}
-
}
}
}
}
-
-
#[cfg(test)]
mod tests {
use super::*;
use std::io::BufReader;
macro_rules! assert_match {
($expr:expr, $pat:pat) => {
if let $pat = $expr {
// all good
} else {
assert!(false, "Expression {:?} does not match the pattern {:?}", $expr, stringify!($pat));
}
}
}
#[test]
fn skip_next_test() {
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("(foo", ')'), None);
assert_eq!(skip_next("", ')'), None);
assert_eq!(skip_next("фоо", 'ф'), Some("оо"));
}
#[test]
fn take_until_test() {
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("фоо/бар", '/'), ("фоо", "/бар"));
assert_eq!(take_until("фооДбар", 'Д'), ("фоо", "Дбар"));
}
#[test]
fn take_and_skip_test() {
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(("фоо", "бар")));
}
#[test]
fn split_line_test() {
assert_eq!(split_line("foo,bar,zoo", ','), vec!["foo","bar","zoo"]);
assert_eq!(split_line(" foo , bar , zoo ", ','), vec!["foo","bar","zoo"]);
assert_eq!(split_line("foo,,zoo", ','), vec!["foo","","zoo"]);
assert_eq!(split_line(r#""foo","bar","zoo""#, ','), vec![r#""foo""#,r#""bar""#,r#""zoo""#]);
assert_eq!(split_line(r#"" foo ","b,a,r"," z,oo ""#, ','), vec![r#"" foo ""#,r#""b,a,r""#,r#"" z,oo ""#]);
}
#[test]
fn csv_init_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data)).unwrap();
assert_eq!(csv.columns.len(), 3);
assert_eq!(csv.columns[0], "name");
assert_eq!(csv.columns[1], "age");
assert_eq!(csv.columns[2], "birth date");
}
#[test]
fn csv_init_empty_column_name_test() {
let data = r#"
name, , age
"Gen Z. Person", "20", "2000-01-01"
"#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data)).unwrap();
assert_eq!(csv.columns.len(), 3);
assert_eq!(csv.columns[0], "name");
assert_eq!(csv.columns[1], "");
assert_eq!(csv.columns[2], "age");
}
#[test]
fn csv_init_error_empty() {
let data = " ".trim().as_bytes();
assert_match!(Csv::new(BufReader::new(data)).err(), Some(CsvError::InvalidHeader(_)));
}
#[test]
fn csv_init_error_duplicate() {
let data = "name, age, name".trim().as_bytes();
assert_match!(Csv::new(BufReader::new(data)).err(), Some(CsvError::InvalidHeader(_)));
}
#[test]
fn csv_parse_line_test() {
let data = r#"name, age, birth date"#.trim().as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
let mut row = csv.parse_line(r#""Gen Z. Person", "20", "2000-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Gen Z. Person", "20", "2000-01-01"),
};
// empty column
row = csv.parse_line(r#""Someone", "", "1990-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Someone", "", "1990-01-01"),
};
// whitespace
row = csv.parse_line(r#"" Someone ", "20", "1990-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
(" Someone ", "20", "1990-01-01"),
};
// commas in column
row = csv.parse_line(r#""Some,one", "20", "1990-01-01""#).unwrap();
assert_eq! {
(row["name"].as_str(), row["age"].as_str(), row["birth date"].as_str()),
("Some,one", "20", "1990-01-01"),
};
}
#[test]
fn csv_iter_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "", "1990-01-01"
"#.trim().as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|_row| Ok(true));
let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
assert_eq!(names, &["Gen Z. Person", "Douglas Adams", "Ada Lovelace", "Someone"]);
let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
assert_eq!(ages, &["20", "42", "36", ""]);
let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
assert_eq!(birth_dates, &["2000-01-01", "1952-03-11", "1815-12-10", "1990-01-01"]);
}
#[test]
fn csv_iter_filter_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "25", "1990-01-01"
"#.trim().as_bytes();
let mut csv = Csv::new(BufReader::new(data)).unwrap();
csv.apply_selection(|row| { Ok(row["age"].parse::<u32>().unwrap() < 30) });
let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
assert_eq!(names, &["Gen Z. Person", "Someone"]);
let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
assert_eq!(ages, &["20", "25"]);
let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
assert_eq!(birth_dates, &["2000-01-01", "1990-01-01"]);
}
#[test]
fn csv_iter_empty_filter_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "25", "1990-01-01"
"#.trim().as_bytes();
let csv = Csv::new(BufReader::new(data)).unwrap();
let rows: Vec<Row> = csv.map(|row| row.unwrap()).collect();
let names: Vec<&str> = rows.iter().map(|row| row["name"].as_str()).collect();
assert_eq!(names, &["Gen Z. Person", "Douglas Adams", "Ada Lovelace", "Someone"]);
let ages: Vec<&str> = rows.iter().map(|row| row["age"].as_str()).collect();
assert_eq!(ages, &["20", "42", "36", "25"]);
let birth_dates: Vec<&str> = rows.iter().map(|row| row["birth date"].as_str()).collect();
assert_eq!(birth_dates, &["2000-01-01", "1952-03-11", "1815-12-10", "1990-01-01"]);
}
#[test]
fn csv_iter_filter_site_test() {
let reader = BufReader::new(r#"
name, age, birth date
"Douglas Adams", "42", "1952-03-11"
"Gen Z. Person", "20", "2000-01-01"
"Ada Lovelace", "36", "1815-12-10"
"#.trim().as_bytes());
let mut csv = Csv::new(reader).unwrap();
csv.apply_selection(|row| {
let age = row.get("age").ok_or_else(|| CsvError::InvalidColumn(String::from("age")))?;
let age = age.parse::<u32>().map_err(|_| CsvError::ParseError(String::from(age)))?;
Ok(age > 30)
});
let filtered_names = csv.map(|row| row.unwrap()["name"].clone()).collect::<Vec<_>>();
assert_eq!(filtered_names, &["Douglas Adams", "Ada Lovelace"]);
}
#[test]
fn csv_write_site_test() {
let reader = BufReader::new(r#"
name, age ,birth date
"Douglas Adams","42","1952-03-11"
"Gen Z. Person", "20" , "2000-01-01"
"Ada Lovelace","36","1815-12-10"
"#.trim().as_bytes());
let mut csv = Csv::new(reader).unwrap();
csv.apply_selection(|row| {
Ok(row["age"].parse::<u32>().unwrap() < 40)
});
let mut output = Vec::new();
csv.write_to(&mut output).unwrap();
let expected_output = concat!("name, age, birth date", "\n",
r#""Gen Z. Person", "20", "2000-01-01""#, "\n",
r#""Ada Lovelace", "36", "1815-12-10""#, "\n");
assert_eq!(String::from_utf8(output).unwrap(), expected_output);
}
#[test]
fn csv_write_test() {
let data = r#"
name, age, birth date
"Gen Z. Person", "20", "2000-01-01"
"Douglas Adams", "42", "1952-03-11"
"Ada Lovelace", "36", "1815-12-10"
"Someone", "25", "1990-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 expected_output = concat!("name, age, birth date", "\n",
r#""Gen Z. Person", "20", "2000-01-01""#, "\n",
r#""Douglas Adams", "42", "1952-03-11""#, "\n",
r#""Ada Lovelace", "36", "1815-12-10""#, "\n",
r#""Someone", "25", "1990-01-01""#, "\n");
assert_eq!(String::from_utf8(output).unwrap(), expected_output);
}
}