Skip to main content

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(PostModule::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;
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, renderer: Renderer) -> Lssg {
52        Lssg {
53            input,
54            output_directory,
55            renderer,
56        }
57    }
58
59    pub fn render(&mut self) -> Result<(), LssgError> {
60        info!("Generating SiteTree");
61        let mut site_tree = SiteTree::from_input(self.input.clone())?;
62
63        self.renderer.init(&mut site_tree);
64        info!("SiteTree:\n{site_tree}");
65
66        // site_tree.minify();
67
68        self.renderer.after_init(&site_tree);
69
70        if self.output_directory.exists() {
71            info!(
72                "Removing {:?}",
73                self.output_directory.canonicalize_nonexistent_path()
74            );
75            remove_dir_all(&self.output_directory)?;
76        }
77        info!(
78            "Creating {:?}",
79            self.output_directory.canonicalize_nonexistent_path()
80        );
81        create_dir_all(&self.output_directory)?;
82
83        let mut queue: Vec<SiteId> = vec![site_tree.root()];
84        while let Some(site_id) = queue.pop() {
85            queue.append(&mut site_tree[site_id].children.clone());
86            let rel_path = site_tree.rel_path(site_tree.root(), site_id);
87            let path = self
88                .output_directory
89                .join(rel_path)
90                .canonicalize_nonexistent_path();
91            match &site_tree[site_id].kind {
92                SiteNodeKind::Javascript(javascript) => {
93                    let mut javascript = javascript.clone();
94
95                    // update javascript import paths
96                    for link in site_tree.links_from(site_id) {
97                        if let Relation::Discovered { raw_path } = &link.relation {
98                            let updated_resource = site_tree.rel_path(
99                                site_tree[site_id]
100                                    .parent
101                                    .expect("stylesheet must have parent"),
102                                link.to,
103                            );
104                            javascript.update_resource(raw_path, &updated_resource);
105                        }
106                    }
107
108                    javascript.write(&path)?;
109                }
110                SiteNodeKind::Stylesheet(stylesheet) => {
111                    let mut stylesheet = stylesheet.clone();
112
113                    // update stylesheet imports in content
114                    for link in site_tree.links_from(site_id) {
115                        if let Relation::Discovered { raw_path } = &link.relation {
116                            let updated_resource = site_tree.rel_path(
117                                site_tree[site_id]
118                                    .parent
119                                    .expect("stylesheet must have parent"),
120                                link.to,
121                            );
122                            stylesheet.update_resource(raw_path, &updated_resource);
123                        }
124                    }
125
126                    stylesheet.write(&path)?;
127                }
128                SiteNodeKind::Resource(resource) => {
129                    if let Err(e) = resource.write(&path) {
130                        log::error!("Failed to write resource to {path:?}: {e}")
131                    }
132                }
133                SiteNodeKind::Folder => {
134                    info!("Creating folder {path:?}",);
135                    create_dir(path)?;
136                }
137                SiteNodeKind::Page { .. } => {
138                    let html = self.renderer.render(&site_tree, site_id)?;
139                    create_dir_all(&path)?;
140                    let html_output_path = path.join("index.html").canonicalize_nonexistent_path();
141
142                    info!(
143                        "Writing to {:?}",
144                        html_output_path.canonicalize_nonexistent_path()
145                    );
146                    write(html_output_path, html)?;
147                }
148            }
149        }
150
151        info!("All files written");
152
153        Ok(())
154    }
155}