mirror of
https://github.com/zyllian/webdog.git
synced 2025-05-09 18:16:40 -07:00
add syntax highlighting for code blocks
This commit is contained in:
parent
a8d8096ba6
commit
e3630208fa
8 changed files with 251 additions and 6 deletions
145
Cargo.lock
generated
145
Cargo.lock
generated
|
@ -17,6 +17,12 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
|
@ -124,7 +130,7 @@ dependencies = [
|
|||
"derive_builder",
|
||||
"diligent-date-parser",
|
||||
"never",
|
||||
"quick-xml",
|
||||
"quick-xml 0.36.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -143,7 +149,7 @@ dependencies = [
|
|||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"miniz_oxide 0.7.4",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
@ -154,6 +160,21 @@ version = "0.21.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -339,6 +360,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.13"
|
||||
|
@ -614,6 +644,16 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -879,7 +919,7 @@ version = "0.3.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"headers-core",
|
||||
"http 0.2.12",
|
||||
|
@ -1322,6 +1362,12 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "litemap"
|
||||
version = "0.7.3"
|
||||
|
@ -1410,6 +1456,15 @@ dependencies = [
|
|||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
|
@ -1526,6 +1581,28 @@ version = "1.20.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "onig"
|
||||
version = "6.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"onig_sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "onig_sys"
|
||||
version = "69.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
|
@ -1753,6 +1830,25 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "plist"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"indexmap",
|
||||
"quick-xml 0.32.0",
|
||||
"serde",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
|
@ -1807,6 +1903,15 @@ version = "0.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.36.2"
|
||||
|
@ -1976,7 +2081,7 @@ dependencies = [
|
|||
"derive_builder",
|
||||
"mime",
|
||||
"never",
|
||||
"quick-xml",
|
||||
"quick-xml 0.36.2",
|
||||
"url",
|
||||
]
|
||||
|
||||
|
@ -2246,6 +2351,28 @@ dependencies = [
|
|||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntect"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags 1.3.2",
|
||||
"flate2",
|
||||
"fnv",
|
||||
"once_cell",
|
||||
"onig",
|
||||
"plist",
|
||||
"regex-syntax",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"walkdir",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tera"
|
||||
version = "1.20.0"
|
||||
|
@ -2727,6 +2854,7 @@ dependencies = [
|
|||
"rss",
|
||||
"serde",
|
||||
"serde_yml",
|
||||
"syntect",
|
||||
"tera",
|
||||
"time",
|
||||
"tokio",
|
||||
|
@ -2913,6 +3041,15 @@ version = "0.5.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.4"
|
||||
|
|
|
@ -23,6 +23,7 @@ rayon = "1"
|
|||
rss = {version = "2", features = ["validation"]}
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_yml = "0.0.12"
|
||||
syntect = "5"
|
||||
tera = "1"
|
||||
time = {version = "0.3", features = ["serde-human-readable"]}
|
||||
tokio = {version = "1.10", features = [
|
||||
|
|
|
@ -3,6 +3,7 @@ title: webdog
|
|||
description: "static site builder for dogs"
|
||||
sass_styles: [index.scss]
|
||||
cdn_url: "https://i.zyl.gay"
|
||||
code_theme: base16-ocean.dark # options: base16-ocean.dark, base16-eighties.dark, base16-mocha.dark, base16-ocean.light, InspiredGitHub, Solarized (dark), and Solarized (light)
|
||||
|
||||
resources:
|
||||
blog:
|
||||
|
|
|
@ -10,7 +10,7 @@ extra:
|
|||
|
||||
welcome to webdog, the static site generator fit for a dog :3
|
||||
|
||||
```
|
||||
```sh
|
||||
git clone https://zyllian/webdog
|
||||
cd webdog
|
||||
cargo install .
|
||||
|
|
|
@ -166,3 +166,17 @@ abbr {
|
|||
.flex-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.wd-codeblock {
|
||||
position: relative;
|
||||
|
||||
.copy {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
& > pre {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,15 @@ use eyre::{eyre, Context, OptionExt};
|
|||
use lol_html::{element, html_content::ContentType, HtmlRewriter, Settings};
|
||||
use pulldown_cmark::{Options, Parser};
|
||||
use serde::Serialize;
|
||||
use syntect::{highlighting::ThemeSet, parsing::SyntaxSet};
|
||||
use tera::Tera;
|
||||
use url::Url;
|
||||
|
||||
use crate::{resource::ResourceBuilder, util, PageMetadata, Site, ROOT_PATH, SASS_PATH};
|
||||
|
||||
/// Path for static webdog resources included with the site build.
|
||||
const WEBDOG_PATH: &str = "webdog";
|
||||
|
||||
/// Struct containing data to be sent to templates when rendering them.
|
||||
#[derive(Debug, Serialize)]
|
||||
struct TemplateData<'a, T> {
|
||||
|
@ -33,6 +37,10 @@ struct TemplateData<'a, T> {
|
|||
pub struct SiteBuilder {
|
||||
/// The Handlebars registry used to render templates.
|
||||
pub(crate) tera: Tera,
|
||||
/// The syntax set used to render source code.
|
||||
pub(crate) syntax_set: SyntaxSet,
|
||||
/// The theme set used to render source code.
|
||||
pub(crate) theme_set: ThemeSet,
|
||||
/// The site info used to build the site.
|
||||
pub site: Site,
|
||||
/// The path to the build directory.
|
||||
|
@ -66,6 +74,8 @@ impl SiteBuilder {
|
|||
|
||||
Self {
|
||||
tera,
|
||||
syntax_set: SyntaxSet::load_defaults_newlines(),
|
||||
theme_set: ThemeSet::load_defaults(),
|
||||
resource_builders: HashMap::new(),
|
||||
site,
|
||||
build_path,
|
||||
|
@ -92,6 +102,13 @@ impl SiteBuilder {
|
|||
std::fs::create_dir(&self.build_path).wrap_err("Failed to create build directory")?;
|
||||
}
|
||||
|
||||
let webdog_path = self.build_path.join(WEBDOG_PATH);
|
||||
std::fs::create_dir(&webdog_path)?;
|
||||
std::fs::write(
|
||||
webdog_path.join("webdog.js"),
|
||||
include_str!("./js/webdog.js"),
|
||||
)?;
|
||||
|
||||
let root_path = self.site.site_path.join(ROOT_PATH);
|
||||
if root_path.exists() {
|
||||
for entry in walkdir::WalkDir::new(&root_path) {
|
||||
|
@ -114,6 +131,10 @@ impl SiteBuilder {
|
|||
|
||||
/// Performs actions that need to be done when the config changes while serving.
|
||||
pub fn reload(&mut self) -> eyre::Result<()> {
|
||||
self.site
|
||||
.config
|
||||
.check(self)
|
||||
.wrap_err("site config failed check:")?;
|
||||
self.resource_builders.clear();
|
||||
for (prefix, config) in &self.site.config.resources {
|
||||
self.resource_builders
|
||||
|
@ -152,6 +173,10 @@ impl SiteBuilder {
|
|||
}),
|
||||
element!("head", |el| {
|
||||
el.prepend(r#"<meta charset="utf-8">"#, ContentType::Html);
|
||||
el.append(
|
||||
r#"<script type="text/javascript" src="/webdog/webdog.js" defer></script>"#,
|
||||
ContentType::Html,
|
||||
);
|
||||
if self.serving {
|
||||
el.append(r#"<script src="/_dev.js"></script>"#, ContentType::Html);
|
||||
}
|
||||
|
@ -306,7 +331,47 @@ impl SiteBuilder {
|
|||
.with_context(|| format!("Failed to read page at {}", page_path.display()))?;
|
||||
let page = crate::frontmatter::FrontMatter::parse(input)?;
|
||||
|
||||
let parser = Parser::new_ext(&page.content, Options::all());
|
||||
let mut language = None;
|
||||
let parser = Parser::new_ext(&page.content, Options::all()).filter_map(|event| {
|
||||
// syntax highlighting for code blocks
|
||||
match event {
|
||||
pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock(
|
||||
pulldown_cmark::CodeBlockKind::Fenced(name),
|
||||
)) => {
|
||||
language = Some(name);
|
||||
None
|
||||
}
|
||||
pulldown_cmark::Event::Text(code) => {
|
||||
if let Some(language) = language.take() {
|
||||
let syntax_reference = self
|
||||
.syntax_set
|
||||
.find_syntax_by_token(&language)
|
||||
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
|
||||
let html = format!(
|
||||
r#"<div class="wd-codeblock">
|
||||
<button class="copy">Copy</button>
|
||||
{}
|
||||
</div>"#,
|
||||
syntect::html::highlighted_html_for_string(
|
||||
&code,
|
||||
&self.syntax_set,
|
||||
syntax_reference,
|
||||
self.theme_set
|
||||
.themes
|
||||
.get(&self.site.config.code_theme)
|
||||
.as_ref()
|
||||
.expect("should never fail"),
|
||||
)
|
||||
.expect("failed to highlight syntax")
|
||||
);
|
||||
Some(pulldown_cmark::Event::Html(html.into()))
|
||||
} else {
|
||||
Some(pulldown_cmark::Event::Text(code))
|
||||
}
|
||||
}
|
||||
_ => Some(event),
|
||||
}
|
||||
});
|
||||
let mut page_html = String::new();
|
||||
pulldown_cmark::html::push_html(&mut page_html, parser);
|
||||
|
||||
|
|
10
src/js/webdog.js
Normal file
10
src/js/webdog.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
(function () {
|
||||
"use strict";
|
||||
|
||||
for (const copyButton of document.querySelectorAll(".wd-codeblock .copy")) {
|
||||
const source = copyButton.parentElement.querySelector("pre").innerText;
|
||||
copyButton.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(source);
|
||||
});
|
||||
}
|
||||
})();
|
17
src/lib.rs
17
src/lib.rs
|
@ -43,6 +43,12 @@ pub struct SiteConfig {
|
|||
pub sass_styles: Vec<PathBuf>,
|
||||
/// 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<String, ResourceBuilderConfig>,
|
||||
}
|
||||
|
@ -52,6 +58,17 @@ impl SiteConfig {
|
|||
pub fn cdn_url(&self, file: &str) -> eyre::Result<Url> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct for the front matter in templates. (nothing here yet)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue