mylloon.fr/src/misc/markdown.rs

150 lines
4.2 KiB
Rust
Raw Normal View History

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
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_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 options = get_options();
let arena = Arena::new();
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();
File {
metadata: Metadata {
info: FileMetadata::default(),
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
})
}
*/