use std::sync::OnceLock;
use markdown_it::parser::block::builtin::BlockParserRule;
use markdown_it::parser::core::CoreRule;
use markdown_it::parser::inline::builtin::InlineParserRule;
use markdown_it::parser::inline::InlineRoot;
use markdown_it::plugins::cmark::block::fence::CodeFence;
use markdown_it::plugins::cmark::block::heading::ATXHeading;
use markdown_it::plugins::cmark::block::lheading::SetextHeader;
use markdown_it::plugins::cmark::block::list::ListItem;
use markdown_it::plugins::cmark::block::paragraph::Paragraph;
use markdown_it::{MarkdownIt, Node};
struct PyStripInlineRule;
impl CoreRule for PyStripInlineRule {
fn run(root: &mut Node, _md: &MarkdownIt) {
fn walk(node: &mut Node) {
let strip_it = node.is::<ATXHeading>()
|| node.is::<SetextHeader>()
|| node.is::<Paragraph>()
|| node.is::<ListItem>();
for child in node.children.iter_mut() {
if strip_it {
if let Some(inline) = child.cast_mut::<InlineRoot>() {
let stripped = crate::pycompat::py_strip(&inline.content);
if stripped.len() != inline.content.len() {
inline.content = stripped.to_string();
}
}
}
walk(child);
}
}
walk(root);
}
}
fn parser() -> &'static MarkdownIt {
static PARSER: OnceLock<MarkdownIt> = OnceLock::new();
PARSER.get_or_init(|| {
let mut md = MarkdownIt::new();
markdown_it::plugins::cmark::add(&mut md);
md.add_rule::<PyStripInlineRule>()
.after::<BlockParserRule>()
.before::<InlineParserRule>();
md
})
}
pub fn render(body: &str) -> String {
let mut ast = parser().parse(body);
if !body.ends_with('\n') {
fix_eof_fence_content(&mut ast, body);
}
ast.xrender()
}
fn fix_eof_fence_content(node: &mut Node, body: &str) {
for child in node.children.iter_mut() {
fix_eof_fence_content(child, body);
}
let Some(map) = node.srcmap else { return };
let (start, end) = map.get_byte_offsets();
if end != body.len() {
return;
}
if let Some(fence) = node.cast_mut::<CodeFence>() {
if fence.content.ends_with('\n') {
let span_lines = body[start..end].lines().count();
let content_lines = fence.content.lines().count();
if span_lines == content_lines + 1 {
fence.content.pop();
}
}
}
}