A static site generator written in Rust
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

commands.rs 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. use std::sync::mpsc::channel;
  2. use std::time::Duration;
  3. use std::{env, fs};
  4. use fs_extra::dir;
  5. use notify::{RecommendedWatcher, RecursiveMode, Watcher};
  6. use toml::Value;
  7. use config::Config;
  8. use post::{parse_post, read_posts_dir};
  9. use write::{write_post, write_post_listing};
  10. pub fn build() {
  11. let cwd = env::current_dir().expect("Couldn't read current directory");
  12. let config = match fs::read_to_string(cwd.join("tlon.toml")) {
  13. Ok(contents) => match contents.parse::<Value>() {
  14. Ok(config) => Config {
  15. site_name: String::from(config["site_name"].as_str().unwrap()),
  16. },
  17. Err(_) => panic!("Invalid tlon.toml"),
  18. },
  19. Err(_) => {
  20. panic!("Can't find tlon.toml");
  21. }
  22. };
  23. match fs::read_dir(cwd.join("public")) {
  24. Ok(_) => {
  25. fs::remove_dir_all(cwd.join("public")).unwrap();
  26. }
  27. Err(_) => {}
  28. }
  29. fs::create_dir(cwd.join("public")).expect("Couldn't create public directory");
  30. let layout_template = fs::read_to_string(&cwd.join("templates").join("layout.html"))
  31. .expect("Couldn't find layout template");
  32. let post_template = fs::read_to_string(cwd.join("templates").join("post.html"))
  33. .expect("Couldn't find post template");
  34. let post_listing_template = fs::read_to_string(cwd.join("templates").join("post_listing.html"))
  35. .expect("Couldn't find post listing item template");
  36. let post_item_template =
  37. fs::read_to_string(cwd.join("templates").join("post_listing_item.html"))
  38. .expect("Couldn't find post listing item template");
  39. let post_paths = read_posts_dir(&cwd.join("posts"));
  40. let posts = post_paths
  41. .into_iter()
  42. .map(|path| match path {
  43. Ok(p) => {
  44. let post = parse_post(p.path());
  45. write_post(&cwd, &layout_template, &post_template, &post, &config);
  46. post
  47. }
  48. Err(err) => panic!(err),
  49. }).collect();
  50. write_post_listing(
  51. &cwd,
  52. &layout_template,
  53. &post_listing_template,
  54. &post_item_template,
  55. &posts,
  56. &config,
  57. );
  58. fs_extra::copy_items(
  59. &vec![cwd.join("css"), cwd.join("js")],
  60. cwd.join("public"),
  61. &dir::CopyOptions::new(),
  62. ).expect("Couldn't copy css/js directories");
  63. }
  64. pub fn new(name: &str) {
  65. let cwd = env::current_dir().expect("Couldn't read current directory");
  66. let project_path = cwd.join(name);
  67. fs::create_dir(&project_path).expect(&format!("Couldn't create directory '{}'", &name));
  68. fs::write(
  69. project_path.join("tlon.toml"),
  70. format!("site_name = \"{}\"", &name),
  71. ).expect("Could not create tlon.toml");
  72. for dir in &["posts", "public", "templates", "css", "js"] {
  73. fs::create_dir(&project_path.join(&dir))
  74. .expect(&format!("Couldn't create {} directory", &dir));
  75. }
  76. fs::write(project_path.join("css").join("style.css"), "")
  77. .expect("Couldn't create css/style.css");
  78. fs::write(project_path.join("js").join("index.js"), "").expect("Couldn't create js/index.js");
  79. for file in &["layout", "post_listing", "post", "post_listing_item"] {
  80. fs::write(
  81. &project_path
  82. .join("templates")
  83. .join(format!("{}.html", file)),
  84. "",
  85. ).expect(&format!("Couldn't write templates/{}.html", file));
  86. }
  87. }
  88. pub fn watch() -> notify::Result<()> {
  89. let cwd = env::current_dir().expect("Couldn't read current directory");
  90. let (tx, rx) = channel();
  91. let mut watcher: RecommendedWatcher = try!(Watcher::new(tx, Duration::from_secs(2)));
  92. try!(watcher.watch(&cwd.join("posts"), RecursiveMode::Recursive));
  93. println!("Watching {}/posts", cwd.to_str().unwrap());
  94. loop {
  95. match rx.recv() {
  96. Ok(_) => {
  97. build();
  98. }
  99. Err(e) => println!("watch error: {:?}", e),
  100. }
  101. }
  102. }
  103. #[cfg(test)]
  104. mod tests {
  105. #[allow(unused_imports)]
  106. use super::*;
  107. #[allow(unused_imports)]
  108. use uuid::Uuid;
  109. #[test]
  110. fn test_new() {
  111. let temp_dir = env::temp_dir();
  112. env::set_current_dir(&temp_dir).unwrap();
  113. let uuid = Uuid::new_v4().to_string();
  114. let project_dir = temp_dir.join(&uuid);
  115. new(&uuid);
  116. for dir in &["public", "posts", "templates"] {
  117. fs::read_dir(&project_dir.join(dir)).unwrap();
  118. }
  119. assert_eq!(
  120. "",
  121. fs::read_to_string(&project_dir.join("templates").join("post_listing.html")).unwrap()
  122. );
  123. assert_eq!(
  124. "",
  125. fs::read_to_string(&project_dir.join("templates").join("layout.html")).unwrap()
  126. );
  127. assert_eq!(
  128. "",
  129. fs::read_to_string(&project_dir.join("templates").join("post_listing_item.html"))
  130. .unwrap()
  131. );
  132. assert_eq!(
  133. "",
  134. fs::read_to_string(&project_dir.join("templates").join("post.html")).unwrap()
  135. );
  136. assert_eq!(
  137. "",
  138. fs::read_to_string(&project_dir.join("css").join("style.css")).unwrap()
  139. );
  140. assert_eq!(
  141. "",
  142. fs::read_to_string(&project_dir.join("js").join("index.js")).unwrap()
  143. );
  144. assert_eq!(
  145. format!("site_name = \"{}\"", &uuid),
  146. fs::read_to_string(&project_dir.join("tlon.toml")).unwrap()
  147. );
  148. fs::remove_dir_all(project_dir).unwrap();
  149. }
  150. #[test]
  151. fn test_build() {
  152. let temp_dir = env::temp_dir();
  153. let uuid = Uuid::new_v4().to_string();
  154. let project_dir = temp_dir.join(&uuid);
  155. fs::create_dir(&project_dir).unwrap();
  156. env::set_current_dir(&project_dir).unwrap();
  157. fs::create_dir(project_dir.join("posts")).unwrap();
  158. fs::create_dir(project_dir.join("public")).unwrap();
  159. fs::create_dir(project_dir.join("templates")).unwrap();
  160. fs::create_dir(project_dir.join("css")).unwrap();
  161. fs::create_dir(project_dir.join("js")).unwrap();
  162. fs::write(
  163. project_dir.join("css").join("style.css"),
  164. "body { background: blue; }",
  165. ).unwrap();
  166. fs::write(
  167. project_dir.join("js").join("index.js"),
  168. "window.onload = function () { alert() }",
  169. ).unwrap();
  170. fs::write(
  171. project_dir.join("templates").join("layout.html"),
  172. "<html><head><title>{{ page_title }}</title></head><body>{{ contents }}</body></html>",
  173. ).unwrap();
  174. fs::write(
  175. project_dir.join("templates").join("post.html"),
  176. "<article><h1>{{ title }}</h1><div>{{ body }}</div></article>",
  177. ).unwrap();
  178. fs::write(
  179. project_dir.join("templates").join("post_listing.html"),
  180. "<ul>{{ post_listing }}</ul>",
  181. ).unwrap();
  182. fs::write(
  183. project_dir.join("templates").join("post_listing_item.html"),
  184. "<li><a href=\"/{{ slug }}\">{{ title }}</a></li>",
  185. ).unwrap();
  186. fs::write(
  187. project_dir.join("posts").join("first-post.md"),
  188. "# First post\n\nThis is the first post\n\nIt has multiple paragraphs",
  189. ).unwrap();
  190. fs::write(project_dir.join("tlon.toml"), "site_name = \"Test Site\"").unwrap();
  191. build();
  192. assert_eq!(
  193. "<html><head><title>Test Site</title></head><body><ul><li><a href=\"/fir\
  194. st-post\">First post</a></li></ul></body></html>",
  195. fs::read_to_string(project_dir.join("public").join("index.html")).unwrap(),
  196. );
  197. assert_eq!(
  198. "<html><head><title>First post | Test Site</title></head><body><article><h1>First pos\
  199. t</h1><div><p>This is the first post</p><p>It has multiple paragra\
  200. phs</p></div></article></body></html>",
  201. fs::read_to_string(
  202. project_dir
  203. .join("public")
  204. .join("first-post")
  205. .join("index.html")
  206. ).unwrap()
  207. .replace("\n", ""),
  208. );
  209. assert_eq!(
  210. "body { background: blue; }",
  211. fs::read_to_string(project_dir.join("public").join("css").join("style.css")).unwrap()
  212. );
  213. assert_eq!(
  214. "window.onload = function () { alert() }",
  215. fs::read_to_string(project_dir.join("public").join("js").join("index.js")).unwrap()
  216. );
  217. fs::remove_dir_all(project_dir).unwrap();
  218. }
  219. }