use crate::style;
use anyhow::{anyhow, Context, Result};
use chrono::prelude::*;
use std::collections::HashMap;
use std::str::FromStr;
pub struct MemoData {
pub contents: HashMap<u32, Content>,
}
pub struct Content {
pub text: String,
pub date_time: NaiveDateTime,
}
impl MemoData {
pub fn new() -> Self {
MemoData {
contents: HashMap::new(),
}
}
pub fn parse(data: String) -> Result<HashMap<u32, Content>> {
data.lines()
.filter(|line| !line.is_empty())
.map(vaidate_line)
.collect()
}
pub fn get(&self, id: u32) -> Option<&Content> {
self.contents.get(&id)
}
pub fn sorted_ids(&self) -> Vec<u32> {
let mut ids: Vec<u32> = self.contents.keys().cloned().collect();
ids.sort();
ids
}
pub fn group_by_date(&self) -> Result<String> {
let mut result = String::new();
let mut previous_date =
NaiveDate::from_ymd_opt(1, 1, 1).with_context(|| "Error creating date NaiveDate")?;
for id in self.sorted_ids().iter().rev() {
let content = self
.contents
.get(id)
.with_context(|| format!("No item found for id '{}'", id))?;
let currrent_date = content.date_time.date();
let current_time = content.date_time.time();
if previous_date != currrent_date || result.is_empty() {
result.push_str(&format!(
"\n\n{}",
style::str(
&currrent_date.format("%A, %B %e, %Y").to_string(),
style::Options::Title
)
));
}
let id_and_time = format!("{:0>#2}: {}", id, current_time);
result.push_str(&format!(
"\n{} {}",
style::str(&id_and_time, style::Options::Muted),
content.text
));
previous_date = currrent_date;
}
result = result.trim_start().to_string();
Ok(result)
}
}
fn vaidate_line(line: &str) -> Result<(u32, Content)> {
let mut parts = line.splitn(2, ':');
let id = parts
.next()
.ok_or_else(|| anyhow!("Missing id in line"))?
.parse::<u32>()
.with_context(|| format!("Invalid id in line '{}'", line))?;
let content = parts
.next()
.ok_or_else(|| anyhow!("Missing content in line"))?
.trim();
let content = Content::from_str(content)?;
Ok((id, content))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_memo_data_new() {
let d = MemoData::new();
assert_eq!(d.contents.len(), 0);
}
#[test]
fn test_memo_data_parse() {
let data =
"1: 2001-01-01 01:01:01 one\n2: 2002-02-02 02:02:02 two\n3: 2003-03-03 03:03:03 three\n"
.to_string();
let d = MemoData::parse(data).unwrap();
assert_eq!(d.len(), 3);
}
#[test]
fn test_memo_data_get() {
let mut d = MemoData::new();
d.contents.insert(
1,
Content {
text: "one".to_string(),
date_time: NaiveDate::from_ymd_opt(2001, 1, 1)
.expect("Error creating date NaiveDate")
.and_hms_opt(1, 1, 1)
.expect("Error creating time NaiveDateTime"),
},
);
assert_eq!(d.get(1).unwrap().text, "one");
assert!(d.get(2).is_none());
}
#[test]
fn test_validate_line() {
let line = "1: 2001-01-01 01:01:01 one";
let (id, content) = vaidate_line(line).unwrap();
assert_eq!(id, 1);
assert_eq!(content.text, "one");
assert_eq!(
content.date_time.format("%Y-%m-%d %H:%M:%S").to_string(),
"2001-01-01 01:01:01"
);
}
#[test]
fn test_validate_line_invalid() {
let line_invalid_id = "a: 2001-01-01 01:01:01 one";
let line_missing_id = ": 2001-01-01 01:01:01 one";
let line_missing_content = "1: 2001-01-01 01:01:01";
let line_invalid_date = "1: 2001-01-01-01 01:01:01 one";
assert!(vaidate_line(line_invalid_id).is_err());
assert!(vaidate_line(line_missing_id).is_err());
assert!(vaidate_line(line_missing_content).is_err());
assert!(vaidate_line(line_invalid_date).is_err());
}
}