A static site generator written in Rust
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

main.rs 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. extern crate clap;
  2. extern crate comrak;
  3. extern crate fs_extra;
  4. #[macro_use]
  5. extern crate lazy_static;
  6. extern crate regex;
  7. extern crate toml;
  8. extern crate uuid;
  9. use std::{env, fs};
  10. use clap::{App, Arg};
  11. use fs_extra::dir;
  12. use toml::Value;
  13. use config::Config;
  14. use post::{parse_post, read_posts_dir};
  15. use write::{write_post, write_post_listing};
  16. mod config;
  17. mod post;
  18. mod render;
  19. mod write;
  20. fn build() {
  21. let cwd = env::current_dir().expect("Couldn't read current directory");
  22. let config = match fs::read_to_string(cwd.join("tlon.toml")) {
  23. Ok(contents) => match contents.parse::<Value>() {
  24. Ok(config) => Config {
  25. site_name: String::from(config["site_name"].as_str().unwrap()),
  26. },
  27. Err(_) => panic!("Invalid tlon.toml"),
  28. },
  29. Err(_) => {
  30. panic!("Can't find tlon.toml");
  31. }
  32. };
  33. match fs::read_dir(cwd.join("public")) {
  34. Ok(_) => {
  35. fs::remove_dir_all(cwd.join("public")).unwrap();
  36. }
  37. Err(_) => {}
  38. }
  39. fs::create_dir(cwd.join("public")).expect("Couldn't create public 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 post_paths = read_posts_dir(&cwd.join("posts"));
  50. let posts = post_paths
  51. .into_iter()
  52. .map(|path| match path {
  53. Ok(p) => {
  54. let post = parse_post(p.path());
  55. write_post(&cwd, &layout_template, &post_template, &post, &config);
  56. post
  57. }
  58. Err(err) => panic!(err),
  59. }).collect();
  60. write_post_listing(
  61. &cwd,
  62. &layout_template,
  63. &post_listing_template,
  64. &post_item_template,
  65. &posts,
  66. &config,
  67. );
  68. fs_extra::copy_items(
  69. &vec![cwd.join("css"), cwd.join("js")],
  70. cwd.join("public"),
  71. &dir::CopyOptions::new(),
  72. ).expect("Couldn't copy css/js directories");
  73. }
  74. fn new(name: &str) {
  75. let cwd = env::current_dir().expect("Couldn't read current directory");
  76. let project_path = cwd.join(name);
  77. fs::create_dir(&project_path).expect(&format!("Couldn't create directory '{}'", &name));
  78. fs::write(
  79. project_path.join("tlon.toml"),
  80. format!("site_name = \"{}\"", &name),
  81. ).expect("Could not create tlon.toml");
  82. for dir in &["posts", "public", "templates", "css", "js"] {
  83. fs::create_dir(&project_path.join(&dir))
  84. .expect(&format!("Couldn't create {} directory", &dir));
  85. }
  86. fs::write(project_path.join("css").join("style.css"), "")
  87. .expect("Couldn't create css/style.css");
  88. fs::write(project_path.join("js").join("index.js"), "").expect("Couldn't create js/index.js");
  89. for file in &["layout", "post_listing", "post", "post_listing_item"] {
  90. fs::write(
  91. &project_path
  92. .join("templates")
  93. .join(format!("{}.html", file)),
  94. "",
  95. ).expect(&format!("Couldn't write templates/{}.html", file));
  96. }
  97. }
  98. fn main() {
  99. let matches = App::new("tlon")
  100. .version("0.1.0")
  101. .author("Dylan Baker")
  102. .about("Highly opinionated static site generator")
  103. .arg(
  104. Arg::with_name("command")
  105. .required(true)
  106. .possible_values(&["build", "new"])
  107. .index(1),
  108. ).arg(
  109. Arg::with_name("name")
  110. .required_if("command", "new")
  111. .index(2),
  112. ).get_matches();
  113. let command = matches.value_of("command").unwrap();
  114. if command == "build" {
  115. build();
  116. } else if command == "new" {
  117. new(&matches.value_of("name").unwrap());
  118. }
  119. }
  120. #[cfg(test)]
  121. mod tests {
  122. #[allow(unused_imports)]
  123. use super::*;
  124. #[allow(unused_imports)]
  125. use uuid::Uuid;
  126. #[test]
  127. fn test_new() {
  128. let temp_dir = env::temp_dir();
  129. env::set_current_dir(&temp_dir).unwrap();
  130. let uuid = Uuid::new_v4().to_string();
  131. let project_dir = temp_dir.join(&uuid);
  132. new(&uuid);
  133. for dir in &["public", "posts", "templates"] {
  134. fs::read_dir(&project_dir.join(dir)).unwrap();
  135. }
  136. assert_eq!(
  137. "",
  138. fs::read_to_string(&project_dir.join("templates").join("post_listing.html")).unwrap()
  139. );
  140. assert_eq!(
  141. "",
  142. fs::read_to_string(&project_dir.join("templates").join("layout.html")).unwrap()
  143. );
  144. assert_eq!(
  145. "",
  146. fs::read_to_string(&project_dir.join("templates").join("post_listing_item.html"))
  147. .unwrap()
  148. );
  149. assert_eq!(
  150. "",
  151. fs::read_to_string(&project_dir.join("templates").join("post.html")).unwrap()
  152. );
  153. assert_eq!(
  154. "",
  155. fs::read_to_string(&project_dir.join("css").join("style.css")).unwrap()
  156. );
  157. assert_eq!(
  158. "",
  159. fs::read_to_string(&project_dir.join("js").join("index.js")).unwrap()
  160. );
  161. assert_eq!(
  162. format!("site_name = \"{}\"", &uuid),
  163. fs::read_to_string(&project_dir.join("tlon.toml")).unwrap()
  164. );
  165. fs::remove_dir_all(project_dir).unwrap();
  166. }
  167. #[test]
  168. fn test_build() {
  169. let temp_dir = env::temp_dir();
  170. let uuid = Uuid::new_v4().to_string();
  171. let project_dir = temp_dir.join(&uuid);
  172. fs::create_dir(&project_dir).unwrap();
  173. env::set_current_dir(&project_dir).unwrap();
  174. fs::create_dir(project_dir.join("posts")).unwrap();
  175. fs::create_dir(project_dir.join("public")).unwrap();
  176. fs::create_dir(project_dir.join("templates")).unwrap();
  177. fs::create_dir(project_dir.join("css")).unwrap();
  178. fs::create_dir(project_dir.join("js")).unwrap();
  179. fs::write(
  180. project_dir.join("css").join("style.css"),
  181. "body { background: blue; }",
  182. ).unwrap();
  183. fs::write(
  184. project_dir.join("js").join("index.js"),
  185. "window.onload = function () { alert() }",
  186. ).unwrap();
  187. fs::write(
  188. project_dir.join("templates").join("layout.html"),
  189. "<html><head><title>{{ page_title }}</title></head><body>{{ contents }}</body></html>",
  190. ).unwrap();
  191. fs::write(
  192. project_dir.join("templates").join("post.html"),
  193. "<article><h1>{{ title }}</h1><div>{{ body }}</div></article>",
  194. ).unwrap();
  195. fs::write(
  196. project_dir.join("templates").join("post_listing.html"),
  197. "<ul>{{ post_listing }}</ul>",
  198. ).unwrap();
  199. fs::write(
  200. project_dir.join("templates").join("post_listing_item.html"),
  201. "<li><a href=\"/{{ slug }}\">{{ title }}</a></li>",
  202. ).unwrap();
  203. fs::write(
  204. project_dir.join("posts").join("first-post.md"),
  205. "# First post\n\nThis is the first post\n\nIt has multiple paragraphs",
  206. ).unwrap();
  207. fs::write(project_dir.join("tlon.toml"), "site_name = \"Test Site\"").unwrap();
  208. build();
  209. assert_eq!(
  210. "<html><head><title>Test Site</title></head><body><ul><li><a href=\"/fir\
  211. st-post\">First post</a></li></ul></body></html>",
  212. fs::read_to_string(project_dir.join("public").join("index.html")).unwrap(),
  213. );
  214. assert_eq!(
  215. "<html><head><title>First post | Test Site</title></head><body><article><h1>First pos\
  216. t</h1><div><p>This is the first post</p><p>It has multiple paragra\
  217. phs</p></div></article></body></html>",
  218. fs::read_to_string(
  219. project_dir
  220. .join("public")
  221. .join("first-post")
  222. .join("index.html")
  223. ).unwrap()
  224. .replace("\n", ""),
  225. );
  226. assert_eq!(
  227. "body { background: blue; }",
  228. fs::read_to_string(project_dir.join("public").join("css").join("style.css")).unwrap()
  229. );
  230. assert_eq!(
  231. "window.onload = function () { alert() }",
  232. fs::read_to_string(project_dir.join("public").join("js").join("index.js")).unwrap()
  233. );
  234. fs::remove_dir_all(project_dir).unwrap();
  235. }
  236. }