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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. #[macro_use]
  2. extern crate lazy_static;
  3. extern crate regex;
  4. use std::env;
  5. use std::fs;
  6. use std::path;
  7. use regex::Regex;
  8. #[derive(Debug)]
  9. struct Post {
  10. title: String,
  11. body: String,
  12. slug: String,
  13. }
  14. fn parse_post(path: path::PathBuf) -> Post {
  15. let contents = fs::read_to_string(&path).expect("Couldn't read post file");
  16. lazy_static! {
  17. static ref re: Regex = Regex::new(r"^# (?P<title>.*)\n\n(?P<body>.*)").unwrap();
  18. static ref slug_re: Regex = Regex::new(r"(?P<slug>\S+).md").unwrap();
  19. }
  20. let title = &re.captures(&contents).expect("Couldn't parse title")["title"];
  21. let body = &re.captures(&contents).expect("Couldn't parse body")["body"];
  22. let filename = &path.file_name().unwrap().to_str().unwrap();
  23. let slug = &slug_re.captures(filename).expect("Couldn't parse slug")["slug"];
  24. Post {
  25. title: String::from(title),
  26. body: String::from(body),
  27. slug: String::from(slug),
  28. }
  29. }
  30. fn read_posts_dir(cwd: &path::PathBuf) -> fs::ReadDir {
  31. match fs::read_dir(cwd) {
  32. Ok(posts) => posts,
  33. Err(err) => panic!(err),
  34. }
  35. }
  36. fn render_post(cwd: &path::PathBuf, layout: &str, post: &Post) {
  37. let post_template = fs::read_to_string(cwd.join("templates").join("post.html"))
  38. .expect("Couldn't find post template");
  39. let output = layout.replace(
  40. "{{ contents }}",
  41. &post_template
  42. .replace("{{ title }}", &post.title)
  43. .replace("{{ body }}", &post.body),
  44. );
  45. match fs::create_dir(cwd.join("public").join(&post.slug)) {
  46. Ok(_) => {}
  47. Err(err) => match err.kind() {
  48. std::io::ErrorKind::AlreadyExists => {}
  49. _ => panic!(err),
  50. },
  51. }
  52. fs::write(
  53. cwd.join("public").join(&post.slug).join("index.html"),
  54. &output,
  55. ).expect("Unable to write file");
  56. }
  57. fn render_post_listing(cwd: &path::PathBuf, layout: &str, posts: &Vec<Post>) {
  58. let post_item_template =
  59. fs::read_to_string(cwd.join("templates").join("post_listing_item.html"))
  60. .expect("Couldn't find post listing item template");
  61. let post_listing = posts
  62. .iter()
  63. .map(|ref post| {
  64. post_item_template
  65. .replace("{{ slug }}", &post.slug)
  66. .replace("{{ title }}", &post.title)
  67. }).collect::<Vec<String>>()
  68. .join("\n");
  69. let output = layout.replace("{{ contents }}", &post_listing);
  70. fs::write(cwd.join("public").join("index.html"), &output).expect("Unable to write file");
  71. }
  72. fn main() {
  73. let cwd = env::current_dir().expect("Couldn't read current directory");
  74. match fs::create_dir(cwd.join("public")) {
  75. Ok(_) => {},
  76. Err(err) => {
  77. match err.kind() {
  78. std::io::ErrorKind::AlreadyExists => {},
  79. _ => panic!(err)
  80. }
  81. }
  82. }
  83. let layout = fs::read_to_string(&cwd.join("templates").join("layout.html"))
  84. .expect("Couldn't find layout template");
  85. let post_paths = read_posts_dir(&cwd.join("posts"));
  86. let mut posts: Vec<Post> = vec![];
  87. for path in post_paths {
  88. match path {
  89. Ok(p) => {
  90. let post = parse_post(p.path());
  91. render_post(&cwd, &layout, &post);
  92. posts.push(post);
  93. }
  94. Err(err) => panic!(err),
  95. }
  96. }
  97. render_post_listing(&cwd, &layout, &posts);
  98. }