use chrono::NaiveDate; use regex::Regex; use std::fs; use std::path; #[derive(Debug)] pub struct Post { pub title: String, pub body: String, pub slug: String, pub date: NaiveDate, } pub fn read_posts_dir(cwd: &path::PathBuf) -> Vec { match fs::read_dir(cwd) { Ok(posts) => posts.into_iter().map(|post| post.unwrap()).collect(), Err(err) => panic!(err), } } pub fn parse_post(path: path::PathBuf) -> Post { let contents = fs::read_to_string(&path).expect("Couldn't read post file"); lazy_static! { static ref re: Regex = Regex::new(r"^# (?P.*) \| (?P<date>\d{4}-\d{2}-\d{2})\n\n(?s)(?P<body>.*)") .unwrap(); static ref slug_re: Regex = Regex::new(r"(?P<slug>\S+).md").unwrap(); } let title = &re.captures(&contents).expect("Couldn't parse title")["title"]; let date = &re.captures(&contents).expect("Couldn't parse date")["date"]; let body = &re.captures(&contents).expect("Couldn't parse body")["body"]; let filename = &path.file_name().unwrap().to_str().unwrap(); let slug = &slug_re.captures(filename).expect("Couldn't parse slug")["slug"]; Post { title: String::from(title), body: String::from(body), slug: String::from(slug), date: NaiveDate::parse_from_str(&date, "%Y-%m-%d").expect("Couldn't parse date"), } } #[cfg(test)] mod tests { #[allow(unused_imports)] use super::*; #[allow(unused_imports)] #[allow(unused_imports)] use std::{env, fs, path}; #[allow(unused_imports)] use uuid::Uuid; #[test] fn test_read_posts_dir() { let temp_dir = env::temp_dir(); let working_dir = temp_dir.join(&Uuid::new_v4().to_string()); fs::create_dir(&working_dir).unwrap(); env::set_current_dir(&working_dir).unwrap(); let cwd = env::current_dir().unwrap(); fs::create_dir(cwd.join("posts")).unwrap(); let post_body = "# This is a post\n\nHere is some content that goes in the post"; let mut uuids: Vec<String> = vec![]; for _ in 1..11 { let uuid = String::from(Uuid::new_v4().to_string()); uuids.push(uuid.clone()); fs::write( cwd.join("posts").join(format!("{}.md", &uuid)), &String::from(post_body), ) .unwrap(); } let mut expected_paths: Vec<String> = uuids .into_iter() .map(|uuid| { String::from( cwd.join("posts") .join(format!("{}.md", uuid)) .to_str() .unwrap(), ) }) .collect(); expected_paths.sort(); let mut actual_paths: Vec<String> = read_posts_dir(&cwd.join("posts")) .into_iter() .map(|dir_entry| String::from(dir_entry.path().to_str().unwrap())) .collect(); actual_paths.sort(); assert_eq!(expected_paths, actual_paths); fs::remove_dir_all(temp_dir.join(&working_dir)).unwrap(); } #[test] fn test_parse_post() { let temp_dir = env::temp_dir(); let working_dir = temp_dir.join(&Uuid::new_v4().to_string()); fs::create_dir(&working_dir).unwrap(); env::set_current_dir(&working_dir).unwrap(); let cwd = env::current_dir().unwrap(); fs::create_dir(cwd.join("posts")).unwrap(); let slug = Uuid::new_v4().to_string(); let filename = format!("{}.md", slug); fs::write( cwd.join("posts").join(&filename), "# This is a post | 2019-01-01\n\nHere is some content that goes in the post", ) .unwrap(); let post = parse_post(cwd.join("posts").join(&filename)); let date = NaiveDate::from_ymd(2019, 1, 1); assert_eq!("This is a post", post.title); assert_eq!("Here is some content that goes in the post", post.body); assert_eq!(slug, post.slug); assert_eq!(date, post.date); fs::remove_dir_all(temp_dir.join(&working_dir)).unwrap(); } #[test] fn test_post_with_multiple_paragraphs() { let temp_dir = env::temp_dir(); let working_dir = temp_dir.join(&Uuid::new_v4().to_string()); fs::create_dir(&working_dir).unwrap(); env::set_current_dir(&working_dir).unwrap(); let cwd = env::current_dir().unwrap(); fs::create_dir(cwd.join("posts")).unwrap(); let slug = Uuid::new_v4().to_string(); let filename = format!("{}.md", slug); fs::write( cwd.join("posts").join(&filename), "# This is a post | 2019-01-01\n\nHere is a line\n\nHere is another line\n\nAnd a third", ) .unwrap(); let post = parse_post(cwd.join("posts").join(&filename)); let date = NaiveDate::from_ymd(2019, 1, 1); assert_eq!("This is a post", post.title); assert_eq!( "Here is a line\n\nHere is another line\n\nAnd a third", post.body ); assert_eq!(slug, post.slug); assert_eq!(date, post.date); fs::remove_dir_all(temp_dir.join(&working_dir)).unwrap(); } }