webdog/
lib.rs

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
24/// Source base path for normal site pages.
25pub const PAGES_PATH: &str = "pages";
26/// Source base path for site templates.
27pub const TEMPLATES_PATH: &str = "templates";
28/// Source base path for SASS stylesheets.
29pub const SASS_PATH: &str = "sass";
30/// Source base path for files which will be copied to the site root.
31pub const ROOT_PATH: &str = "root";
32/// Source base path for resources.
33pub const RESOURCES_PATH: &str = "resources";
34
35/// Struct for the site's configuration.
36#[derive(Debug, Serialize, Deserialize)]
37pub struct SiteConfig {
38	/// The location the site is at.
39	pub base_url: Url,
40	/// The site's title.
41	pub title: String,
42	/// The site's description? Not sure if this will actually be used or not
43	pub description: String,
44	/// the site's theme color for embed metadata
45	pub theme_color: String,
46	/// The site's build directory. Defaults to <site>/build if not specified.
47	pub build: Option<String>,
48	/// A list of Sass stylesheets that will be built.
49	pub sass_styles: Vec<PathBuf>,
50	/// URL to the CDN used for the site's images.
51	pub cdn_url: Url,
52	/// The path to output webdog static resources to. Defaults to "webdog"
53	pub webdog_path: Option<String>,
54	/// The theme to use for the site's code blocks.
55	/// TODO: dark/light themes
56	/// TODO: export themes as CSS instead of styling HTML directly
57	/// TODO: allow loading user themes
58	pub code_theme: String,
59
60	/// List of resources the site should build.
61	pub resources: HashMap<String, ResourceBuilderConfig>,
62}
63
64impl SiteConfig {
65	/// The filename for site config files.
66	pub const FILENAME: &str = "config.yaml";
67
68	/// Creates a new site config from the given title.
69	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	/// Gets a CDN url from the given file name.
85	pub fn cdn_url(&self, file: &str) -> eyre::Result<Url> {
86		Ok(self.cdn_url.join(file)?)
87	}
88
89	/// Checks the site config for errors.
90	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	/// Helper to read the site config from the given path.
101	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/// Struct for the front matter in templates. (nothing here yet)
113#[derive(Debug, Default, Deserialize)]
114pub struct TemplateMetadata {}
115
116/// Struct for the front matter in pages.
117#[derive(Debug, Default, Serialize, Deserialize)]
118pub struct PageMetadata {
119	/// The page's title.
120	pub title: Option<String>,
121	/// The template to use for the page. If not specified, it defaults to "base".
122	pub template: Option<String>,
123	/// custom embed info for a template
124	#[serde(default)]
125	pub embed: Option<EmbedMetadata>,
126	/// The page's custom scripts, if any.
127	#[serde(default)]
128	pub scripts: Vec<String>,
129	/// the page's custom styles, if any.
130	#[serde(default)]
131	pub styles: Vec<String>,
132	/// The extra stuff to run for the page, if any.
133	#[serde(default)]
134	pub extra: Option<ExtraData>,
135	/// Custom values passed to the base template.
136	#[serde(default)]
137	pub userdata: serde_yaml_ng::Value,
138	/// Whether this page being rendered is a partial. Set by the builder, not your page metadata.
139	#[serde(skip)]
140	pub is_partial: bool,
141}
142
143/// Struct containing information about the site.
144#[derive(Debug)]
145pub struct Site {
146	/// The path to the site.
147	pub site_path: PathBuf,
148	/// The site's configuration.
149	pub config: SiteConfig,
150	/// An index of available pages.
151	pub page_index: HashMap<String, PathBuf>,
152}
153
154impl Site {
155	/// Creates a new site from the given path.
156	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	/// Builds the site once.
188	pub fn build_once(self) -> eyre::Result<()> {
189		SiteBuilder::new(self, false)?.prepare()?.build_all()
190	}
191}