mod builder; mod extras; pub mod frontmatter; mod link_list; pub mod resource; #[cfg(feature = "serve")] pub mod serving; mod util; use std::{ collections::HashMap, path::{Path, PathBuf}, }; use extras::ExtraData; use eyre::Context; use resource::{EmbedMetadata, ResourceBuilderConfig}; use serde::{Deserialize, Serialize}; use url::Url; use walkdir::WalkDir; use builder::SiteBuilder; /// Source base path for normal site pages. pub const PAGES_PATH: &str = "pages"; /// Source base path for site templates. pub const TEMPLATES_PATH: &str = "templates"; /// Source base path for SASS stylesheets. pub const SASS_PATH: &str = "sass"; /// Source base path for files which will be copied to the site root. pub const ROOT_PATH: &str = "root"; /// Source base path for resources. pub const RESOURCES_PATH: &str = "resources"; /// Struct for the site's configuration. #[derive(Debug, Serialize, Deserialize)] pub struct SiteConfig { /// The location the site is at. pub base_url: Url, /// The site's title. pub title: String, /// The site's description? Not sure if this will actually be used or not pub description: String, /// the site's theme color for embed metadata pub theme_color: String, /// The site's build directory. Defaults to /build if not specified. pub build: Option, /// A list of Sass stylesheets that will be built. pub sass_styles: Vec, /// URL to the CDN used for the site's images. pub cdn_url: Url, /// The theme to use for the site's code blocks. /// TODO: dark/light themes /// TODO: export themes as CSS instead of styling HTML directly /// TODO: allow loading user themes pub code_theme: String, /// List of resources the site should build. pub resources: HashMap, } impl SiteConfig { /// The filename for site config files. pub const FILENAME: &str = "config.yaml"; /// Creates a new site config from the given title. pub fn new(base_url: Url, cdn_url: Url, title: String) -> Self { Self { base_url, title, description: Default::default(), theme_color: "#ffc4fc".to_string(), build: None, sass_styles: vec!["index.scss".into()], cdn_url, code_theme: "base16-ocean.dark".to_string(), resources: Default::default(), } } /// Gets a CDN url from the given file name. pub fn cdn_url(&self, file: &str) -> eyre::Result { Ok(self.cdn_url.join(file)?) } /// Checks the site config for errors. pub fn check(&self, builder: &SiteBuilder) -> eyre::Result<()> { builder .theme_set .themes .contains_key(&self.code_theme) .then_some(()) .ok_or_else(|| eyre::eyre!("missing code theme: {}", self.code_theme))?; Ok(()) } /// Helper to read the site config from the given path. pub fn read(site_path: &Path) -> eyre::Result { let config_path = site_path.join(SiteConfig::FILENAME); if !config_path.exists() { eyre::bail!("no site config found!"); } Ok(serde_yaml_ng::from_str(&std::fs::read_to_string( config_path, )?)?) } } /// Struct for the front matter in templates. (nothing here yet) #[derive(Debug, Default, Deserialize)] pub struct TemplateMetadata {} /// Struct for the front matter in pages. #[derive(Debug, Default, Serialize, Deserialize)] pub struct PageMetadata { /// The page's title. pub title: Option, /// The template to use for the page. If not specified, it defaults to "base". pub template: Option, /// custom embed info for a template #[serde(default)] pub embed: Option, /// The page's custom scripts, if any. #[serde(default)] pub scripts: Vec, /// the page's custom styles, if any. #[serde(default)] pub styles: Vec, /// The extra stuff to run for the page, if any. #[serde(default)] pub extra: Option, /// Custom values passed to the base template. #[serde(default)] pub userdata: serde_yaml_ng::Value, /// Whether this page being rendered is a partial. Set by the builder, not your page metadata. #[serde(skip)] pub is_partial: bool, } /// Struct containing information about the site. #[derive(Debug)] pub struct Site { /// The path to the site. pub site_path: PathBuf, /// The site's configuration. pub config: SiteConfig, /// An index of available pages. pub page_index: HashMap, } impl Site { /// Creates a new site from the given path. pub fn new(site_path: &Path) -> eyre::Result { let config = SiteConfig::read(site_path)?; let mut page_index = HashMap::new(); let pages_path = site_path.join(PAGES_PATH); for entry in WalkDir::new(&pages_path).into_iter() { let entry = entry.wrap_err("Failed to read page entry")?; let path = entry.path(); if let Some(ext) = path.extension() { if ext == "md" && entry.file_type().is_file() { page_index.insert( path.strip_prefix(&pages_path) .wrap_err("This really shouldn't have happened")? .with_extension("") .to_string_lossy() .to_string(), path.to_owned(), ); } } } Ok(Self { site_path: site_path.to_owned(), config, page_index, }) } /// Builds the site once. pub fn build_once(self) -> eyre::Result<()> { SiteBuilder::new(self, false)?.prepare()?.build_all() } }