2023-04-21 16:27:06 +02:00
|
|
|
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
|
2023-04-21 16:48:31 +02:00
|
|
|
pub fn get_options() -> ComrakOptions {
|
2023-04-21 16:27:06 +02:00
|
|
|
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_string()),
|
|
|
|
},
|
|
|
|
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();
|
2023-04-21 16:48:31 +02:00
|
|
|
|
|
|
|
let options = get_options();
|
2023-04-21 16:27:06 +02:00
|
|
|
let root = parse_document(&arena, raw_text, &options);
|
|
|
|
|
|
|
|
// Find metadata
|
2023-04-21 16:48:31 +02:00
|
|
|
let metadata = get_metadata(root);
|
2023-04-21 16:27:06 +02:00
|
|
|
|
|
|
|
// Convert to HTML
|
|
|
|
let mut html = vec![];
|
|
|
|
format_html(root, &options, &mut html).unwrap();
|
|
|
|
|
|
|
|
File {
|
|
|
|
metadata: Metadata {
|
2023-04-21 16:48:31 +02:00
|
|
|
info: metadata,
|
2023-04-21 16:27:06 +02:00
|
|
|
math: false,
|
|
|
|
mermaid: false,
|
|
|
|
syntax_highlight: false,
|
|
|
|
},
|
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* /// Check recursively if maths is in the AST
|
|
|
|
fn check_math(vec: &[Node]) -> bool {
|
|
|
|
vec.iter().any(|x| {
|
|
|
|
matches!(x, Node::Math(_) | Node::InlineMath(_))
|
|
|
|
|| match x.children() {
|
|
|
|
Some(children) => check_math(children),
|
|
|
|
None => false,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Recursively check whether mermaid diagrams are in the AST
|
|
|
|
fn check_mermaid(vec: &[Node], mermaid_str: String) -> bool {
|
|
|
|
vec.iter().any(|x| match x {
|
|
|
|
Node::Code(code) => code.lang == Some(mermaid_str.clone()),
|
|
|
|
_ => false,
|
|
|
|
} || match x.children() {
|
|
|
|
Some(children) => check_mermaid(children, mermaid_str.clone()),
|
|
|
|
None => false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Recursively check if code is in the AST
|
|
|
|
fn check_code(vec: &[Node], blacklist: Vec<String>) -> bool {
|
|
|
|
vec.iter().any(|x| match x {
|
|
|
|
Node::InlineCode(_) => true,
|
|
|
|
Node::Code(code) => match &code.lang {
|
|
|
|
Some(lang) => !blacklist.contains(lang),
|
|
|
|
None => true,
|
|
|
|
},
|
|
|
|
_ => false,
|
|
|
|
} || match x.children() {
|
|
|
|
Some(children) => check_code(children, blacklist.clone()),
|
|
|
|
None => false
|
|
|
|
})
|
|
|
|
}
|
|
|
|
*/
|