135 lines
4.1 KiB
Rust
135 lines
4.1 KiB
Rust
use crate::misc::date::Date;
|
|
use comrak::nodes::{AstNode, NodeValue};
|
|
use comrak::{
|
|
format_html, parse_document, Arena, ComrakExtensionOptions, ComrakOptions, ComrakParseOptions,
|
|
ComrakRenderOptions, ListStyleType,
|
|
};
|
|
use ramhorns::Content;
|
|
use serde::Deserialize;
|
|
use std::fs;
|
|
|
|
#[derive(Default, Deserialize, Content)]
|
|
pub struct FileMetadata {
|
|
pub title: Option<String>,
|
|
pub link: Option<String>,
|
|
pub date: Option<Date>,
|
|
}
|
|
|
|
#[derive(Content)]
|
|
pub struct Metadata {
|
|
pub info: FileMetadata,
|
|
pub math: bool,
|
|
pub mermaid: bool,
|
|
pub syntax_highlight: bool,
|
|
}
|
|
|
|
#[derive(Content)]
|
|
pub struct File {
|
|
pub metadata: Metadata,
|
|
pub content: String,
|
|
}
|
|
|
|
/// Options used for parser and compiler MD --> HTML
|
|
pub fn get_options() -> ComrakOptions {
|
|
ComrakOptions {
|
|
extension: ComrakExtensionOptions {
|
|
strikethrough: true,
|
|
tagfilter: true,
|
|
table: true,
|
|
autolink: true,
|
|
tasklist: true,
|
|
superscript: true,
|
|
header_ids: Some(String::new()),
|
|
footnotes: false, // true?
|
|
description_lists: true,
|
|
front_matter_delimiter: Some("---".to_owned()),
|
|
},
|
|
parse: ComrakParseOptions {
|
|
smart: false, // true one day?
|
|
default_info_string: None,
|
|
relaxed_tasklist_matching: true,
|
|
},
|
|
render: ComrakRenderOptions {
|
|
hardbreaks: false, // could be true? change by metadata could be good for compatibility
|
|
github_pre_lang: false,
|
|
full_info_string: true,
|
|
width: 0, // 0 mean disabled?
|
|
unsafe_: false, // could be true? change by metadata could be good for compatibility
|
|
escape: false, // may change in the future?
|
|
list_style: ListStyleType::Dash,
|
|
sourcepos: false,
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn read(raw_text: &str) -> File {
|
|
let arena = Arena::new();
|
|
|
|
let options = get_options();
|
|
let root = parse_document(&arena, raw_text, &options);
|
|
|
|
// Find metadata
|
|
let metadata = get_metadata(root);
|
|
|
|
// Convert to HTML
|
|
let mut html = vec![];
|
|
format_html(root, &options, &mut html).unwrap();
|
|
|
|
let mermaid_name = "mermaid";
|
|
|
|
File {
|
|
metadata: Metadata {
|
|
info: metadata,
|
|
mermaid: check_mermaid(root, mermaid_name.to_owned()),
|
|
syntax_highlight: check_code(root, &[mermaid_name.to_owned()]),
|
|
},
|
|
content: String::from_utf8(html).unwrap(),
|
|
}
|
|
}
|
|
|
|
/// Read markdown file
|
|
pub fn read_file(filename: &str) -> Option<File> {
|
|
match fs::read_to_string(filename) {
|
|
Ok(text) => Some(read(&text)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Fetch metadata from AST
|
|
pub fn get_metadata<'a>(root: &'a AstNode<'a>) -> FileMetadata {
|
|
match root
|
|
.children()
|
|
.find_map(|node| match &node.data.borrow().value {
|
|
NodeValue::FrontMatter(text) => {
|
|
// '-' correspond to `front_matter_delimiter`
|
|
serde_yaml::from_str(text.trim_matches(&['-', '\n'] as &[_])).unwrap_or_default()
|
|
}
|
|
_ => None,
|
|
}) {
|
|
Some(data) => data,
|
|
None => FileMetadata::default(),
|
|
}
|
|
}
|
|
|
|
/// Recursively check whether mermaid diagrams are in the AST
|
|
fn check_mermaid<'a>(root: &'a AstNode<'a>, mermaid_str: String) -> bool {
|
|
root.children().any(|node| match &node.data.borrow().value {
|
|
// Check if code of block define a mermaid diagram
|
|
NodeValue::CodeBlock(code_block) => code_block.info == mermaid_str,
|
|
_ => false,
|
|
})
|
|
}
|
|
|
|
/// Recursively check if code is in the AST
|
|
fn check_code<'a>(root: &'a AstNode<'a>, blacklist: &[String]) -> bool {
|
|
root.children().any(|node| match &node.data.borrow().value {
|
|
// Detect code in paragraph
|
|
/* NodeValue::Paragraph => match &node.children().next() {
|
|
Some(child) => matches!(child.data.borrow().value, NodeValue::Code(_)),
|
|
None => false,
|
|
}, */
|
|
// Detect blocks of code where the lang isn't in the blacklist
|
|
NodeValue::CodeBlock(code_block) => !blacklist.contains(&code_block.info),
|
|
_ => false,
|
|
})
|
|
}
|