use crate::app;
use crate::data::{read_file, DataFile, DisplayMode};
use crate::models::{Content, MemoData};
use anyhow::{anyhow, Context, Result};
use chrono::prelude::*;
use std::fmt;
pub const DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
impl std::str::FromStr for Content {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let parts: Vec<_> = s.trim().splitn(3, ' ').collect();
if parts.len() != 3 {
return Err(anyhow!(
"Invalid content '{}'.\nExpected: format: %Y-%m-%d %H:%M:%S content",
s
));
}
let date_time = format!("{} {}", parts[0], parts[1]);
let date_time = NaiveDateTime::parse_from_str(&date_time, DATE_TIME_FORMAT)
.with_context(|| format!("invalid date time '{}'", date_time))?;
let content = parts[2];
Ok(Content {
text: content.to_string(),
date_time,
})
}
}
impl fmt::Display for Content {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {}",
self.date_time.format(DATE_TIME_FORMAT),
self.text
)
}
}
impl Default for MemoData {
fn default() -> Self {
Self::new()
}
}
impl DataFile for MemoData {
fn load(&mut self, cli_app: &app::AppConfig) -> Result<()> {
let data = read_file(&cli_app.data_file_path())?;
self.contents = MemoData::parse(data)?;
Ok(())
}
fn sorted_ids(&self) -> Vec<u32> {
self.sorted_ids()
}
fn add(&mut self, id: u32, name: &str) -> Result<()> {
if self.contents.contains_key(&id) {
return Err(anyhow!("Id '{}' already exists", id));
}
let date_time = Local::now().naive_local();
self.contents.insert(
id,
Content {
text: name.to_string(),
date_time,
},
);
Ok(())
}
fn remove(&mut self, id: u32) -> Result<()> {
if !self.contents.contains_key(&id) {
return Err(anyhow!("Id '{}' not found", id));
}
self.contents.remove(&id);
Ok(())
}
fn display(&self, mode: DisplayMode) -> Result<()> {
match mode {
DisplayMode::Sorted => println!("{}", self),
DisplayMode::GroupByDate => println!("{}", self.group_by_date()?),
}
Ok(())
}
}
impl fmt::Display for MemoData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for id in self.sorted_ids() {
writeln!(f, "{}: {}", id, self.contents[&id])?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use std::io::Write;
use std::str::FromStr;
use tempfile::tempdir;
#[test]
fn test_memo_data_add() {
let mut d = MemoData::new();
assert_eq!(d.add(1, "one").is_ok(), true);
assert_eq!(d.contents.len(), 1);
}
#[test]
fn test_memo_data_load() {
let mut app_config = app::AppConfig::new("memo", "memo.txt");
let dir = tempdir().unwrap();
let mut data_dir = dir.path().to_path_buf();
data_dir.push(app_config.name());
fs::create_dir_all(&data_dir).unwrap();
let mut file_dir = data_dir.clone();
file_dir.push(app_config.data_file());
let mut file = fs::File::create(&file_dir).unwrap();
writeln!(file, "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").unwrap();
app_config.data_dir = data_dir.clone();
let mut d = MemoData::new();
assert_eq!(d.load(&app_config).is_ok(), true);
}
#[test]
fn test_memo_data_sorted_ids() {
let mut d = MemoData::new();
assert_eq!(d.add(2, "two").is_ok(), true);
assert_eq!(d.add(1, "one").is_ok(), true);
assert_eq!(d.add(3, "three").is_ok(), true);
assert_eq!(d.sorted_ids(), vec![1, 2, 3]);
}
#[test]
fn test_memo_data_remove() {
let mut d = MemoData::new();
assert_eq!(d.add(1, "one").is_ok(), true);
assert_eq!(d.remove(1).is_ok(), true);
assert_eq!(d.contents.len(), 0);
}
#[test]
fn test_memo_data_get() {
let mut d = MemoData::new();
assert_eq!(d.add(1, "one").is_ok(), true);
assert_eq!(d.get(1).expect("Id should exist").text, "one");
}
#[test]
fn test_memo_data_display() {
let mut d = MemoData::new();
let data_str = "1: 2021-01-01 01:01:01 one\n2: 2021-01-01 01:01:01 two\n3: 2021-01-01 01:01:01 three\n";
d.contents = MemoData::parse(data_str.to_string()).expect("Error parsing data");
assert_eq!(format!("{}", d), data_str);
}
#[test]
fn test_content_from_str() {
let content = "2021-01-01 01:01:01 one";
let date_time = "2021-01-01 01:01:01";
let c = Content::from_str(content).expect("Error creating Content");
assert_eq!(c.text, "one");
assert_eq!(c.date_time.format(DATE_TIME_FORMAT).to_string(), date_time);
}
#[test]
fn test_content_from_str_spaces() {
let content = " 2021-01-01 01:01:01 one two three ";
let date_time = "2021-01-01 01:01:01";
let c = Content::from_str(content).expect("Error creating Content");
assert_eq!(c.text, "one two three");
assert_eq!(c.date_time.format(DATE_TIME_FORMAT).to_string(), date_time);
}
#[test]
fn test_content_display() {
let content = "2021-01-01 01:01:01 one";
let c = Content::from_str(content).expect("Error creating Content");
assert_eq!(format!("{}", c), content);
}
}