1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
use std::path::Path;
use syntect::highlighting::ThemeSet;
use syntect::html::highlighted_html_for_string;
use syntect::parsing::SyntaxSet;
const THEME: &str = "InspiredGitHub";
const STYLE: &str = "html,body{margin:0;background:#fff}\
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("<!doctype html>\n<html>\n<head>\n<meta charset=\"utf-8\">\n<title>");
push_escaped(&mut out, title);
out.push_str("</title>\n<style>");
out.push_str(STYLE);
out.push_str("</style>\n</head>\n<body>\n");
out.push_str(&body);
out.push_str("\n</body>\n</html>\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("<pre>");
push_escaped(&mut out, text);
out.push_str("</pre>");
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("<title>x.rs</title>"));
}
#[test]
fn page_escapes_filename() {
let html = page(Path::new("/srv/<script>.txt"), "hi");
assert!(html.contains("<script>.txt"));
assert!(!html.contains("<title><script>"));
}
#[test]
fn page_emits_pre_block() {
let html = page(Path::new("/srv/x.txt"), "hello");
assert!(html.contains("<pre"));
assert!(html.contains("hello"));
}
#[test]
fn page_highlights_known_extension() {
// Rust syntax should produce styled spans, not just a plain <pre>hello</pre>.
let html = page(Path::new("/srv/x.rs"), "fn main() {}\n");
assert!(html.contains("<span"));
}
}
|