From 8cc11c52a88ec78d175b3ec8de854916f631caab Mon Sep 17 00:00:00 2001 From: Ruben Beltran del Rio Date: Thu, 30 Apr 2026 15:17:30 +0200 Subject: Initial build --- src/render.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 src/render.rs (limited to 'src/render.rs') diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 0000000..8e366dd --- /dev/null +++ b/src/render.rs @@ -0,0 +1,102 @@ +use std::path::Path; + +use syntect::highlighting::ThemeSet; +use syntect::html::highlighted_html_for_string; +use syntect::parsing::SyntaxSet; + +const THEME: &str = "base16-ocean.dark"; +const STYLE: &str = "html,body{margin:0;background:#2b303b}\ +pre{margin:0;padding:1rem;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;overflow:auto}"; + +pub fn page(path: &Path, text: &str) -> String { + let title = path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or("paste"); + + let body = highlight(path, text); + + let mut out = String::with_capacity(body.len() + 256); + out.push_str("\n\n"); + push_escaped(&mut out, title); + out.push_str("\n\n"); + out.push_str(&body); + out.push('\n'); + out +} + +fn highlight(path: &Path, text: &str) -> String { + let syntaxes = SyntaxSet::load_defaults_newlines(); + let themes = ThemeSet::load_defaults(); + + let syntax = path + .extension() + .and_then(|e| e.to_str()) + .and_then(|e| syntaxes.find_syntax_by_extension(e)) + .or_else(|| syntaxes.find_syntax_by_first_line(text)) + .unwrap_or_else(|| syntaxes.find_syntax_plain_text()); + + let Some(theme) = themes.themes.get(THEME) else { + return fallback_pre(text); + }; + + match highlighted_html_for_string(text, &syntaxes, syntax, theme) { + Ok(html) => html, + Err(_) => fallback_pre(text), + } +} + +fn fallback_pre(text: &str) -> String { + let mut out = String::with_capacity(text.len() + 16); + out.push_str("
");
+    push_escaped(&mut out, text);
+    out.push_str("
"); + out +} + +fn push_escaped(out: &mut String, s: &str) { + for c in s.chars() { + match c { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + '\'' => out.push_str("'"), + other => out.push(other), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn page_contains_filename_in_title() { + let html = page(Path::new("/srv/x.rs"), "fn main() {}\n"); + assert!(html.contains("x.rs")); + } + + #[test] + fn page_escapes_filename() { + let html = page(Path::new("/srv/