1mod builder;
2mod extras;
3pub mod frontmatter;
4mod link_list;
5pub mod resource;
6#[cfg(feature = "serve")]
7pub mod serving;
8mod util;
9
10use std::{
11 collections::HashMap,
12 path::{Path, PathBuf},
13};
14
15use extras::ExtraData;
16use eyre::Context;
17use resource::{EmbedMetadata, ResourceBuilderConfig};
18use serde::{Deserialize, Serialize};
19use url::Url;
20use walkdir::WalkDir;
21
22use builder::SiteBuilder;
23
24pub const PAGES_PATH: &str = "pages";
26pub const TEMPLATES_PATH: &str = "templates";
28pub const SASS_PATH: &str = "sass";
30pub const ROOT_PATH: &str = "root";
32pub const RESOURCES_PATH: &str = "resources";
34
35#[derive(Debug, Serialize, Deserialize)]
37pub struct SiteConfig {
38 pub base_url: Url,
40 pub title: String,
42 pub description: String,
44 pub theme_color: String,
46 pub build: Option<String>,
48 pub sass_styles: Vec<PathBuf>,
50 pub cdn_url: Url,
52 pub webdog_path: Option<String>,
54 pub code_theme: String,
59
60 pub resources: HashMap<String, ResourceBuilderConfig>,
62}
63
64impl SiteConfig {
65 pub const FILENAME: &str = "config.yaml";
67
68 pub fn new(base_url: Url, cdn_url: Url, title: String) -> Self {
70 Self {
71 base_url,
72 title,
73 description: Default::default(),
74 theme_color: "#ffc4fc".to_string(),
75 build: None,
76 sass_styles: vec!["index.scss".into()],
77 cdn_url,
78 webdog_path: None,
79 code_theme: "base16-ocean.dark".to_string(),
80 resources: Default::default(),
81 }
82 }
83
84 pub fn cdn_url(&self, file: &str) -> eyre::Result<Url> {
86 Ok(self.cdn_url.join(file)?)
87 }
88
89 pub fn check(&self, builder: &SiteBuilder) -> eyre::Result<()> {
91 builder
92 .theme_set
93 .themes
94 .contains_key(&self.code_theme)
95 .then_some(())
96 .ok_or_else(|| eyre::eyre!("missing code theme: {}", self.code_theme))?;
97 Ok(())
98 }
99
100 pub fn read(site_path: &Path) -> eyre::Result<Self> {
102 let config_path = site_path.join(SiteConfig::FILENAME);
103 if !config_path.exists() {
104 eyre::bail!("no site config found!");
105 }
106 Ok(serde_yaml_ng::from_str(&std::fs::read_to_string(
107 config_path,
108 )?)?)
109 }
110}
111
112#[derive(Debug, Default, Deserialize)]
114pub struct TemplateMetadata {}
115
116#[derive(Debug, Default, Serialize, Deserialize)]
118pub struct PageMetadata {
119 pub title: Option<String>,
121 pub template: Option<String>,
123 #[serde(default)]
125 pub embed: Option<EmbedMetadata>,
126 #[serde(default)]
128 pub scripts: Vec<String>,
129 #[serde(default)]
131 pub styles: Vec<String>,
132 #[serde(default)]
134 pub extra: Option<ExtraData>,
135 #[serde(default)]
137 pub userdata: serde_yaml_ng::Value,
138 #[serde(skip)]
140 pub is_partial: bool,
141}
142
143#[derive(Debug)]
145pub struct Site {
146 pub site_path: PathBuf,
148 pub config: SiteConfig,
150 pub page_index: HashMap<String, PathBuf>,
152}
153
154impl Site {
155 pub fn new(site_path: &Path) -> eyre::Result<Self> {
157 let config = SiteConfig::read(site_path)?;
158
159 let mut page_index = HashMap::new();
160 let pages_path = site_path.join(PAGES_PATH);
161 for entry in WalkDir::new(&pages_path).into_iter() {
162 let entry = entry.wrap_err("Failed to read page entry")?;
163 let path = entry.path();
164
165 if let Some(ext) = path.extension()
166 && ext == "md"
167 && entry.file_type().is_file()
168 {
169 page_index.insert(
170 path.strip_prefix(&pages_path)
171 .wrap_err("This really shouldn't have happened")?
172 .with_extension("")
173 .to_string_lossy()
174 .to_string(),
175 path.to_owned(),
176 );
177 }
178 }
179
180 Ok(Self {
181 site_path: site_path.to_owned(),
182 config,
183 page_index,
184 })
185 }
186
187 pub fn build_once(self) -> eyre::Result<()> {
189 SiteBuilder::new(self, false)?.prepare()?.build_all()
190 }
191}