mylloon.fr/src/misc/markdown.rs
Mylloon 22b68f549e
Some checks are pending
ci/woodpecker/push/publish Pipeline is pending
use of mathml instead of katex
2023-04-21 19:34:34 +02:00

137 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 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_: true,
escape: false, // may change in the future?
list_style: ListStyleType::Dash,
sourcepos: false,
},
}
}
pub fn read(raw_text: &str) -> File {
let arena = Arena::new();
// LaTeX conversion to MathML
let text = latex2mathml::replace(raw_text).unwrap_or_default();
let options = get_options();
let root = parse_document(&arena, &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,
})
}