use crate::err;
use crate::task::Task;
use crate::util::Result;
use itertools::Itertools;
use std::error;
use std::fmt;
use std::str;
pub struct SectionIterator<'a> {
lines: Vec<&'a str>,
index: usize,
}
impl<'a> SectionIterator<'a> {
pub fn new(s: &'a str) -> Self {
SectionIterator {
lines: s.lines().collect(),
index: 0,
}
}
}
impl<'a> Iterator for SectionIterator<'a> {
type Item = Result<Section>;
fn next(&mut self) -> Option<Self::Item> {
let mut section: Vec<&'a str> = Vec::new();
if self.index >= self.lines.len() {
None
} else {
while self.index < self.lines.len() && !is_section_start(self.lines[self.index]) {
self.index += 1;
}
if self.index >= self.lines.len() {
return None;
}
let start = self.lines[self.index];
section.push(start);
self.index += 1;
while self.index < self.lines.len() && !is_section_end(self.lines[self.index]) {
section.push(self.lines[self.index]);
self.index += 1;
}
Some(section.join("\n").parse())
}
}
}
fn is_section_start(line: &str) -> bool {
if line.trim().is_empty() {
return false;
}
let first_char = line.chars().next();
match first_char {
Some('-') => true, Some('[') => false,
None => false,
_ => true,
}
}
fn is_section_end(line: &str) -> bool {
if line.trim().is_empty() {
return false;
}
let first_char = line.chars().next();
match first_char {
Some('-') => false,
Some('[') => false,
None => false,
_ => true,
}
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct Section {
pub name: String,
pub tasks: Vec<Task>,
}
impl Section {
pub fn new(name: &str) -> Section {
Section {
name: name.to_string(),
tasks: Vec::<Task>::new(),
}
}
}
impl str::FromStr for Section {
type Err = Box<dyn error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self> {
let text = s.trim().to_string();
let mut lines = text.lines();
let first_line = lines
.next()
.expect("Unable to read section name")
.trim()
.to_string();
let mut tasks: Vec<Task> = Vec::new();
let first_char = first_line.chars().next();
let name = match first_char {
Some('-') => {
tasks.push(first_line.parse().expect("Unable to parse task"));
"".to_string()
}
_ => first_line,
};
for line in lines {
let task: Task = match line.parse() {
Ok(t) => t,
Err(e) => return err!("Unable to parse section {name}: {e}"),
};
tasks.push(task);
}
Ok(Section { name, tasks })
}
}
impl fmt::Display for Section {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let name = self.name.clone();
let tasks = self.tasks.iter().join("\n");
write!(f, "{name}\n{tasks}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::indoc;
#[test]
fn parse_section() {
let section_text = indoc! {"
Some Section
- task 1
- task 3
- task 2
"};
let actual: Section = section_text.parse().expect("Unable to parse section");
let expected = Section {
name: "Some Section".to_string(),
tasks: vec![
Task {
text: "task 1".to_string(),
},
Task {
text: "task 3".to_string(),
},
Task {
text: "task 2".to_string(),
},
],
};
assert_eq!(actual, expected);
}
#[test]
fn err_parse_section() {
let section_text = indoc! {"
Some Section
- task 1
- task 3
whoops other section
- task 2
"};
let name = "Some Section";
let line = "whoops other section";
let expected_error =
format!("Unable to parse section {name}: Unable to parse task: {line}");
let actual: Result<Section> = section_text.parse();
match actual {
Err(actual_error) => assert_eq!(actual_error.to_string(), expected_error),
Ok(_) => panic!("Expected {expected_error}, got Ok({})", actual.unwrap()),
}
}
}