A small, fast, static site generator
1use anyhow::Result;
2use copy_dir::copy_dir;
3use std::{
4 fs::{create_dir_all, remove_dir_all, write},
5 path::Path,
6};
7use walkdir::WalkDir;
8
9use crate::{
10 config::Config,
11 markdown::Page,
12 template::{TemplateContext, TemplateEnvironment},
13};
14
15pub struct Builder<'a> {
16 template_env: TemplateEnvironment<'a>,
17 pages: Vec<Page>,
18 config: &'a Config,
19 content_root: &'a Path,
20 static_root: &'a Path,
21 build_root: &'a Path,
22 built: bool,
23}
24
25impl<'a> Builder<'a> {
26 pub fn new(config: &'a Config) -> Self {
27 Self {
28 template_env: TemplateEnvironment::new(),
29 pages: Vec::new(),
30 config,
31 content_root: Path::new(&config.build.content_dir),
32 static_root: Path::new(&config.build.static_dir),
33 build_root: Path::new(&config.build.build_dir),
34 built: false,
35 }
36 }
37
38 pub fn clean(&self) -> Result<()> {
39 // Clean any existing build
40 if self.build_root.is_dir() {
41 remove_dir_all(self.build_root)?;
42 }
43
44 Ok(())
45 }
46
47 pub fn copy_static(&self) -> Result<()> {
48 // Copy the static content directory
49 if self.static_root.is_dir() {
50 copy_dir(
51 self.static_root,
52 self.build_root.join(&self.config.build.static_dir),
53 )?;
54 println!("Copied static content directory");
55 }
56
57 Ok(())
58 }
59
60 pub fn load_pages(&mut self) -> Result<()> {
61 for entry in WalkDir::new(self.content_root) {
62 let entry = entry?;
63 let entry_type = entry.file_type();
64 let path = entry.path();
65 let rel_path = path.strip_prefix(self.content_root)?;
66 let dst_path = self.build_root.join(rel_path);
67
68 if entry_type.is_file() {
69 let page = Page::from_file(self.config, self.content_root, &path.to_path_buf())?;
70 self.pages.push(page);
71 } else if entry_type.is_dir() {
72 create_dir_all(dst_path)?;
73 }
74 }
75
76 Ok(())
77 }
78
79 pub fn generate_pages(&mut self) -> Result<()> {
80 for i in 0..self.pages.len() {
81 let page = &self.pages[i];
82
83 if !self.config.build.drafts && page.meta.draft {
84 continue;
85 }
86
87 let ctx = TemplateContext::new(&self.pages, page);
88
89 let mut dst_path = self.build_root.join(&page.rel_path);
90 dst_path.set_extension("html");
91
92 if let Some(tmpl_name) = &page.meta.template {
93 let render_str = self.template_env.render_template(&ctx, tmpl_name)?;
94 write(&dst_path, render_str)?;
95 } else {
96 write(&dst_path, &page.content)?;
97 }
98
99 println!("Generated {}", dst_path.display());
100 }
101
102 Ok(())
103 }
104
105 pub fn rebuild(&mut self) -> Result<()> {
106 self.built = false;
107 self.template_env = TemplateEnvironment::new();
108 self.pages = Vec::new();
109
110 self.build()
111 }
112
113 pub fn build(&mut self) -> Result<()> {
114 self.clean()?;
115
116 create_dir_all(self.build_root)?;
117
118 self.copy_static()?;
119
120 self.template_env.load_templates(self.config)?;
121 self.load_pages()?;
122 self.generate_pages()?;
123
124 self.built = true;
125
126 Ok(())
127 }
128}