pub mod ast;
pub mod position;
pub mod shared;
pub mod blocks;
pub mod inlines;
pub use ast::*;
pub use blocks::parse_blocks;
pub use inlines::parse_inlines;
pub use position::*;
#[derive(Debug, Clone)]
pub struct ParseOptions {
pub track_positions: bool,
pub parse_math: bool,
pub parse_diagrams: bool,
}
impl Default for ParseOptions {
fn default() -> Self {
Self {
track_positions: true,
parse_math: true,
parse_diagrams: true,
}
}
}
pub fn parse_with_options(
input: &str,
opts: ParseOptions,
) -> Result<Document, Box<dyn std::error::Error>> {
log::info!("Starting parse: {} bytes", input.len());
let _guard =
shared::ParseOptionsGuard::new(opts.track_positions, opts.parse_math, opts.parse_diagrams);
let mut document = parse_blocks(input)?;
log::debug!("Parsed {} blocks", document.children.len());
resolve_reference_links(&mut document);
blocks::gfm_admonitions::apply_gfm_admonitions(&mut document);
Ok(document)
}
pub fn parse(input: &str) -> Result<Document, Box<dyn std::error::Error>> {
parse_with_options(input, ParseOptions::default())
}
fn resolve_reference_links(document: &mut Document) {
resolve_reference_links_in_nodes(&mut document.children, &document.references);
}
fn unescape_commonmark_backslash_escapes(input: &str) -> String {
const ESCAPABLE: &str = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
let mut out = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
if let Some(&next) = chars.peek() {
if ESCAPABLE.contains(next) {
out.push(next);
chars.next();
continue;
}
}
}
out.push(ch);
}
out
}
fn resolve_reference_links_in_nodes(nodes: &mut Vec<Node>, references: &ReferenceMap) {
let mut i = 0;
while i < nodes.len() {
if !nodes[i].children.is_empty() {
resolve_reference_links_in_nodes(&mut nodes[i].children, references);
}
let is_ref = matches!(nodes[i].kind, NodeKind::LinkReference { .. });
if !is_ref {
i += 1;
continue;
}
let (label, suffix) = match &nodes[i].kind {
NodeKind::LinkReference { label, suffix } => (label.clone(), suffix.clone()),
_ => unreachable!(),
};
if let Some((url, title)) = references.get(&label) {
nodes[i].kind = NodeKind::Link {
url: url.clone(),
title: title.clone(),
};
i += 1;
continue;
}
let mut inner_children = std::mem::take(&mut nodes[i].children);
let mut replacement: Vec<Node> = Vec::new();
replacement.push(Node {
kind: NodeKind::Text("[".to_string()),
span: None,
children: Vec::new(),
});
replacement.append(&mut inner_children);
replacement.push(Node {
kind: NodeKind::Text("]".to_string()),
span: None,
children: Vec::new(),
});
if !suffix.is_empty() {
replacement.push(Node {
kind: NodeKind::Text(unescape_commonmark_backslash_escapes(&suffix)),
span: None,
children: Vec::new(),
});
}
let replacement_len = replacement.len();
nodes.splice(i..i + 1, replacement);
i += replacement_len;
}
}