lssg_lib/
lib.rs

1//! Lyr's Static Site Generator
2//!
3//! This is a static site generator I wrote mostly for personal use but can also be fitted to work for anyone else.
4//!
5//!
6//! # Examples on how to use this crate
7//! ```rs
8//! let input = Input::from_string("./test.md")
9//! let output = Input::from_string("./build")
10//! let mut lssg = Lssg::new(input, output);
11//! // Add modules
12//! lssg.add_module(ExternalModule::new());
13//! lssg.add_module(BlogModule::new());
14//! lssg.add_module(DefaultModule::new());
15//! // Render code to the folder
16//! lssg.render().unwrap()
17//! ```
18pub mod char_reader;
19pub mod lmarkdown;
20pub mod parse_error;
21pub mod renderer;
22pub mod sitetree;
23
24mod domnode_to_token;
25pub mod lssg_error;
26mod path_extension;
27mod tree;
28
29use std::{
30    fs::{create_dir, create_dir_all, remove_dir_all, write},
31    path::PathBuf,
32};
33
34use log::info;
35use lssg_error::LssgError;
36use renderer::{Renderer, RendererModule};
37use sitetree::Input;
38
39use crate::{
40    path_extension::PathExtension,
41    sitetree::{Relation, SiteId, SiteNodeKind, SiteTree},
42};
43
44pub struct Lssg {
45    input: Input,
46    output_directory: PathBuf,
47    renderer: Renderer,
48}
49
50impl Lssg {
51    pub fn new(input: Input, output_directory: PathBuf) -> Lssg {
52        let renderer = Renderer::default();
53        Lssg {
54            input,
55            output_directory,
56            renderer,
57        }
58    }
59
60    pub fn add_module(&mut self, module: impl RendererModule + 'static) {
61        self.renderer.add_module(module)
62    }
63
64    pub fn render(&mut self) -> Result<(), LssgError> {
65        info!("Generating SiteTree");
66        let mut site_tree = SiteTree::from_input(self.input.clone())?;
67
68        self.renderer.init(&mut site_tree);
69        info!("SiteTree:\n{site_tree}");
70
71        // site_tree.minify();
72
73        self.renderer.after_init(&site_tree);
74
75        if self.output_directory.exists() {
76            info!(
77                "Removing {:?}",
78                self.output_directory.canonicalize_nonexistent_path()
79            );
80            remove_dir_all(&self.output_directory)?;
81        }
82        info!(
83            "Creating {:?}",
84            self.output_directory.canonicalize_nonexistent_path()
85        );
86        create_dir_all(&self.output_directory)?;
87
88        let mut queue: Vec<SiteId> = vec![site_tree.root()];
89        while let Some(site_id) = queue.pop() {
90            queue.append(&mut site_tree[site_id].children.clone());
91            let rel_path = site_tree.rel_path(site_tree.root(), site_id);
92            let path = self
93                .output_directory
94                .join(rel_path)
95                .canonicalize_nonexistent_path();
96            match &mut site_tree[site_id].kind {
97                SiteNodeKind::Stylesheet(stylesheet) => {
98                    let mut stylesheet = stylesheet.clone();
99
100                    // update resources to stylesheet sitenode path
101                    for link in site_tree.links_from(site_id) {
102                        if let Relation::Discovered { raw_path } = &link.relation {
103                            let updated_resource = site_tree.rel_path(
104                                site_tree[site_id]
105                                    .parent
106                                    .expect("stylesheet must have parent"),
107                                link.to,
108                            );
109                            stylesheet.update_resource(raw_path, &updated_resource);
110                        }
111                    }
112
113                    stylesheet.write(&path)?;
114                }
115                SiteNodeKind::Resource(resource) => {
116                    resource.write(&path)?;
117                }
118                SiteNodeKind::Folder => {
119                    info!("Creating folder {path:?}",);
120                    create_dir(path)?;
121                }
122                SiteNodeKind::Page { .. } => {
123                    let html = self.renderer.render(&site_tree, site_id)?;
124                    create_dir_all(&path)?;
125                    let html_output_path = path.join("index.html").canonicalize_nonexistent_path();
126
127                    info!(
128                        "Writing to {:?}",
129                        html_output_path.canonicalize_nonexistent_path()
130                    );
131                    write(html_output_path, html)?;
132                }
133            }
134        }
135
136        info!("All files written");
137
138        Ok(())
139    }
140}