extern crate chrono; extern crate fs_extra; extern crate htmlescape; #[macro_use] extern crate lazy_static; extern crate notify; extern crate pulldown_cmark; extern crate regex; extern crate toml; extern crate uuid; use std::sync::mpsc::channel; use std::time::Duration; use std::{fs, path}; use fs_extra::dir; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; use toml::Value; use config::Config; use entry::{parse_entry, read_entry_dir, write_entry, write_entry_listing, Entry, EntryKind}; use rss::generate_rss; mod config; mod entry; mod rss; pub fn build(include_drafts: bool, cwd: &path::Path) { 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()), url: String::from(config["url"].as_str().unwrap()), description: String::from(config["description"].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"); fs::create_dir(cwd.join("public").join("posts")).expect("Couldn't create posts 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 page_template = fs::read_to_string(cwd.join("templates").join("page.html")) .expect("Couldn't find page template"); let post_paths = match include_drafts { true => { let mut posts = read_entry_dir(&cwd.join("posts")); posts.append(&mut read_entry_dir(&cwd.join("drafts"))); posts } false => read_entry_dir(&cwd.join("posts")), }; let page_paths = read_entry_dir(&cwd.join("pages")); let mut posts: Vec = post_paths .into_iter() .map(|entry| { let path = entry.path(); let contents = fs::read_to_string(&path).expect("Couldn't read post file"); parse_entry(EntryKind::Post, &contents, &path) }) .collect::>(); posts.sort_by(|a, b| b.date.cmp(&a.date)); for post in &posts { write_entry(&cwd, &layout_template, &post_template, &post, &config); } for entry in page_paths.into_iter() { let path = entry.path(); let contents = fs::read_to_string(&path).expect("Couldn't read page file"); let page = parse_entry(EntryKind::Page, &contents, &path); write_entry(&cwd, &layout_template, &page_template, &page, &config); } write_entry_listing( &cwd, &layout_template, &post_listing_template, &post_item_template, &posts, &config, ); fs_extra::copy_items( &vec![cwd.join("assets")], cwd.join("public"), &dir::CopyOptions::new(), ) .expect("Couldn't copy assets directory"); let rss = generate_rss(&config, &posts); fs::write(cwd.join("public").join("rss.xml"), rss).expect("Couldn't write rss file"); } pub fn new(name: &str, cwd: &path::Path) { 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 = \"{}\"\nurl = \"{}.com\"\ndescription = \"\"", &name, &name ), ) .expect("Could not create casaubon.toml"); for dir in &[ "drafts", "posts", "pages", "public", "templates", "assets", "js", ] { fs::create_dir(&project_path.join(&dir)) .expect(&format!("Couldn't create {} directory", &dir)); } let default_layout_template = format!( " {}

{}

{{{{ contents }}}} \n", name, name ); let default_post_listing_template = format!( "

Posts

    {{{{ post_listing }}}}
\n" ); let default_post_template = format!( "

{{{{ title }}}}

{{{{ body }}}}
\n" ); let default_page_template = format!( "

{{{{ title }}}}

{{{{ body }}}}
\n" ); let default_post_listing_item_template = format!( "
  • {{ date }} {{{{ title }}}}
  • \n" ); for (filename, contents) in &[ ("layout", &default_layout_template), ("post_listing", &default_post_listing_template), ("post", &default_post_template), ("page", &default_page_template), ("post_listing_item", &default_post_listing_item_template), ] { fs::write( &project_path .join("templates") .join(format!("{}.html", filename)), &contents, ) .expect(&format!("Couldn't write templates/{}.html", filename)); } } fn should_rebuild(cwd: &path::Path, 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, cwd: &path::Path) -> notify::Result<()> { 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, &cwd); } }; 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; use std::env; #[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"))); } }