diff options
author | Ben Bridle <ben@derelict.engineering> | 2025-05-21 14:25:18 +1200 |
---|---|---|
committer | Ben Bridle <ben@derelict.engineering> | 2025-05-21 19:50:09 +1200 |
commit | 060c1f23a5585a5fdabc56eda808b4cfd90f9081 (patch) | |
tree | a8bd3d2aa0e5c87528a5b7c417cf4a63edb2dbef /src | |
parent | c749c1db30f2b757e63093ac39127b3e338cb704 (diff) | |
download | toaster-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.rs | 47 | ||||
-rw-r--r-- | src/generate_html.rs | 19 |
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>"), |