162 lines
4.8 KiB
Rust
162 lines
4.8 KiB
Rust
use comrak::{
|
|
self,
|
|
nodes::{AstNode, NodeCodeBlock, NodeHeading, NodeHtmlBlock, NodeLink, NodeValue},
|
|
Arena,
|
|
};
|
|
use docs::{
|
|
page::{Page, COMRAK_OPTIONS},
|
|
views,
|
|
};
|
|
use std::{
|
|
env,
|
|
error::Error,
|
|
fs::{self, File},
|
|
io::BufReader,
|
|
path::Path,
|
|
str::{self, Utf8Error},
|
|
string::FromUtf8Error,
|
|
};
|
|
use syntect::{
|
|
highlighting::{Color, ThemeSet},
|
|
html::highlighted_html_for_string,
|
|
parsing::SyntaxSet,
|
|
};
|
|
|
|
fn main() -> Result<(), Box<dyn Error>> {
|
|
let args = env::args().collect::<Vec<_>>();
|
|
if args.len() != 7 {
|
|
return Err("invalid arguments".into());
|
|
}
|
|
build_page(&args[1], &args[2], &args[3], &args[4], &args[5], &args[6])
|
|
}
|
|
|
|
fn build_page(
|
|
output_path: &str,
|
|
slug: &str,
|
|
input_path: &str,
|
|
nav_path: &str,
|
|
version: &str,
|
|
hash: &str,
|
|
) -> Result<(), Box<dyn Error>> {
|
|
let nav: Vec<(String, Option<String>)> = {
|
|
let file = File::open(nav_path)?;
|
|
let reader = BufReader::new(file);
|
|
serde_json::from_reader(reader)?
|
|
};
|
|
|
|
let arena = Arena::new();
|
|
|
|
let nav = nav
|
|
.iter()
|
|
.filter_map(|(slug, title)| {
|
|
title.as_ref().map(|title| {
|
|
let title = comrak::parse_document(&arena, title, &COMRAK_OPTIONS);
|
|
(slug.as_str(), title)
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let page = Page::load(&arena, input_path)?;
|
|
postprocess(page.content)?;
|
|
|
|
let markup = views::main(slug, page, &nav, version, hash);
|
|
|
|
fs::create_dir_all(Path::new(output_path).parent().unwrap())?;
|
|
fs::write(output_path, markup.into_string())?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn postprocess<'a>(content: &'a AstNode<'a>) -> Result<(), Box<dyn Error>> {
|
|
lower_headings(content);
|
|
rewrite_md_links(content)?;
|
|
strip_hidden_code(content)?;
|
|
highlight_code(content)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn lower_headings<'a>(root: &'a AstNode<'a>) {
|
|
for node in root.descendants() {
|
|
let mut data = node.data.borrow_mut();
|
|
if let NodeValue::Heading(NodeHeading { level, .. }) = &mut data.value {
|
|
*level += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn rewrite_md_links<'a>(root: &'a AstNode<'a>) -> Result<(), FromUtf8Error> {
|
|
for node in root.descendants() {
|
|
let mut data = node.data.borrow_mut();
|
|
if let NodeValue::Link(NodeLink { url, .. }) = &mut data.value {
|
|
let mut url_string = String::from_utf8(std::mem::take(url))?;
|
|
if url_string.ends_with(".md") {
|
|
url_string.truncate(url_string.len() - ".md".len());
|
|
url_string.push_str(".html");
|
|
}
|
|
*url = url_string.into_bytes();
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn strip_hidden_code<'a>(root: &'a AstNode<'a>) -> Result<(), Box<dyn Error>> {
|
|
for node in root.descendants() {
|
|
let mut data = node.data.borrow_mut();
|
|
if let NodeValue::CodeBlock(NodeCodeBlock { info, literal, .. }) = &mut data.value {
|
|
let info = parse_code_block_info(info)?;
|
|
if !info.contains(&"rust") {
|
|
continue;
|
|
}
|
|
*literal = strip_hidden_code_inner(str::from_utf8(literal)?).into_bytes();
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn strip_hidden_code_inner(literal: &str) -> String {
|
|
let lines = literal
|
|
.split('\n')
|
|
.filter(|line| {
|
|
let line = line.trim();
|
|
line != "#" && !line.starts_with("# ")
|
|
})
|
|
.collect::<Vec<_>>();
|
|
lines.join("\n")
|
|
}
|
|
|
|
fn highlight_code<'a>(root: &'a AstNode<'a>) -> Result<(), Box<dyn Error>> {
|
|
let ss = SyntaxSet::load_defaults_newlines();
|
|
let ts = ThemeSet::load_defaults();
|
|
let mut theme = ts.themes["InspiredGitHub"].clone();
|
|
theme.settings.background = Some(Color {
|
|
r: 0xff,
|
|
g: 0xee,
|
|
b: 0xff,
|
|
a: 0xff,
|
|
});
|
|
for node in root.descendants() {
|
|
let mut data = node.data.borrow_mut();
|
|
if let NodeValue::CodeBlock(NodeCodeBlock { info, literal, .. }) = &mut data.value {
|
|
let info = parse_code_block_info(info)?;
|
|
let syntax = info
|
|
.into_iter()
|
|
.filter_map(|token| ss.find_syntax_by_token(token))
|
|
.next()
|
|
.unwrap_or_else(|| ss.find_syntax_plain_text());
|
|
let mut literal = String::from_utf8(std::mem::take(literal))?;
|
|
if !literal.ends_with('\n') {
|
|
// Syntect expects a trailing newline
|
|
literal.push('\n');
|
|
}
|
|
let html = highlighted_html_for_string(&literal, &ss, syntax, &theme);
|
|
let mut html_block = NodeHtmlBlock::default();
|
|
html_block.literal = html.into_bytes();
|
|
data.value = NodeValue::HtmlBlock(html_block);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn parse_code_block_info(info: &[u8]) -> Result<Vec<&str>, Utf8Error> {
|
|
str::from_utf8(info).map(|info| info.split(',').map(str::trim).collect())
|
|
}
|