Решение на CSV Filter от Даниел Стоянов

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

Към профила на Даниел Стоянов

Резултати

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

Код

use std::io::Error;
use std::fmt::Debug;
use std::fmt;
/*
pub fn skip_next(input: &str, target: char) -> Option<&str> {
let len = input.len();
for (i, c) in input.chars().enumerate() {
if c == target {
let res = &input[(i+1)..len];
return Some(res);
}
}
None
} */
pub fn skip_next(input: &str, target: char) -> Option<&str> { // works with UTF-8 chars
let mut char_indices = input.char_indices();
let mut temp = char_indices.next();
let len = input.len();
while temp != None {
if temp.unwrap().1 == target {
let res = &input[temp.unwrap().0 + temp.unwrap().1.len_utf8() .. len];
return Some(res);
}
temp = char_indices.next();
}
None
}
/*
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let empty = "";
let len = input.len();
for (i, c) in input.chars().enumerate() {
if c == target {
let first = &input[0..i];
let second = &input[i..len];
return (first, second);
}
}
(input, empty)
}*/
pub fn take_until(input: &str, target: char) -> (&str, &str) { // works with UTF-8 chars
let empty = "";
let mut char_indices = input.char_indices();
let mut temp = char_indices.next();
while temp != None {
if temp.unwrap().1 == target {
let (first, second) = input.split_at(temp.unwrap().0);
return (first, second);
}
temp = char_indices.next();
}
(input, empty)
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let res = take_until(input, target);
match res.1 {
"" => None,
_ => Some((res.0, skip_next(res.1, target).unwrap()))
}
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
impl fmt::Display for CsvError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CsvError::IO(E) => write!(f, "-->ERROR-MESSAGE|IO: {}", E),
CsvError::ParseError(text) => write!(f, "-->ERROR-MESSAGE|PARSE-ERROR: {}", text),
CsvError::InvalidHeader(text) => write!(f, "-->ERROR-MESSAGE|INVALID-HEADER: {}", text),
CsvError::InvalidRow(text) => write!(f, "-->ERROR-MESSAGE|INVALID-ROW: {}", text),
CsvError::InvalidColumn(text) => write!(f, "-->ERROR-MESSAGE|INVALID-COLUMN: {}",text),
}
}
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
use std::io::BufRead;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
fn remove_quotes(data: &str) -> &str {
let res = data.trim();
&res[1.. res.len()-1]
}
fn contains_dup(buffer: Vec<String>) -> bool {
let mut res: Vec<&str> = vec![];
for el in &buffer {
if res.contains(&el.trim()) == true {
return true;
}
res.push(&el.trim());
}
return false;
}
fn convert_header(header: &str) -> Option<Vec<String>> {
let mut words = vec![];
let mut pair: Option<(&str, &str)> = take_and_skip(header, ',');
let mut last = "";
while pair != None {
words.push(pair.unwrap().0.trim().to_string());
if take_and_skip(pair.unwrap().1, ',') == None {
last = pair.unwrap().1;
}
pair = take_and_skip(pair.unwrap().1, ',');
}
if words.len() == 0 {
words.push(header.trim().to_string());
}
else {
words.push(last.trim().to_string());
}
match words.len() {
0 => return None,
_ => return Some(words),
}
}
fn in_quotes(argument: &str) -> bool {
let chars: Vec<char> = argument.trim().chars().collect();
if chars.len() < 2 {
return false
}
chars[0] == '\"' && chars[chars.len()-1] == '\"'
}
use std::io::Write;
use std::io::{self, BufReader};
use std::io::prelude::*;
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
//let mut source = BufReader::new(reader);
let mut buf = String::new();
match reader.read_line(&mut buf) {
Ok(0) => return Err(CsvError::InvalidHeader(String::from("Empty header!"))),
Err(E) => return Err(CsvError::IO(E)),
_ => {
let first_line = take_until(&buf, '\n');
let header = convert_header(&first_line.0).unwrap();
match contains_dup(header.clone()) {
true => return Err(CsvError::InvalidHeader(String::from("Contains duplicated values!"))),
_ => return Ok(Csv {columns: header, reader: reader, selection: None}),
}
}
}
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let len = self.columns.len();
let mut temp = line.trim();
let mut row = Row::new();
for i in 0..len {
let pair = take_and_skip(temp, ',');
match pair {
None => {
if i != len-1 {
return Err(CsvError::InvalidRow(String::from("-->ERROR-MESSAGE|INVALID-ROW: Keys more than arguments!")));
}
else {
if in_quotes(&temp) == true {
row.insert(String::from(self.columns[i].clone()), String::from(remove_quotes(&temp)));
return Ok(row);
}
else {
return Err(CsvError::InvalidRow(String::from("-->ERROR-MESSAGE|INVALID-ROW: Argument not starting or ending with '\"' !")));
}
}
},
Some((current, next)) => {
if in_quotes(current) == true {
if i != len-1 {
row.insert(String::from(self.columns[i].clone()), String::from(remove_quotes(&current)));
temp = next;
}
else {
return Err(CsvError::InvalidRow(String::from("-->ERROR-MESSAGE|INVALID-ROW: Arguments more than keys!")));
}
}
else {
return Err(CsvError::ParseError(String::from("Invalid row! Argument not starting or ending with '\"' !")));
}
},
}
}
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> {
let len = self.columns.len();
// print header:
for i in 0 .. len {
if i != len - 1 {
match write!(&mut writer, "{}, ", self.columns[i]) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
else {
match write!(&mut writer, "{}\n", self.columns[i]) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
}
// print rows
let mut buf = String::new();
let mut line = self.reader.read_line(&mut buf);
loop {
match line {
Ok(0) => return Ok(()),
Err(E) => return Err(CsvError::IO(E)),
_ => {
let row = self.parse_line(&buf);
match row {
Err(E) => return Err(CsvError::ParseError(String::from(""))),
Ok(_) => {
let mut i = 0;
for val in self.columns.clone() {
i = i + 1;
if i != self.columns.len() {
match write!(&mut writer, "\"{}\", ", self.parse_line(&buf).unwrap()[&val].as_str()) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
else {
match write!(&mut writer, "\"{}\"\n", self.parse_line(&buf).unwrap()[&val].as_str()) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
}
buf.clear();
line = self.reader.read_line(&mut buf);
}
};
}
}
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut buf = String::new();
match self.reader.read_line(&mut buf) {
Err(E) => return Some(Err(CsvError::IO(E))),
Ok(0) => return None,
_ => {
let row = self.parse_line(&buf);
match row {
Err(E) => return Some(Err(E)),
Ok(_) => {
match self.selection {
None => return Some(Ok(self.parse_line(&buf).unwrap())),
_ => {
match self.selection.as_ref().unwrap()(&row.unwrap()) {
Err(E) => return Some(Err(E)),
Ok(false) => return self.next(),
Ok(true) => return Some(Ok(self.parse_line(&buf).unwrap())),
}
}
}
},
}
},
}
}
}
// --------------------------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
fn test_skip_next() {
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("foo", '('), None);
assert_eq!(skip_next("", '('), None);
assert_eq!(skip_next("", ' '), None);
assert_eq!(skip_next("алаБбала", 'Б'), Some("бала"));
}
#[test]
fn test_take_until() {
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("", '/'), ("", ""));
assert_eq!(take_until("аз обичамЙхляб и боза", 'Й'), ("аз обичам", "Йхляб и боза"));
}
#[test]
fn test_take_and_skip() {
assert_eq!(take_and_skip(" foo/bar ", '/'), Some((" foo", "bar ")));
assert_eq!(take_and_skip("foobar", '/'), None);
assert_eq!(take_and_skip("first, second", ','), Some(("first", " second")));
assert_eq!(take_and_skip("first,second", ','), Some(("first", "second")));
//assert_eq!(take_and_skip("СФАСЛДААА", 'Л'), Some(("СФАС", "ДААА")));
}
use std::io::{self, Read, BufRead, BufReader};
// За тестване че някакъв резултат пасва на някакъв pattern:
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));
}
}
}
// За тестване на IO грешки:
struct ErroringReader {}
impl Read for ErroringReader {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::Error::new(io::ErrorKind::Other, "read error!"))
}
}
impl BufRead for ErroringReader {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
Err(io::Error::new(io::ErrorKind::Other, "fill_buf error!"))
}
fn consume(&mut self, _amt: usize) { }
}
#[test]
fn test_string_parsing() {
assert_eq!(skip_next("[test]", '['), Some("test]"));
assert_eq!(take_until("one/two", '/'), ("one", "/two"));
assert_eq!(take_and_skip("one/two", '/'), Some(("one", "two")));
}
#[test]
fn test_csv_error() {
assert_match!(Csv::new(ErroringReader {}).err(), Some(CsvError::IO(_)));
}
#[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));
// Парсене на един ред:
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"),
};
// Употреба като итератор:
let filtered_names = csv.map(|row| row.unwrap()["name"].clone()).collect::<Vec<_>>();
assert_eq!(filtered_names, &["Gen Z. Person"]);
// Писане в някакъв изход
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-2y5emy/solution)
warning: unused import: `std::io::Error`
 --> src/lib.rs:1:5
  |
1 | use std::io::Error;
  |     ^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

warning: unused imports: `BufReader`, `self`
   --> src/lib.rs:144:15
    |
144 | use std::io::{self, BufReader};
    |               ^^^^  ^^^^^^^^^

warning: unused import: `std::io::prelude::*`
   --> src/lib.rs:145:5
    |
145 | use std::io::prelude::*;
    |     ^^^^^^^^^^^^^^^^^^^

warning: unreachable expression
   --> src/lib.rs:263:9
    |
231 | /         loop {
232 | |             match line {
233 | |                Ok(0) => return Ok(()),
234 | |                Err(E)  => return Err(CsvError::IO(E)),
...   |
261 | |             }
262 | |         }
    | |_________- any code following this expression is unreachable
263 |           Ok(())
    |           ^^^^^^ unreachable expression
    |
    = note: `#[warn(unreachable_code)]` on by default

warning: unused variable: `E`
   --> src/lib.rs:238:28
    |
238 |                        Err(E) => return Err(CsvError::ParseError(String::from(""))),
    |                            ^ help: if this is intentional, prefix it with an underscore: `_E`
    |
    = note: `#[warn(unused_variables)]` on by default

warning: variable `E` should have a snake case name
  --> src/lib.rs:75:26
   |
75 |             CsvError::IO(E) => write!(f, "-->ERROR-MESSAGE|IO: {}", E),
   |                          ^ help: convert the identifier to snake case: `e`
   |
   = note: `#[warn(non_snake_case)]` on by default

warning: variable `E` should have a snake case name
   --> src/lib.rs:153:17
    |
153 |             Err(E)  => return Err(CsvError::IO(E)),
    |                 ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:216:25
    |
216 |                     Err(E) => return Err(CsvError::IO(E)),
    |                         ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:222:25
    |
222 |                     Err(E) => return Err(CsvError::IO(E)),
    |                         ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:234:20
    |
234 |                Err(E)  => return Err(CsvError::IO(E)),
    |                    ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:238:28
    |
238 |                        Err(E) => return Err(CsvError::ParseError(String::from(""))),
    |                            ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:246:41
    |
246 | ...                   Err(E) => return Err(CsvError::IO(E)),
    |                           ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:252:41
    |
252 | ...                   Err(E) => return Err(CsvError::IO(E)),
    |                           ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:272:17
    |
272 |             Err(E) => return Some(Err(CsvError::IO(E))),
    |                 ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:277:25
    |
277 |                     Err(E) => return Some(Err(E)),
    |                         ^ help: convert the identifier to snake case: `e`

warning: variable `E` should have a snake case name
   --> src/lib.rs:283:41
    |
283 | ...                   Err(E) => return Some(Err(E)),
    |                           ^ help: convert the identifier to snake case: `e`

warning: 16 warnings emitted

    Finished test [unoptimized + debuginfo] target(s) in 5.35s
     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 ... FAILED
test solution_test::test_csv_selection_and_writing ... FAILED
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

failures:

---- solution_test::test_csv_parse_line_with_commas stdout ----
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ParseError("Invalid row! Argument not starting or ending with \'\"\' !")', tests/solution_test.rs:162:56
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

---- solution_test::test_csv_selection_and_writing stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `["name, age, birth date", "\"Douglas Adams\", \"42\", \"1952-03-11\"", "\"Gen Z. Person\", \"20\", \"2000-01-01\"", "\"Ada Lovelace\", \"36\", \"1815-12-10\""]`,
 right: `["name, age, birth date", "\"Gen Z. Person\", \"20\", \"2000-01-01\""]`', tests/solution_test.rs:269:9
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `["name, age, birth date", "\"Douglas Adams\", \"42\", \"1952-03-11\"", "\"Gen Z. Person\", \"20\", \"2000-01-01\"", "\"Ada Lovelace\", \"36\", \"1815-12-10\""]`,
 right: `["name, age, birth date", "\"Gen Z. Person\", \"20\", \"2000-01-01\""]`', tests/solution_test.rs:251:5


failures:
    solution_test::test_csv_parse_line_with_commas
    solution_test::test_csv_selection_and_writing

test result: FAILED. 13 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--test solution_test'

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

Даниел качи първо решение на 07.01.2021 17:13 (преди 7 месеца)

Даниел качи решение на 11.01.2021 12:50 (преди 6 месеца)

use std::io::Error;
use std::fmt::Debug;
use std::fmt;
/*
pub fn skip_next(input: &str, target: char) -> Option<&str> {
let len = input.len();
for (i, c) in input.chars().enumerate() {
if c == target {
let res = &input[(i+1)..len];
return Some(res);
}
}
None
} */
pub fn skip_next(input: &str, target: char) -> Option<&str> { // works with UTF-8 chars
let mut char_indices = input.char_indices();
let mut temp = char_indices.next();
let len = input.len();
while temp != None {
if temp.unwrap().1 == target {
let res = &input[temp.unwrap().0 + temp.unwrap().1.len_utf8() .. len];
return Some(res);
}
temp = char_indices.next();
}
None
}
/*
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let empty = "";
let len = input.len();
for (i, c) in input.chars().enumerate() {
if c == target {
let first = &input[0..i];
let second = &input[i..len];
return (first, second);
}
}
(input, empty)
}*/
pub fn take_until(input: &str, target: char) -> (&str, &str) { // works with UTF-8 chars
let empty = "";
let mut char_indices = input.char_indices();
let mut temp = char_indices.next();
while temp != None {
if temp.unwrap().1 == target {
let (first, second) = input.split_at(temp.unwrap().0);
return (first, second);
}
temp = char_indices.next();
}
(input, empty)
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let res = take_until(input, target);
match res.1 {
"" => None,
_ => Some((res.0, skip_next(res.1, target).unwrap()))
}
}
#[derive(Debug)]
pub enum CsvError {
IO(std::io::Error),
ParseError(String),
InvalidHeader(String),
InvalidRow(String),
InvalidColumn(String),
}
impl fmt::Display for CsvError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
CsvError::IO(E) => write!(f, "-->ERROR-MESSAGE|IO: {}", E),
CsvError::ParseError(text) => write!(f, "-->ERROR-MESSAGE|PARSE-ERROR: {}", text),
CsvError::InvalidHeader(text) => write!(f, "-->ERROR-MESSAGE|INVALID-HEADER: {}", text),
CsvError::InvalidRow(text) => write!(f, "-->ERROR-MESSAGE|INVALID-ROW: {}", text),
CsvError::InvalidColumn(text) => write!(f, "-->ERROR-MESSAGE|INVALID-COLUMN: {}",text),
}
}
}
use std::collections::HashMap;
type Row = HashMap<String, String>;
use std::io::BufRead;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
fn remove_quotes(data: &str) -> &str {
let res = data.trim();
&res[1.. res.len()-1]
}
fn contains_dup(buffer: Vec<String>) -> bool {
let mut res: Vec<&str> = vec![];
for el in &buffer {
if res.contains(&el.trim()) == true {
return true;
}
res.push(&el.trim());
}
return false;
}
fn convert_header(header: &str) -> Option<Vec<String>> {
let mut words = vec![];
let mut pair: Option<(&str, &str)> = take_and_skip(header, ',');
let mut last = "";
while pair != None {
words.push(pair.unwrap().0.trim().to_string());
if take_and_skip(pair.unwrap().1, ',') == None {
last = pair.unwrap().1;
}
pair = take_and_skip(pair.unwrap().1, ',');
}
if words.len() == 0 {
words.push(header.trim().to_string());
}
else {
words.push(last.trim().to_string());
}
match words.len() {
0 => return None,
_ => return Some(words),
}
}
fn in_quotes(argument: &str) -> bool {
let chars: Vec<char> = argument.trim().chars().collect();
if chars.len() < 2 {
return false
}
chars[0] == '\"' && chars[chars.len()-1] == '\"'
}
use std::io::Write;
use std::io::{self, BufReader};
use std::io::prelude::*;
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
//let mut source = BufReader::new(reader);
let mut buf = String::new();
match reader.read_line(&mut buf) {
Ok(0) => return Err(CsvError::InvalidHeader(String::from("Empty header!"))),
Err(E) => return Err(CsvError::IO(E)),
_ => {
let first_line = take_until(&buf, '\n');
let header = convert_header(&first_line.0).unwrap();
match contains_dup(header.clone()) {
true => return Err(CsvError::InvalidHeader(String::from("Contains duplicated values!"))),
_ => return Ok(Csv {columns: header, reader: reader, selection: None}),
}
}
}
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let len = self.columns.len();
let mut temp = line.trim();
let mut row = Row::new();
for i in 0..len {
let pair = take_and_skip(temp, ',');
match pair {
None => {
if i != len-1 {
return Err(CsvError::InvalidRow(String::from("-->ERROR-MESSAGE|INVALID-ROW: Keys more than arguments!")));
}
else {
if in_quotes(&temp) == true {
row.insert(String::from(self.columns[i].clone()), String::from(remove_quotes(&temp)));
return Ok(row);
}
else {
return Err(CsvError::InvalidRow(String::from("-->ERROR-MESSAGE|INVALID-ROW: Argument not starting or ending with '\"' !")));
}
}
},
Some((current, next)) => {
if in_quotes(current) == true {
if i != len-1 {
row.insert(String::from(self.columns[i].clone()), String::from(remove_quotes(&current)));
temp = next;
}
else {
return Err(CsvError::InvalidRow(String::from("-->ERROR-MESSAGE|INVALID-ROW: Arguments more than keys!")));
}
}
else {
return Err(CsvError::ParseError(String::from("Invalid row! Argument not starting or ending with '\"' !")));
}
},
}
}
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> {
let len = self.columns.len();
// print header:
for i in 0 .. len {
if i != len - 1 {
match write!(&mut writer, "{}, ", self.columns[i]) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
else {
match write!(&mut writer, "{}\n", self.columns[i]) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
}
// print rows
let mut buf = String::new();
let mut line = self.reader.read_line(&mut buf);
loop {
match line {
Ok(0) => return Ok(()),
Err(E) => return Err(CsvError::IO(E)),
_ => {
let row = self.parse_line(&buf);
match row {
Err(E) => return Err(CsvError::ParseError(String::from(""))),
Ok(_) => {
let mut i = 0;
for val in self.columns.clone() {
i = i + 1;
if i != self.columns.len() {
match write!(&mut writer, "\"{}\", ", self.parse_line(&buf).unwrap()[&val].as_str()) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
else {
match write!(&mut writer, "\"{}\"\n", self.parse_line(&buf).unwrap()[&val].as_str()) {
Ok(_) => (),
Err(E) => return Err(CsvError::IO(E)),
};
}
}
buf.clear();
line = self.reader.read_line(&mut buf);
}
};
}
}
}
Ok(())
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
let mut buf = String::new();
match self.reader.read_line(&mut buf) {
Err(E) => return Some(Err(CsvError::IO(E))),
Ok(0) => return None,
_ => {
let row = self.parse_line(&buf);
match row {
Err(E) => return Some(Err(E)),
Ok(_) => {
- match self.selection.as_ref().unwrap()(&row.unwrap()) {
- Err(E) => return Some(Err(E)),
- Ok(false) => return self.next(),
- Ok(true) => return Some(Ok(self.parse_line(&buf).unwrap())),
- }
+ match self.selection {
+ None => return Some(Ok(self.parse_line(&buf).unwrap())),
+ _ => {
+ match self.selection.as_ref().unwrap()(&row.unwrap()) {
+ Err(E) => return Some(Err(E)),
+ Ok(false) => return self.next(),
+ Ok(true) => return Some(Ok(self.parse_line(&buf).unwrap())),
+ }
+ }
+ }
},
}
},
}
}
}
// --------------------------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
#[test]
fn test_skip_next() {
assert_eq!(skip_next("(foo", '('), Some("foo"));
assert_eq!(skip_next("foo", '('), None);
assert_eq!(skip_next("", '('), None);
assert_eq!(skip_next("", ' '), None);
assert_eq!(skip_next("алаБбала", 'Б'), Some("бала"));
}
#[test]
fn test_take_until() {
assert_eq!(take_until(" foo/bar ", '/'), (" foo", "/bar "));
assert_eq!(take_until("foobar", '/'), ("foobar", ""));
assert_eq!(take_until("", '/'), ("", ""));
assert_eq!(take_until("аз обичамЙхляб и боза", 'Й'), ("аз обичам", "Йхляб и боза"));
}
#[test]
fn test_take_and_skip() {
assert_eq!(take_and_skip(" foo/bar ", '/'), Some((" foo", "bar ")));
assert_eq!(take_and_skip("foobar", '/'), None);
assert_eq!(take_and_skip("first, second", ','), Some(("first", " second")));
assert_eq!(take_and_skip("first,second", ','), Some(("first", "second")));
//assert_eq!(take_and_skip("СФАСЛДААА", 'Л'), Some(("СФАС", "ДААА")));
}
use std::io::{self, Read, BufRead, BufReader};
// За тестване че някакъв резултат пасва на някакъв pattern:
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));
}
}
}
// За тестване на IO грешки:
struct ErroringReader {}
impl Read for ErroringReader {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(io::Error::new(io::ErrorKind::Other, "read error!"))
}
}
impl BufRead for ErroringReader {
fn fill_buf(&mut self) -> io::Result<&[u8]> {
Err(io::Error::new(io::ErrorKind::Other, "fill_buf error!"))
}
fn consume(&mut self, _amt: usize) { }
}
#[test]
fn test_string_parsing() {
assert_eq!(skip_next("[test]", '['), Some("test]"));
assert_eq!(take_until("one/two", '/'), ("one", "/two"));
assert_eq!(take_and_skip("one/two", '/'), Some(("one", "two")));
}
#[test]
fn test_csv_error() {
assert_match!(Csv::new(ErroringReader {}).err(), Some(CsvError::IO(_)));
}
#[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));
// Парсене на един ред:
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"),
};
// Употреба като итератор:
let filtered_names = csv.map(|row| row.unwrap()["name"].clone()).collect::<Vec<_>>();
assert_eq!(filtered_names, &["Gen Z. Person"]);
// Писане в някакъв изход
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\"",
]);
}
-}
+}
+