use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use std::time::SystemTime; use comrak; use glob::glob; use inflector::Inflector; use rocket::serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(crate = "rocket::serde")] /// Structure for representing a wish note pub struct Note { /// The ID of the note (unique string) pub id: String, /// The index of the note (unique number) pub index: usize, /// The raw note data pub data: String, /// The time the note was last modified pub mtime: SystemTime, /// The name of the note, i.e. the person it is for pub name: String, /// The path to the note file path: PathBuf, } impl Note { pub fn to_html(&self) -> String { let mut options = comrak::ComrakOptions::default(); options.extension.strikethrough = true; options.extension.tagfilter = true; options.extension.autolink = true; options.extension.tasklist = true; comrak::markdown_to_html(&self.data, &options) } pub fn update_data(&mut self, data: &String) { let mut file = File::create(&self.path).expect(&format!( "Cannot open note file: {}", self.path.to_str().unwrap() )); file.write_all(data.as_bytes()).expect(&format!( "Cannot write note file: {}", self.path.to_str().unwrap() )); self.data = data.clone(); let metadata = file.metadata().unwrap(); self.mtime = metadata.modified().unwrap(); } pub fn load_all(note_path: Option<&str>) -> Vec { let mut notes: Vec = vec![]; let mut index = 0; let path_glob = match note_path { Some(dir) => format!("{}/notes/*.note", dir), None => format!("notes/*.note"), }; for entry in glob(path_glob.as_str()).unwrap().filter_map(Result::ok) { let file_name = entry.file_name().unwrap().to_str().unwrap(); let name = match file_name.find('.') { Some(index) => &file_name[0..index], None => "unknown", }; let mut data = String::new(); let mut file = File::open(&entry).expect(&format!("Cannot open note file: {}", file_name)); file.read_to_string(&mut data) .expect(&format!("Cannot read note file: {}", file_name)); let metadata = file .metadata() .expect(&format!("Cannot get metadata of note file: {}", file_name)); let note = Note { id: String::from(name), index: index, data: data, mtime: metadata.modified().unwrap(), name: String::from(name).to_title_case(), path: entry.clone(), }; notes.push(note); index += 1; } notes } } #[cfg(test)] mod tests { use super::*; #[test] fn loads_all_notes() { let notes = Note::load_all(Some("test")); let note_ids: Vec<&str> = notes.iter().map(|note| note.id.as_ref()).collect(); assert_eq!(note_ids, vec!["test", "updatable"]); } #[test] fn converts_to_html() { let notes = Note::load_all(Some("test")); let note = notes.iter().find(|note| note.id == "test").unwrap(); assert_eq!( note.to_html(), r#"

This is a test note

"# ); } #[test] fn updates_data() { let mut notes = Note::load_all(Some("test")); let note = notes .iter_mut() .find(|note| note.id == "updatable") .unwrap(); assert_eq!(note.data, "Some content"); // Update the data van verify it has changed let new_data = "New content"; note.update_data(&String::from(new_data)); assert_eq!(note.data, new_data); // Verify that the data is written to the file of the note by // loading them again let mut notes = Note::load_all(Some("test")); let note = notes .iter_mut() .find(|note| note.id == "updatable") .unwrap(); assert_eq!(note.data, new_data); // ... and change it back again note.update_data(&String::from("Some content")); } }