use std::sync::mpsc::channel; use std::time::Duration; use std::{env, fs, path}; use fs_extra::dir; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; use toml::Value; use config::Config; use post::{parse_post, read_posts_dir}; use write::{write_post, write_post_listing}; pub fn build(include_drafts: bool) { let cwd = env::current_dir().expect("Couldn't read current directory"); let config = match fs::read_to_string(cwd.join("casaubon.toml")) { Ok(contents) => match contents.parse::() { Ok(config) => Config { site_name: String::from(config["site_name"].as_str().unwrap()), }, Err(_) => panic!("Invalid casaubon.toml"), }, Err(_) => { panic!("Can't find casaubon.toml"); } }; match fs::read_dir(cwd.join("public")) { Ok(_) => { fs::remove_dir_all(cwd.join("public")).unwrap(); } Err(_) => {} } fs::create_dir(cwd.join("public")).expect("Couldn't create public directory"); let layout_template = fs::read_to_string(&cwd.join("templates").join("layout.html")) .expect("Couldn't find layout template"); let post_template = fs::read_to_string(cwd.join("templates").join("post.html")) .expect("Couldn't find post template"); let post_listing_template = fs::read_to_string(cwd.join("templates").join("post_listing.html")) .expect("Couldn't find post listing item template"); let post_item_template = fs::read_to_string(cwd.join("templates").join("post_listing_item.html")) .expect("Couldn't find post listing item template"); let post_paths = match include_drafts { true => { let mut posts = read_posts_dir(&cwd.join("posts")); posts.append(&mut read_posts_dir(&cwd.join("drafts"))); posts } false => read_posts_dir(&cwd.join("posts")), }; let posts = post_paths .into_iter() .map(|entry| { let post = parse_post(entry.path()); write_post(&cwd, &layout_template, &post_template, &post, &config); post }) .collect(); write_post_listing( &cwd, &layout_template, &post_listing_template, &post_item_template, &posts, &config, ); fs_extra::copy_items( &vec![cwd.join("css"), cwd.join("js")], cwd.join("public"), &dir::CopyOptions::new(), ) .expect("Couldn't copy css/js directories"); } pub fn new(name: &str) { let cwd = env::current_dir().expect("Couldn't read current directory"); let project_path = cwd.join(name); fs::create_dir(&project_path).expect(&format!("Couldn't create directory '{}'", &name)); fs::write( project_path.join("casaubon.toml"), format!("site_name = \"{}\"", &name), ) .expect("Could not create casaubon.toml"); for dir in &["drafts", "posts", "public", "templates", "css", "js"] { fs::create_dir(&project_path.join(&dir)) .expect(&format!("Couldn't create {} directory", &dir)); } fs::write(project_path.join("css").join("style.css"), "") .expect("Couldn't create css/style.css"); fs::write(project_path.join("js").join("index.js"), "").expect("Couldn't create js/index.js"); for file in &["layout", "post_listing", "post", "post_listing_item"] { fs::write( &project_path .join("templates") .join(format!("{}.html", file)), "", ) .expect(&format!("Couldn't write templates/{}.html", file)); } } fn should_rebuild(cwd: &path::PathBuf, path: &path::PathBuf) -> bool { let path_string = path.to_str().unwrap().to_string(); let change_is_from_public = path_string.contains(cwd.join("public").to_str().unwrap()); let change_is_from_git = path_string.contains(cwd.join(".git").to_str().unwrap()); !change_is_from_public && !change_is_from_git } pub fn watch(include_drafts: bool) -> notify::Result<()> { let cwd = env::current_dir().expect("Couldn't read current directory"); let (tx, rx) = channel(); let mut watcher: RecommendedWatcher = try!(Watcher::new(tx, Duration::from_secs(2))); try!(watcher.watch(&cwd, RecursiveMode::Recursive)); println!("Watching {}", cwd.to_str().unwrap()); let handle_event = |path: &path::PathBuf| { if should_rebuild(&cwd, &path) { println!("Rebuilding"); build(include_drafts); } }; loop { match rx.recv() { Ok(e) => match e { DebouncedEvent::Create(path) => { handle_event(&path); } DebouncedEvent::Write(path) => { handle_event(&path); } _ => {} }, Err(e) => println!("watch error: {:?}", e), } } } #[cfg(test)] mod tests { #[allow(unused_imports)] use super::*; #[allow(unused_imports)] use uuid::Uuid; #[test] fn test_new() { let temp_dir = env::temp_dir(); env::set_current_dir(&temp_dir).unwrap(); let uuid = Uuid::new_v4().to_string(); let project_dir = temp_dir.join(&uuid); new(&uuid); for dir in &["public", "posts", "templates"] { fs::read_dir(&project_dir.join(dir)).unwrap(); } assert_eq!( "", fs::read_to_string(&project_dir.join("templates").join("post_listing.html")).unwrap() ); assert_eq!( "", fs::read_to_string(&project_dir.join("templates").join("layout.html")).unwrap() ); assert_eq!( "", fs::read_to_string(&project_dir.join("templates").join("post_listing_item.html")) .unwrap() ); assert_eq!( "", fs::read_to_string(&project_dir.join("templates").join("post.html")).unwrap() ); assert_eq!( "", fs::read_to_string(&project_dir.join("css").join("style.css")).unwrap() ); assert_eq!( "", fs::read_to_string(&project_dir.join("js").join("index.js")).unwrap() ); assert_eq!( format!("site_name = \"{}\"", &uuid), fs::read_to_string(&project_dir.join("casaubon.toml")).unwrap() ); fs::remove_dir_all(project_dir).unwrap(); } #[test] fn test_build() { let temp_dir = env::temp_dir(); let uuid = Uuid::new_v4().to_string(); let project_dir = temp_dir.join(&uuid); fs::create_dir(&project_dir).unwrap(); env::set_current_dir(&project_dir).unwrap(); fs::create_dir(project_dir.join("posts")).unwrap(); fs::create_dir(project_dir.join("public")).unwrap(); fs::create_dir(project_dir.join("templates")).unwrap(); fs::create_dir(project_dir.join("css")).unwrap(); fs::create_dir(project_dir.join("js")).unwrap(); fs::write( project_dir.join("css").join("style.css"), "body { background: blue; }", ) .unwrap(); fs::write( project_dir.join("js").join("index.js"), "window.onload = function () { alert() }", ) .unwrap(); fs::write( project_dir.join("templates").join("layout.html"), "{{ page_title }}{{ contents }}", ) .unwrap(); fs::write( project_dir.join("templates").join("post.html"), "

{{ title }}

{{ body }}
", ) .unwrap(); fs::write( project_dir.join("templates").join("post_listing.html"), "", ) .unwrap(); fs::write( project_dir.join("templates").join("post_listing_item.html"), "
  • {{ title }}
  • ", ) .unwrap(); fs::write( project_dir.join("posts").join("first-post.md"), "# First post\n\nThis is the first post\n\nIt has multiple paragraphs", ) .unwrap(); fs::write(project_dir.join("casaubon.toml"), "site_name = \"Test Site\"").unwrap(); build(false); assert_eq!( "Test Site", fs::read_to_string(project_dir.join("public").join("index.html")).unwrap(), ); assert_eq!( "First post | Test Site

    First pos\ t

    This is the first post

    It has multiple paragra\ phs

    ", fs::read_to_string( project_dir .join("public") .join("first-post") .join("index.html") ) .unwrap() .replace("\n", ""), ); assert_eq!( "body { background: blue; }", fs::read_to_string(project_dir.join("public").join("css").join("style.css")).unwrap() ); assert_eq!( "window.onload = function () { alert() }", fs::read_to_string(project_dir.join("public").join("js").join("index.js")).unwrap() ); fs::remove_dir_all(project_dir).unwrap(); } #[test] fn test_build_drafts() { let temp_dir = env::temp_dir(); let uuid = Uuid::new_v4().to_string(); let project_dir = temp_dir.join(&uuid); fs::create_dir(&project_dir).unwrap(); env::set_current_dir(&project_dir).unwrap(); fs::create_dir(project_dir.join("drafts")).unwrap(); fs::create_dir(project_dir.join("posts")).unwrap(); fs::create_dir(project_dir.join("public")).unwrap(); fs::create_dir(project_dir.join("templates")).unwrap(); fs::create_dir(project_dir.join("css")).unwrap(); fs::create_dir(project_dir.join("js")).unwrap(); fs::write( project_dir.join("css").join("style.css"), "body { background: blue; }", ) .unwrap(); fs::write( project_dir.join("js").join("index.js"), "window.onload = function () { alert() }", ) .unwrap(); fs::write( project_dir.join("templates").join("layout.html"), "{{ page_title }}{{ contents }}", ) .unwrap(); fs::write( project_dir.join("templates").join("post.html"), "

    {{ title }}

    {{ body }}
    ", ) .unwrap(); fs::write( project_dir.join("templates").join("post_listing.html"), "", ) .unwrap(); fs::write( project_dir.join("templates").join("post_listing_item.html"), "
  • {{ title }}
  • ", ) .unwrap(); fs::write( project_dir.join("posts").join("first-post.md"), "# First post\n\nThis is the first post", ) .unwrap(); fs::write( project_dir.join("drafts").join("first-draft.md"), "# First draft\n\nThis is the first draft", ) .unwrap(); fs::write(project_dir.join("casaubon.toml"), "site_name = \"Test Site\"").unwrap(); build(true); assert_eq!( "Test Site", fs::read_to_string(project_dir.join("public").join("index.html")).unwrap().replace("\n", ""), ); assert_eq!( "First post | Test Site

    First post

    This is the first post

    ", fs::read_to_string( project_dir .join("public") .join("first-post") .join("index.html") ).unwrap() .replace("\n", ""), ); assert_eq!( "First draft | Test Site

    First draft

    This is the first draft

    ", fs::read_to_string( project_dir .join("public") .join("first-draft") .join("index.html") ).unwrap() .replace("\n", ""), ); fs::remove_dir_all(project_dir).unwrap(); } #[test] fn test_should_rebuild() { let cwd = env::current_dir().unwrap(); assert_eq!( false, should_rebuild(&cwd, &cwd.join("public").join("index.html")) ); assert_eq!( false, should_rebuild(&cwd, &cwd.join(".git").join("index.html")) ); assert_eq!( true, should_rebuild(&cwd, &cwd.join("posts").join("test.md")) ); assert_eq!( true, should_rebuild(&cwd, &cwd.join("drafts").join("test.md")) ); assert_eq!( true, should_rebuild(&cwd, &cwd.join("css").join("style.css")) ); assert_eq!(true, should_rebuild(&cwd, &cwd.join("js").join("index.js"))); } }