Решение на CSV Filter от Йоана Зелова
Резултати
- 15 точки от тестове
- 0 бонус точки
- 15 точки общо
- 15 успешни тест(а)
- 0 неуспешни тест(а)
Код
pub fn skip_next(input: &str, target: char) -> Option<&str> {
if input.len() == 0 {
None
} else {
if input.chars().nth(0).unwrap() == target {
let mut chars = input.chars();
chars.next();
Some(chars.as_str())
} else {
None
}
}
}
pub fn take_until(input: &str, target: char) -> (&str, &str) {
let id = input.find(target);
match id {
None => (input, ""),
Some(x) => input.split_at(x),
}
}
pub fn take_and_skip(input: &str, target: char) -> Option<(&str, &str)> {
let (before, after) = take_until(input, target);
if after.eq("") {
None
} else {
let skipped = skip_next(after, target);
match skipped {
Some(val) => Some((before, val)),
None => None,
}
}
}
#[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;
pub struct Csv<R: BufRead> {
pub columns: Vec<String>,
reader: R,
selection: Option<Box<dyn Fn(&Row) -> Result<bool, CsvError>>>,
}
use std::io::Write;
impl<R: BufRead> Csv<R> {
pub fn new(mut reader: R) -> Result<Self, CsvError> {
let mut line: String = String::from("");
let result = reader.read_line(&mut line);
match result {
Ok(0) => Err(CsvError::InvalidHeader(String::from("No headers provided"))),
Err(e) => Err(CsvError::IO(e)),
Ok(_) => {
let mut columns: Vec<String> = vec![];
while line.len() != 0 {
let (column_name, rest) = take_until(&line, ',');
let trimmed_column_name = column_name.trim();
if columns.iter().any(|column| column == trimmed_column_name) {
return Err(CsvError::InvalidHeader(String::from(
"Must not have 2 equal headers",
)));
} else {
columns.push(String::from(trimmed_column_name));
let res = skip_next(&rest, ',');
if res.is_none() {
return Ok(Csv {
columns,
reader,
selection: None,
});
} else {
let rest_of_line = res.unwrap();
line = String::from(rest_of_line);
}
}
}
return Ok(Csv {
columns,
reader,
selection: None,
});
}
}
}
pub fn parse_line(&mut self, line: &str) -> Result<Row, CsvError> {
let mut clean = line.trim();
let mut row = Row::new();
let columns_count = self.columns.len();
for i in 0.. {
if clean.len() == 0 {
if columns_count == i {
return Ok(row);
} else {
return Err(CsvError::InvalidRow(String::from("Too little columns")));
}
}
if i >= columns_count {
return Err(CsvError::InvalidRow(String::from("Too many columns")));
}
let rest: &str;
match skip_next(clean, '"') {
None => return Err(CsvError::InvalidRow(String::from("Expected opening \""))),
Some(ch) => rest = ch,
}
let column_name: &str;
let rest_to_parse: &str;
match take_and_skip(rest, '"') {
None => return Err(CsvError::InvalidRow(String::from("Expected closing \""))),
Some((before, after)) => {
column_name = before;
rest_to_parse = after.trim();
}
}
row.insert(self.columns[i].clone(), String::from(column_name));
match skip_next(rest_to_parse, ',') {
None => {
if i == columns_count - 1 {
return Ok(row);
} else {
return Err(CsvError::InvalidRow(String::from(
"Expected comma for separation",
)));
}
}
Some(string) => clean = string.trim(),
}
}
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> {
for i in 0..self.columns.len() {
let mut to_write = self.columns[i].clone();
if i != self.columns.len() - 1 {
to_write = to_write + ", ";
}
let status = writer.write(to_write.as_bytes());
match status {
Err(e) => return Err(CsvError::IO(e)),
_ => (),
}
}
let status = writer.write("\n".as_bytes());
match status {
Err(e) => return Err(CsvError::IO(e)),
_ => (),
}
loop {
let next = self.next();
let row: Row;
match next {
None => return Ok(()),
Some(Err(e)) => return Err(e),
Some(Ok(r)) => row = r,
}
for i in 0..self.columns.len() {
let mut to_write = String::from("\"");
to_write.push_str(&row[&self.columns[i]]);
to_write.push('"');
if i != self.columns.len() - 1 {
to_write.push_str(", ");
}
let status = writer.write(to_write.as_bytes());
match status {
Err(e) => return Err(CsvError::IO(e)),
_ => (),
}
}
let status = writer.write("\n".as_bytes());
match status {
Err(e) => return Err(CsvError::IO(e)),
_ => (),
}
}
}
}
impl<R: BufRead> Iterator for Csv<R> {
type Item = Result<Row, CsvError>;
fn next(&mut self) -> Option<Self::Item> {
loop {
let mut line: String = String::from("");
let res = self.reader.read_line(&mut line);
match res {
Err(e) => return Some(Err(CsvError::IO(e))),
Ok(0) => return None,
_ => (),
}
match self.parse_line(&line) {
Err(e) => return Some(Err(e)),
Ok(row) => match &self.selection {
None => return Some(Ok(row)),
Some(func) => match func(&row) {
Err(e) => return Some(Err(e)),
Ok(true) => return Some(Ok(row)),
Ok(false) => (),
},
},
}
}
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20210111-1538662-1lcx825/solution) Finished test [unoptimized + debuginfo] target(s) in 4.05s 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