use comrak; use glob::glob; use inflector::Inflector; use std::fs::File; use std::io::prelude::*; use std::path::PathBuf; use std::time::SystemTime; #[derive(Clone, Debug, Serialize, Deserialize)] /// Structure for representing a wish list pub struct List { /// The ID of the list (unique string) pub id: String, /// The index of the list (unique number) pub index: i8, /// The raw list data pub data: String, /// The time the list was last modified pub mtime: SystemTime, /// The name of the list, i.e. the person it is for pub name: String, /// The path to the list file path: PathBuf } impl List { pub fn to_html(&self) -> String { let mut options = comrak::ComrakOptions::default(); options.ext_strikethrough = true; options.ext_tagfilter = true; options.ext_autolink = true; options.ext_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 list file {}", self.path.to_str().unwrap())); file.write_all(data.as_bytes()) .expect(&format!("Cannot write list file {}", self.path.to_str().unwrap())); self.data = data.clone(); let metadata = file.metadata().unwrap(); self.mtime = metadata.modified().unwrap(); } pub fn load_all(list_path: Option<&str>) -> Vec { let mut lists : Vec = vec![]; let mut index = 0; let path_glob = match list_path { Some(dir) => format!("{}/lists/*.list", dir), None => format!("lists/*.list") }; 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 list file {}", file_name)); file.read_to_string(&mut data) .expect(&format!("Cannot read list file {}", file_name)); let metadata = file.metadata() .expect(&format!("Cannot get metadata of list file {}", file_name)); let mut list = List { id: String::from(name), index : index, data: data, mtime: metadata.modified().unwrap(), name: String::from(name).to_title_case(), path: entry.clone() }; lists.push(list); index += 1; } lists } } #[cfg(test)] mod tests { use super::*; #[test] fn loads_all_lists() { let lists = List::load_all(Some("test")); let list_ids: Vec<&str> = lists.iter() .map(|list| list.id.as_ref()).collect(); assert_eq!(list_ids, vec!["test", "updatable"]); } #[test] fn converts_to_html() { let lists = List::load_all(Some("test")); let list = lists.iter() .find(|list| list.id == "test") .unwrap(); assert_eq!(list.to_html(), r#"

This is a test list

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