diff --git a/Cargo.lock b/Cargo.lock
index 9f77f3c..17bf5f6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,6 +8,17 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
+[[package]]
+name = "ahash"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43bb833f0bf979d8475d38fbf09ed3b8a55e1885fe93ad3f93239fc6a4f17b98"
+dependencies = [
+ "getrandom 0.2.3",
+ "once_cell",
+ "version_check",
+]
+
[[package]]
name = "aho-corasick"
version = "0.7.18"
@@ -17,12 +28,32 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi 0.3.9",
+]
+
[[package]]
name = "anyhow"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi 0.3.9",
+]
+
[[package]]
name = "autocfg"
version = "0.1.7"
@@ -41,6 +72,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+[[package]]
+name = "beef"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736"
+
[[package]]
name = "bitflags"
version = "1.3.2"
@@ -117,6 +154,21 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
[[package]]
name = "cloudabi"
version = "0.0.3"
@@ -126,6 +178,12 @@ dependencies = [
"bitflags",
]
+[[package]]
+name = "codemap"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e769b5c8c8283982a987c6e948e540254f1058d5a74b8794914d4ef5fc2a24"
+
[[package]]
name = "cpufeatures"
version = "0.1.5"
@@ -146,7 +204,7 @@ dependencies = [
"dtoa-short",
"itoa",
"matches",
- "phf",
+ "phf 0.7.24",
"proc-macro2",
"procedural-masquerade",
"quote",
@@ -440,6 +498,25 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1",
]
+[[package]]
+name = "grass"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82317908bc4204532d098390f8e041693aaeab95cf7351f774bdacf253b1c8ed"
+dependencies = [
+ "beef",
+ "clap",
+ "codemap",
+ "indexmap",
+ "lasso",
+ "num-bigint",
+ "num-rational",
+ "num-traits",
+ "once_cell",
+ "phf 0.9.0",
+ "rand 0.8.4",
+]
+
[[package]]
name = "gray_matter"
version = "0.2.0"
@@ -493,7 +570,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
dependencies = [
- "ahash",
+ "ahash 0.4.7",
]
[[package]]
@@ -501,6 +578,9 @@ name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+dependencies = [
+ "ahash 0.7.4",
+]
[[package]]
name = "headers"
@@ -685,6 +765,15 @@ dependencies = [
"winapi-build",
]
+[[package]]
+name = "lasso"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8647c8a01e5f7878eacb2c323c4c949fdb63773110f0686c7810769874b7e0a"
+dependencies = [
+ "hashbrown 0.11.2",
+]
+
[[package]]
name = "lazy_static"
version = "1.4.0"
@@ -904,6 +993,48 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "num-bigint"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e0d047c1062aa51e256408c560894e5251f08925980e53cf1aa5bd00eec6512"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
+dependencies = [
+ "autocfg 1.0.1",
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg 1.0.1",
+]
+
[[package]]
name = "num_cpus"
version = "1.13.0"
@@ -914,6 +1045,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+
[[package]]
name = "opaque-debug"
version = "0.2.3"
@@ -981,7 +1118,18 @@ version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
dependencies = [
- "phf_shared",
+ "phf_shared 0.7.24",
+]
+
+[[package]]
+name = "phf"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37"
+dependencies = [
+ "phf_macros",
+ "phf_shared 0.9.0",
+ "proc-macro-hack",
]
[[package]]
@@ -990,8 +1138,8 @@ version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
dependencies = [
- "phf_generator",
- "phf_shared",
+ "phf_generator 0.7.24",
+ "phf_shared 0.7.24",
]
[[package]]
@@ -1000,17 +1148,50 @@ version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
dependencies = [
- "phf_shared",
+ "phf_shared 0.7.24",
"rand 0.6.5",
]
+[[package]]
+name = "phf_generator"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082"
+dependencies = [
+ "phf_shared 0.9.0",
+ "rand 0.8.4",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b706f5936eb50ed880ae3009395b43ed19db5bff2ebd459c95e7bf013a89ab86"
+dependencies = [
+ "phf_generator 0.9.1",
+ "phf_shared 0.9.0",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "phf_shared"
version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
dependencies = [
- "siphasher",
+ "siphasher 0.2.3",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9"
+dependencies = [
+ "siphasher 0.3.6",
]
[[package]]
@@ -1385,7 +1566,7 @@ dependencies = [
"fxhash",
"log",
"matches",
- "phf",
+ "phf 0.7.24",
"phf_codegen",
"precomputed-hash",
"servo_arc",
@@ -1489,6 +1670,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
+[[package]]
+name = "siphasher"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "729a25c17d72b06c68cb47955d44fda88ad2d3e7d77e025663fdd69b93dd71a1"
+
[[package]]
name = "slab"
version = "0.4.4"
@@ -1520,6 +1707,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
[[package]]
name = "syn"
version = "1.0.74"
@@ -1545,6 +1738,15 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
[[package]]
name = "thin-slice"
version = "0.1.1"
@@ -1768,6 +1970,12 @@ dependencies = [
"tinyvec",
]
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
[[package]]
name = "unicode-xid"
version = "0.2.2"
@@ -1792,6 +2000,12 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
[[package]]
name = "version_check"
version = "0.9.3"
@@ -1930,6 +2144,7 @@ dependencies = [
"extract-frontmatter",
"fs_extra",
"futures",
+ "grass",
"gray_matter",
"handlebars",
"hotwatch",
diff --git a/Cargo.toml b/Cargo.toml
index c7502dc..cdcf2a1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ anyhow = "1"
extract-frontmatter = "2.1"
fs_extra = "1.2"
futures = { version = "0.3", optional = true }
+grass = "0.10"
gray_matter = "0.2"
handlebars = "4.1"
hotwatch = { version = "0.4", optional = true }
diff --git a/site/config.yaml b/site/config.yaml
index 7f3005e..8e929d7 100644
--- a/site/config.yaml
+++ b/site/config.yaml
@@ -1,3 +1,4 @@
base_url: "https://zoey.dev"
title: zoey.dev
description: "Zoey's site."
+sass_styles: [index.scss]
diff --git a/site/static/site.css b/site/sass/index.scss
similarity index 76%
rename from site/static/site.css
rename to site/sass/index.scss
index bf52da3..600e3a6 100644
--- a/site/static/site.css
+++ b/site/sass/index.scss
@@ -11,11 +11,11 @@ header.main-header {
a {
text-decoration: none;
-}
-a:hover,
-a:focus {
- text-decoration: underline;
+ &:hover,
+ &:focus {
+ text-decoration: underline;
+ }
}
main.page {
diff --git a/site/templates/base.hbs b/site/templates/base.hbs
index 65bc59e..1e77d8c 100644
--- a/site/templates/base.hbs
+++ b/site/templates/base.hbs
@@ -4,7 +4,7 @@
{{{head}}}
-
+
diff --git a/src/builder.rs b/src/builder.rs
new file mode 100644
index 0000000..d5f8a8a
--- /dev/null
+++ b/src/builder.rs
@@ -0,0 +1,216 @@
+//! Module containing the site builder.
+
+use std::{path::PathBuf, str::FromStr};
+
+use anyhow::Context;
+use gray_matter::{engine::YAML, Matter};
+use handlebars::Handlebars;
+use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings};
+use pulldown_cmark::{Options, Parser};
+use serde::Serialize;
+use walkdir::WalkDir;
+use warp::hyper::Uri;
+
+use crate::{util, PageMetadata, Site, SASS_PATH, STATIC_PATH};
+
+/// Struct containing data to be sent to templates when rendering them.
+#[derive(Debug, Serialize)]
+struct TemplateData<'a> {
+ /// The rendered page.
+ pub page: &'a str,
+}
+
+/// Struct used to build the site.
+pub struct SiteBuilder<'a> {
+ /// The matter instance used to extract front matter.
+ matter: Matter,
+ /// The Handlebars registry used to render templates.
+ pub(crate) reg: Handlebars<'a>,
+ /// The site info used to build the site.
+ pub site: Site,
+ /// The path to the build directory.
+ pub build_path: PathBuf,
+ /// Whether the site is going to be served locally with the dev server.
+ serving: bool,
+}
+
+impl<'a> SiteBuilder<'a> {
+ /// Creates a new site builder.
+ pub fn new(site: Site, serving: bool) -> Self {
+ let mut build_path = match &site.config.build {
+ Some(build) => site.site_path.join(build),
+ _ => site.site_path.join("build"),
+ };
+ if serving {
+ build_path = site.site_path.join("build");
+ }
+
+ Self {
+ matter: Matter::new(),
+ reg: Handlebars::new(),
+ site,
+ build_path,
+ serving,
+ }
+ }
+
+ /// Prepares the site builder for use.
+ pub fn prepare(mut self) -> anyhow::Result {
+ let build_static_path = self.build_path.join(STATIC_PATH);
+ if std::fs::try_exists(&self.build_path)
+ .context("Failed check if build directory exists")?
+ {
+ if build_static_path.exists() {
+ std::fs::remove_dir_all(&build_static_path)
+ .context("Failed to remove old static directory")?;
+ }
+ for entry in WalkDir::new(&self.build_path) {
+ let entry = entry?;
+ let path = entry.path();
+ if let Some(ext) = path.extension() {
+ if ext == "html" {
+ std::fs::remove_file(path).with_context(|| {
+ format!("Failed to remove file at {}", path.display())
+ })?;
+ }
+ }
+ }
+ } else {
+ std::fs::create_dir(&self.build_path).context("Failed to create build directory")?;
+ }
+
+ for (template_name, template_path) in &self.site.template_index {
+ self.reg
+ .register_template_file(template_name, template_path)
+ .context("Failed to register template file")?;
+ }
+
+ let static_path = self.site.site_path.join(STATIC_PATH);
+ if static_path.exists() {
+ fs_extra::copy_items(
+ &[static_path],
+ &self.build_path,
+ &fs_extra::dir::CopyOptions::default(),
+ )
+ .context("Failed to copy static directory")?;
+ }
+
+ Ok(self)
+ }
+
+ /// Builds a page.
+ pub fn build_page(&self, page_name: &str) -> anyhow::Result<()> {
+ let page_path = self.site.page_index.get(page_name).unwrap();
+
+ let input = std::fs::read_to_string(page_path)
+ .with_context(|| format!("Failed to read page at {}", page_path.display()))?;
+ let page = self.matter.parse(&input);
+ let page_metadata = if let Some(data) = page.data {
+ data.deserialize()?
+ } else {
+ PageMetadata::default()
+ };
+
+ let parser = Parser::new_ext(&page.content, Options::all());
+ let mut page_html = String::new();
+ pulldown_cmark::html::push_html(&mut page_html, parser);
+
+ let out = self.reg.render(
+ &page_metadata.template.unwrap_or_else(|| "base".to_string()),
+ &TemplateData { page: &page_html },
+ )?;
+
+ let title = match &page_metadata.title {
+ Some(page_title) => format!("{} / {}", self.site.config.title, page_title),
+ _ => self.site.config.title.clone(),
+ };
+
+ let mut output = Vec::new();
+ let mut rewriter = HtmlRewriter::new(
+ Settings {
+ element_content_handlers: vec![
+ element!("head", |el| {
+ el.prepend(r#""#, ContentType::Html);
+ el.append(&format!("{}", title), ContentType::Html);
+ if self.serving {
+ el.append(
+ &format!(r#""#, STATIC_PATH),
+ ContentType::Html,
+ );
+ } else {
+ el.append(
+ &format!(r#""#, &self.site.config.base_url),
+ ContentType::Html,
+ );
+ }
+
+ Ok(())
+ }),
+ element!("a", |el| {
+ if let Some(href) = el.get_attribute("href") {
+ if let Ok(uri) = Uri::from_str(&href) {
+ if uri.host().is_some() {
+ el.set_attribute("rel", "noopener noreferrer")?;
+ el.set_attribute("target", "_blank")?;
+ }
+ }
+ }
+
+ Ok(())
+ }),
+ ],
+ ..Default::default()
+ },
+ |c: &[u8]| output.extend_from_slice(c),
+ );
+
+ rewriter.write(out.as_bytes())?;
+ rewriter.end()?;
+
+ let out = String::from_utf8(output)?;
+
+ let out_path = self.build_path.join(page_name).with_extension("html");
+ std::fs::create_dir_all(out_path.parent().unwrap())
+ .with_context(|| format!("Failed to create directory for page {}", page_name))?;
+ std::fs::write(&out_path, out).with_context(|| {
+ format!(
+ "Failed to create page file at {} for page {}",
+ out_path.display(),
+ page_name
+ )
+ })?;
+
+ Ok(())
+ }
+
+ /// Builds the Sass styles in the site.
+ pub fn build_sass(&self) -> anyhow::Result<()> {
+ let styles_path = self.build_path.join("styles");
+ if self.serving {
+ util::remove_dir_contents(&styles_path)
+ .context("Failed to remove old contents of styles directory")?;
+ }
+ let sass_path = self.site.site_path.join(SASS_PATH);
+ for sheet in &self.site.config.sass_styles {
+ let sheet_path = sass_path.join(sheet);
+ if let Some(sheet_path) = sheet_path.to_str() {
+ match grass::from_path(sheet_path, &grass::Options::default()) {
+ Ok(css) => {
+ std::fs::write(styles_path.join(sheet).with_extension("css"), css)
+ .with_context(|| {
+ format!("Failed to write new CSS file for Sass: {:?}", sheet)
+ })?;
+ }
+ Err(e) => eprintln!(
+ "Failed to compile Sass stylesheet at {:?}: {}",
+ sheet_path, e
+ ),
+ }
+ } else {
+ eprintln!("Sass stylesheet contains invalid UTF-8: {:?}", sheet_path);
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 6ef8411..60bd90d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,27 +1,26 @@
#![feature(path_try_exists)]
#![feature(async_closure)]
+mod builder;
#[cfg(feature = "serve")]
pub mod serving;
+mod util;
use std::{
collections::HashMap,
path::{Path, PathBuf},
- str::FromStr,
};
use anyhow::Context;
-use gray_matter::{engine::yaml::YAML, matter::Matter};
-use handlebars::Handlebars;
-use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings};
-use pulldown_cmark::{Options, Parser};
-use serde::{Deserialize, Serialize};
+use serde::Deserialize;
use walkdir::WalkDir;
-use warp::hyper::Uri;
+
+use builder::SiteBuilder;
const PAGES_PATH: &str = "pages";
const TEMPLATES_PATH: &str = "templates";
const STATIC_PATH: &str = "static";
+const SASS_PATH: &str = "sass";
/// Struct for the site's configuration.
#[derive(Debug, Deserialize)]
@@ -34,19 +33,14 @@ pub struct SiteConfig {
pub description: 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,
}
/// Struct for the front matter in templates. (nothing here yet)
#[derive(Debug, Default, Deserialize)]
pub struct TemplateMetadata {}
-/// Struct containing data to be sent to templates when rendering them.
-#[derive(Debug, Serialize)]
-struct TemplateData<'a> {
- /// The rendered page.
- pub page: &'a str,
-}
-
/// Struct for the front matter in pages.
#[derive(Debug, Default, Deserialize)]
pub struct PageMetadata {
@@ -131,6 +125,7 @@ impl Site {
let builder = SiteBuilder::new(self, false).prepare()?;
builder.site.build_all_pages(&builder)?;
+ builder.build_sass()?;
Ok(())
}
@@ -144,168 +139,3 @@ impl Site {
Ok(())
}
}
-
-/// Struct used to build the site.
-struct SiteBuilder<'a> {
- /// The matter instance used to extract front matter.
- matter: Matter,
- /// The Handlebars registry used to render templates.
- reg: Handlebars<'a>,
- /// The site info used to build the site.
- site: Site,
- /// The path to the build directory.
- build_path: PathBuf,
- /// Whether the site is going to be served locally with the dev server.
- serving: bool,
-}
-
-impl<'a> SiteBuilder<'a> {
- /// Creates a new site builder.
- pub fn new(site: Site, serving: bool) -> Self {
- let mut build_path = match &site.config.build {
- Some(build) => site.site_path.join(build),
- _ => site.site_path.join("build"),
- };
- if serving {
- build_path = site.site_path.join("build");
- }
-
- Self {
- matter: Matter::new(),
- reg: Handlebars::new(),
- site,
- build_path,
- serving,
- }
- }
-
- /// Prepares the site builder for use.
- pub fn prepare(mut self) -> anyhow::Result {
- if std::fs::try_exists(&self.build_path)
- .context("Failed check if build directory exists")?
- {
- std::fs::remove_dir_all(self.build_path.join(STATIC_PATH))
- .context("Failed to remove static directory")?;
- for entry in WalkDir::new(&self.build_path) {
- let entry = entry?;
- let path = entry.path();
- if let Some(ext) = path.extension() {
- if ext == "html" {
- std::fs::remove_file(path).with_context(|| {
- format!("Failed to remove file at {}", path.display())
- })?;
- }
- }
- }
- } else {
- std::fs::create_dir(&self.build_path).context("Failed to create build directory")?;
- }
-
- for (template_name, template_path) in &self.site.template_index {
- self.reg
- .register_template_file(template_name, template_path)
- .context("Failed to register template file")?;
- }
-
- fs_extra::copy_items(
- &[self.site.site_path.join(STATIC_PATH)],
- &self.build_path,
- &fs_extra::dir::CopyOptions::default(),
- )
- .context("Failed to copy static directory")?;
-
- if self.serving {
- std::fs::write(
- self.build_path.join(format!("{}/_dev.js", STATIC_PATH)),
- include_str!("./refresh_websocket.js"),
- )?;
- }
-
- Ok(self)
- }
-
- /// Builds a page.
- pub fn build_page(&self, page_name: &str) -> anyhow::Result<()> {
- let page_path = self.site.page_index.get(page_name).unwrap();
-
- let input = std::fs::read_to_string(page_path)
- .with_context(|| format!("Failed to read page at {}", page_path.display()))?;
- let page = self.matter.parse(&input);
- let page_metadata = if let Some(data) = page.data {
- data.deserialize()?
- } else {
- PageMetadata::default()
- };
-
- let parser = Parser::new_ext(&page.content, Options::all());
- let mut page_html = String::new();
- pulldown_cmark::html::push_html(&mut page_html, parser);
-
- let out = self.reg.render(
- &page_metadata.template.unwrap_or_else(|| "base".to_string()),
- &TemplateData { page: &page_html },
- )?;
-
- let title = match &page_metadata.title {
- Some(page_title) => format!("{} / {}", self.site.config.title, page_title),
- _ => self.site.config.title.clone(),
- };
-
- let mut output = Vec::new();
- let mut rewriter = HtmlRewriter::new(
- Settings {
- element_content_handlers: vec![
- element!("head", |el| {
- el.prepend(r#""#, ContentType::Html);
- el.append(&format!("{}", title), ContentType::Html);
- if self.serving {
- el.append(
- &format!(r#""#, STATIC_PATH),
- ContentType::Html,
- );
- } else {
- el.append(
- &format!(r#""#, &self.site.config.base_url),
- ContentType::Html,
- );
- }
-
- Ok(())
- }),
- element!("a", |el| {
- if let Some(href) = el.get_attribute("href") {
- if let Ok(uri) = Uri::from_str(&href) {
- if uri.host().is_some() {
- el.set_attribute("rel", "noopener noreferrer")?;
- el.set_attribute("target", "_blank")?;
- }
- }
- }
-
- Ok(())
- }),
- ],
- ..Default::default()
- },
- |c: &[u8]| output.extend_from_slice(c),
- );
-
- rewriter.write(out.as_bytes())?;
- rewriter.end()?;
-
- let out = String::from_utf8(output)?;
-
- let out_path = self.build_path.join(page_name).with_extension("html");
- std::fs::create_dir_all(out_path.parent().unwrap())
- .with_context(|| format!("Failed to create directory for page {}", page_name))?;
- std::fs::write(&out_path, out).with_context(|| {
- format!(
- "Failed to create page file at {} for page {}",
- out_path.display(),
- page_name
- )
- })?;
-
- Ok(())
- }
-}
diff --git a/src/serving.rs b/src/serving.rs
index 502e17f..3646572 100644
--- a/src/serving.rs
+++ b/src/serving.rs
@@ -7,6 +7,7 @@ use std::{
sync::{Arc, Mutex},
};
+use anyhow::Context;
use futures::SinkExt;
use hotwatch::{Event, Hotwatch};
use warp::{
@@ -17,7 +18,7 @@ use warp::{
Filter,
};
-use crate::{Site, SiteBuilder, PAGES_PATH, STATIC_PATH, TEMPLATES_PATH};
+use crate::{Site, SiteBuilder, PAGES_PATH, SASS_PATH, STATIC_PATH, TEMPLATES_PATH};
fn with_build_path(
build_path: PathBuf,
@@ -44,6 +45,9 @@ fn create(
relative_path: &Path,
build: bool,
) -> anyhow::Result<()> {
+ if path.is_dir() {
+ return Ok(());
+ }
if let Ok(page_path) = relative_path.strip_prefix(PAGES_PATH) {
let (_page_name, page_name_str) = get_name(page_path);
@@ -66,25 +70,40 @@ fn create(
let new_config = serde_yaml::from_str(&std::fs::read_to_string(path)?)?;
builder.site.config = new_config;
builder.site.build_all_pages(builder)?;
+ } else if let Ok(_sass_path) = relative_path.strip_prefix(SASS_PATH) {
+ if build {
+ builder.build_sass().context("Failed to rebuild Sass")?;
+ }
}
Ok(())
}
/// Removes an existing resource.
-fn remove(builder: &mut SiteBuilder, _path: &Path, relative_path: &Path) -> anyhow::Result<()> {
+fn remove(builder: &mut SiteBuilder, path: &Path, relative_path: &Path) -> anyhow::Result<()> {
+ if path.is_dir() {
+ return Ok(());
+ }
if let Ok(page_path) = relative_path.strip_prefix(PAGES_PATH) {
let (page_name, page_name_str) = get_name(page_path);
builder.site.page_index.remove(&page_name_str);
- std::fs::remove_file(builder.build_path.join(page_name.with_extension("html")))?;
+ std::fs::remove_file(builder.build_path.join(page_name.with_extension("html")))
+ .with_context(|| format!("Failed to remove page at {:?}", path))?;
} else if let Ok(template_path) = relative_path.strip_prefix(TEMPLATES_PATH) {
let (_template_name, template_name_str) = get_name(template_path);
builder.site.template_index.remove(&template_name_str);
builder.reg.unregister_template(&template_name_str);
- builder.site.build_all_pages(builder)?;
+ builder
+ .site
+ .build_all_pages(builder)
+ .context("Failed to rebuild pages")?;
} else if let Ok(_static_path) = relative_path.strip_prefix(STATIC_PATH) {
- std::fs::remove_file(builder.build_path.join(relative_path))?;
+ let to_remove = builder.build_path.join(relative_path);
+ std::fs::remove_file(&to_remove)
+ .with_context(|| format!("Failed to remove file at {:?}", to_remove))?;
+ } else if let Ok(_sass_path) = relative_path.strip_prefix(SASS_PATH) {
+ builder.build_sass().context("Failed to rebuild Sass")?;
}
Ok(())
@@ -110,6 +129,7 @@ impl Site {
eprintln!("Failed to build page {}: {}", page_name, e);
}
}
+ builder.build_sass().context("Failed to build Sass")?;
// Map of websocket connections
let peers: Arc>> =
@@ -120,7 +140,6 @@ impl Site {
let hw_peers = peers.clone();
hotwatch
.watch(site.site_path.clone(), move |event| {
- let site_path = builder.site.site_path.canonicalize().unwrap();
let peers = hw_peers.clone();
match (|| match event {
@@ -128,7 +147,7 @@ impl Site {
if skip_path(&builder, &path) {
Ok(false)
} else {
- let rel = rel(&path, &site_path)?;
+ let rel = rel(&path, &builder.site.site_path)?;
println!("CHANGED - {:?}", rel);
create(&mut builder, &path, &rel, true)?;
Ok::<_, anyhow::Error>(true)
@@ -138,7 +157,7 @@ impl Site {
if skip_path(&builder, &path) {
Ok(false)
} else {
- let rel = rel(&path, &site_path)?;
+ let rel = rel(&path, &builder.site.site_path)?;
println!("CREATED - {:?}", rel);
create(&mut builder, &path, &rel, true)?;
Ok(true)
@@ -148,7 +167,7 @@ impl Site {
if skip_path(&builder, &path) {
Ok(false)
} else {
- let rel = rel(&path, &site_path)?;
+ let rel = rel(&path, &builder.site.site_path)?;
println!("REMOVED - {:?}", rel);
remove(&mut builder, &path, &rel)?;
Ok(true)
@@ -158,8 +177,8 @@ impl Site {
if skip_path(&builder, &old) && skip_path(&builder, &new) {
Ok(false)
} else {
- let old_rel = rel(&old, &site_path)?;
- let new_rel = rel(&new, &site_path)?;
+ let old_rel = rel(&old, &builder.site.site_path)?;
+ let new_rel = rel(&new, &builder.site.site_path)?;
println!("RENAMED - {:?} -> {:?}", old_rel, new_rel);
create(&mut builder, &new, &new_rel, false)?;
remove(&mut builder, &old, &old_rel)?;
@@ -218,6 +237,13 @@ impl Site {
-> Result {
// Serve static files
let p = &path.as_str()[1..];
+
+ if p == "static/_dev.js" {
+ let res =
+ Response::new(include_str!("./refresh_websocket.js").into());
+ return Ok(res);
+ }
+
let mut p = build_path.join(p);
if !p.exists() {
@@ -228,8 +254,17 @@ impl Site {
}
if p.exists() {
- let body = std::fs::read_to_string(&p).unwrap();
- let res = Response::new(body.into());
+ let mut res = Response::new("".into());
+ match std::fs::read_to_string(&p) {
+ Ok(body) => {
+ *res.body_mut() = body.into();
+ }
+ Err(e) => {
+ eprintln!("{}", e);
+ *res.body_mut() = format!("Failed to load: {}", e).into();
+ *res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
+ }
+ }
return Ok(res);
}
Err(warp::reject())
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..8625a9e
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,22 @@
+//! Module containing various utilities.
+
+use std::path::Path;
+
+/// Simple helper to remove the contents of a directory without removing the directory itself.
+pub fn remove_dir_contents(path: &Path) -> anyhow::Result<()> {
+ if !path.exists() {
+ std::fs::create_dir_all(path)?;
+ return Ok(());
+ }
+ for entry in path.read_dir()? {
+ let entry = entry?;
+ let path = entry.path();
+ if path.is_file() {
+ std::fs::remove_file(&path)?;
+ } else {
+ std::fs::remove_dir_all(&path)?;
+ }
+ }
+
+ Ok(())
+}