summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBen Bridle <ben@derelict.engineering>2025-05-21 14:25:18 +1200
committerBen Bridle <ben@derelict.engineering>2025-05-21 19:50:09 +1200
commit060c1f23a5585a5fdabc56eda808b4cfd90f9081 (patch)
treea8bd3d2aa0e5c87528a5b7c417cf4a63edb2dbef /src
parentc749c1db30f2b757e63093ac39127b3e338cb704 (diff)
downloadtoaster-060c1f23a5585a5fdabc56eda808b4cfd90f9081.zip
Implement syntax highlighting for syntax fragments
A new key 'highlighters' has been added to the toaster.conf file. The value should be a line defining the languages to use that syntax for, like [py/python]. The lines following are the template definitions, as per the highlighter library.
Diffstat (limited to 'src')
-rw-r--r--src/collect_files.rs47
-rw-r--r--src/generate_html.rs19
2 files changed, 64 insertions, 2 deletions
diff --git a/src/collect_files.rs b/src/collect_files.rs
index 7a3c464..18d75f1 100644
--- a/src/collect_files.rs
+++ b/src/collect_files.rs
@@ -1,5 +1,6 @@
use crate::*;
+use highlight::*;
use vagabond::*;
use std::collections::HashMap;
@@ -8,6 +9,7 @@ use std::collections::HashMap;
pub struct Website {
pub name: String,
pub config: HashMap<String, String>,
+ pub highlighters: Highlighters,
pub pages: Vec<Page>,
pub redirects: Vec<Redirect>,
pub static_files: Vec<StaticItem>, // Redirects, !-prefixed-dir contents
@@ -60,6 +62,10 @@ pub trait LinkFrom {
}
}
+pub struct Highlighters {
+ pub languages: HashMap<String, usize>,
+ pub highlighters: Vec<Highlighter>,
+}
impl Page {
pub fn root(&self) -> String {
@@ -96,8 +102,13 @@ impl Website {
Err(err) => fatal!("Couldn't open {:?}: {:?}", &path, err),
},
config: HashMap::new(),
+ highlighters: Highlighters {
+ languages: HashMap::new(),
+ highlighters: Vec::new(),
+ },
};
new.collect_entry(path, path);
+ new.parse_highlighters();
return new;
}
@@ -149,7 +160,7 @@ impl Website {
let mut key = None;
let mut value = String::new();
for line in config.lines() {
- if line.starts_with(" ") {
+ if line.starts_with(" ") || line.trim().is_empty() {
value.push_str(line.trim());
value.push('\n');
} else {
@@ -268,6 +279,40 @@ impl Website {
}
}
+ pub fn parse_highlighters(&mut self) {
+ let mut languages = Vec::new();
+ let mut source = String::new();
+ for line in self.get_config("highlighters").lines() {
+ if let Some(line) = line.trim().strip_prefix('[') {
+ if let Some(line) = line.strip_suffix(']') {
+ // Bank the current source.
+ if !languages.is_empty() {
+ let i = self.highlighters.highlighters.len();
+ for language in languages {
+ self.highlighters.languages.insert(language, i);
+ }
+ let highlighter = Highlighter::from_str(&source);
+ self.highlighters.highlighters.push(highlighter);
+ }
+ languages = line.split('/').map(|s| s.trim().to_string()).collect();
+ source.clear();
+ continue;
+ }
+ }
+ source.push_str(line);
+ source.push('\n');
+ }
+ // Bank the current source.
+ if !languages.is_empty() {
+ let i = self.highlighters.highlighters.len();
+ for language in languages {
+ self.highlighters.languages.insert(language, i);
+ }
+ let highlighter = Highlighter::from_str(&source);
+ self.highlighters.highlighters.push(highlighter);
+ }
+ }
+
// Ext is extension without a dot.
// Checks if a relative link to an internal page name can be reached from
// the current page, and returns a resolved absolute link to the page with extension.
diff --git a/src/generate_html.rs b/src/generate_html.rs
index 0de42a5..5abddf2 100644
--- a/src/generate_html.rs
+++ b/src/generate_html.rs
@@ -236,7 +236,24 @@ pub fn document_to_html(document: &MarkdownDocument, page: &Page, website: &Webs
warn!("Gallery-nav on page {from:?} has line without a '::' separator");
}
}),
- _ => wrap!("pre", format!("class='{language}'"), html!("{}", sanitize_text(content, false))),
+ _ => wrap!("pre", format!("class='{language}'"), {
+ if let Some(i) = website.highlighters.languages.get(language) {
+ let mut source = String::new();
+ let highlighter = &website.highlighters.highlighters[*i];
+ for span in highlighter.highlight(content) {
+ if span.tag.is_empty() {
+ source.push_str(&sanitize_text(&span.text, false));
+ } else {
+ source.push_str(&format!("<span class='{}'>", span.tag.to_lowercase()));
+ source.push_str(&sanitize_text(&span.text, false));
+ source.push_str("</span>");
+ }
+ }
+ html!("{source}");
+ } else {
+ html!("{}", sanitize_text(content, false))
+ }
+ })
}
}
Block::Break => html!("<hr>"),