A static site generator written in Rust
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. extern crate chrono;
  2. extern crate fs_extra;
  3. #[macro_use]
  4. extern crate lazy_static;
  5. extern crate notify;
  6. extern crate pulldown_cmark;
  7. extern crate regex;
  8. extern crate toml;
  9. extern crate uuid;
  10. use std::sync::mpsc::channel;
  11. use std::time::Duration;
  12. use std::{fs, path};
  13. use fs_extra::dir;
  14. use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
  15. use toml::Value;
  16. use config::Config;
  17. use entry::{parse_entry, read_entry_dir, write_entry, write_entry_listing, Entry};
  18. mod config;
  19. mod entry;
  20. pub fn build(include_drafts: bool, cwd: &path::Path) {
  21. let config = match fs::read_to_string(cwd.join("casaubon.toml")) {
  22. Ok(contents) => match contents.parse::<Value>() {
  23. Ok(config) => Config {
  24. site_name: String::from(config["site_name"].as_str().unwrap()),
  25. },
  26. Err(_) => panic!("Invalid casaubon.toml"),
  27. },
  28. Err(_) => {
  29. panic!("Can't find casaubon.toml");
  30. }
  31. };
  32. match fs::read_dir(cwd.join("public")) {
  33. Ok(_) => {
  34. fs::remove_dir_all(cwd.join("public")).unwrap();
  35. }
  36. Err(_) => {}
  37. }
  38. fs::create_dir(cwd.join("public")).expect("Couldn't create public directory");
  39. fs::create_dir(cwd.join("public").join("posts")).expect("Couldn't create posts directory");
  40. let layout_template = fs::read_to_string(&cwd.join("templates").join("layout.html"))
  41. .expect("Couldn't find layout template");
  42. let post_template = fs::read_to_string(cwd.join("templates").join("post.html"))
  43. .expect("Couldn't find post template");
  44. let post_listing_template = fs::read_to_string(cwd.join("templates").join("post_listing.html"))
  45. .expect("Couldn't find post listing item template");
  46. let post_item_template =
  47. fs::read_to_string(cwd.join("templates").join("post_listing_item.html"))
  48. .expect("Couldn't find post listing item template");
  49. let page_template = fs::read_to_string(cwd.join("templates").join("page.html"))
  50. .expect("Couldn't find page template");
  51. let post_paths = match include_drafts {
  52. true => {
  53. let mut posts = read_entry_dir(&cwd.join("posts"));
  54. posts.append(&mut read_entry_dir(&cwd.join("drafts")));
  55. posts
  56. }
  57. false => read_entry_dir(&cwd.join("posts")),
  58. };
  59. let page_paths = read_entry_dir(&cwd.join("pages"));
  60. let mut posts: Vec<Entry> = post_paths
  61. .into_iter()
  62. .map(|entry| {
  63. let path = entry.path();
  64. let contents = fs::read_to_string(&path).expect("Couldn't read post file");
  65. parse_entry(&contents, &path)
  66. })
  67. .collect::<Vec<Entry>>();
  68. posts.sort_by(|a, b| b.date.cmp(&a.date));
  69. for post in &posts {
  70. write_entry(&cwd, &layout_template, &post_template, &post, &config);
  71. }
  72. for entry in page_paths.into_iter() {
  73. let path = entry.path();
  74. let contents = fs::read_to_string(&path).expect("Couldn't read page file");
  75. let page = parse_entry(&contents, &path);
  76. write_entry(&cwd, &layout_template, &page_template, &page, &config);
  77. }
  78. write_entry_listing(
  79. &cwd,
  80. &layout_template,
  81. &post_listing_template,
  82. &post_item_template,
  83. &posts,
  84. &config,
  85. );
  86. fs_extra::copy_items(
  87. &vec![cwd.join("css"), cwd.join("js")],
  88. cwd.join("public"),
  89. &dir::CopyOptions::new(),
  90. )
  91. .expect("Couldn't copy css/js directories");
  92. }
  93. pub fn new(name: &str, cwd: &path::Path) {
  94. let project_path = cwd.join(name);
  95. fs::create_dir(&project_path).expect(&format!("Couldn't create directory '{}'", &name));
  96. fs::write(
  97. project_path.join("casaubon.toml"),
  98. format!("site_name = \"{}\"", &name),
  99. )
  100. .expect("Could not create casaubon.toml");
  101. for dir in &[
  102. "drafts",
  103. "posts",
  104. "pages",
  105. "public",
  106. "templates",
  107. "css",
  108. "js",
  109. ] {
  110. fs::create_dir(&project_path.join(&dir))
  111. .expect(&format!("Couldn't create {} directory", &dir));
  112. }
  113. fs::write(project_path.join("css").join("style.css"), "")
  114. .expect("Couldn't create css/style.css");
  115. fs::write(project_path.join("js").join("index.js"), "").expect("Couldn't create js/index.js");
  116. let default_layout_template = format!(
  117. "<html>
  118. <head>
  119. <title>{}</title>
  120. </head>
  121. <body>
  122. <h1>{}</h1>
  123. {{{{ contents }}}}
  124. </body>
  125. </html>\n",
  126. name, name
  127. );
  128. let default_post_listing_template = format!(
  129. "<div>
  130. <h3>Posts</h3>
  131. <ul>{{{{ post_listing }}}}</ul>
  132. </div>\n"
  133. );
  134. let default_post_template = format!(
  135. "<article>
  136. <h1>{{{{ title }}}}</h1>
  137. <div>{{{{ body }}}}</div>
  138. </article>\n"
  139. );
  140. let default_page_template = format!(
  141. "<article>
  142. <h1>{{{{ title }}}}</h1>
  143. <div>{{{{ body }}}}</div>
  144. </article>\n"
  145. );
  146. let default_post_listing_item_template = format!(
  147. "<li>
  148. {{ date }} <a href=\"/posts/{{{{ slug }}}}/\">{{{{ title }}}}</a>
  149. </li>\n"
  150. );
  151. for (filename, contents) in &[
  152. ("layout", &default_layout_template),
  153. ("post_listing", &default_post_listing_template),
  154. ("post", &default_post_template),
  155. ("page", &default_page_template),
  156. ("post_listing_item", &default_post_listing_item_template),
  157. ] {
  158. fs::write(
  159. &project_path
  160. .join("templates")
  161. .join(format!("{}.html", filename)),
  162. &contents,
  163. )
  164. .expect(&format!("Couldn't write templates/{}.html", filename));
  165. }
  166. }
  167. fn should_rebuild(cwd: &path::Path, path: &path::PathBuf) -> bool {
  168. let path_string = path.to_str().unwrap().to_string();
  169. let change_is_from_public = path_string.contains(cwd.join("public").to_str().unwrap());
  170. let change_is_from_git = path_string.contains(cwd.join(".git").to_str().unwrap());
  171. !change_is_from_public && !change_is_from_git
  172. }
  173. pub fn watch(include_drafts: bool, cwd: &path::Path) -> notify::Result<()> {
  174. let (tx, rx) = channel();
  175. let mut watcher: RecommendedWatcher = try!(Watcher::new(tx, Duration::from_secs(2)));
  176. try!(watcher.watch(&cwd, RecursiveMode::Recursive));
  177. println!("Watching {}", cwd.to_str().unwrap());
  178. let handle_event = |path: &path::PathBuf| {
  179. if should_rebuild(&cwd, &path) {
  180. println!("Rebuilding");
  181. build(include_drafts, &cwd);
  182. }
  183. };
  184. loop {
  185. match rx.recv() {
  186. Ok(e) => match e {
  187. DebouncedEvent::Create(path) => {
  188. handle_event(&path);
  189. }
  190. DebouncedEvent::Write(path) => {
  191. handle_event(&path);
  192. }
  193. _ => {}
  194. },
  195. Err(e) => println!("watch error: {:?}", e),
  196. }
  197. }
  198. }
  199. #[cfg(test)]
  200. mod tests {
  201. #[allow(unused_imports)]
  202. use super::*;
  203. #[allow(unused_imports)]
  204. use uuid::Uuid;
  205. use std::env;
  206. #[test]
  207. fn test_should_rebuild() {
  208. let cwd = env::current_dir().unwrap();
  209. assert_eq!(
  210. false,
  211. should_rebuild(&cwd, &cwd.join("public").join("index.html"))
  212. );
  213. assert_eq!(
  214. false,
  215. should_rebuild(&cwd, &cwd.join(".git").join("index.html"))
  216. );
  217. assert_eq!(
  218. true,
  219. should_rebuild(&cwd, &cwd.join("posts").join("test.md"))
  220. );
  221. assert_eq!(
  222. true,
  223. should_rebuild(&cwd, &cwd.join("drafts").join("test.md"))
  224. );
  225. assert_eq!(
  226. true,
  227. should_rebuild(&cwd, &cwd.join("css").join("style.css"))
  228. );
  229. assert_eq!(true, should_rebuild(&cwd, &cwd.join("js").join("index.js")));
  230. }
  231. }