mod comrak_build;
mod frontmatter;
#[cfg(test)]
mod tests;
use crate::common::nested_to_flat::tree_to_events;
use crate::error::FormatError;
use crate::ir::nodes::DocNode;
use crate::render_dispatch::dispatch_render;
use comrak::{format_commonmark, Arena};
use lex_core::lex::ast::Document;
use lex_extension_host::Registry;
use comrak_build::{build_comrak_ast, default_comrak_options};
use frontmatter::{
inlines_to_text, prepend_title_as_h1, render_document_annotations_as_yaml,
strip_heading_digit_dot_escape,
};
#[derive(Debug, Clone, PartialEq)]
pub struct MarkdownExportOutcome {
pub markdown: String,
pub diagnostics: Vec<String>,
}
pub fn serialize_to_markdown(doc: &Document) -> Result<String, FormatError> {
let outcome = serialize_to_markdown_with_registry(doc, crate::default_registry())?;
Ok(outcome.markdown)
}
pub fn serialize_to_markdown_with_registry(
doc: &Document,
registry: &Registry,
) -> Result<MarkdownExportOutcome, FormatError> {
let ir_doc = crate::to_ir_with_registry(doc, registry);
let plan = dispatch_render(&ir_doc, registry, "markdown");
let document_title = ir_doc.title.as_ref().map(|title_inlines| {
let title_text = inlines_to_text(title_inlines);
let subtitle_text = ir_doc.subtitle.as_ref().map(|sub| inlines_to_text(sub));
(title_text, subtitle_text)
});
let doc_scope_plan = &plan.nodes[..plan.doc_scope_count];
let body_plan = &plan.nodes[plan.doc_scope_count..];
let frontmatter_yaml =
render_document_annotations_as_yaml(&ir_doc.document_annotations, doc_scope_plan);
let events = tree_to_events(&DocNode::Document(ir_doc));
let arena = Arena::new();
let root = build_comrak_ast(&arena, &events, body_plan)?;
let mut output = Vec::new();
let options = default_comrak_options();
format_commonmark(root, &options, &mut output).map_err(|e| {
FormatError::SerializationError(format!("Comrak serialization failed: {e}"))
})?;
let markdown = String::from_utf8(output)
.map_err(|e| FormatError::SerializationError(format!("UTF-8 conversion failed: {e}")))?;
let cleaned = markdown.replace("<!-- end list -->\n\n", "");
let cleaned = strip_heading_digit_dot_escape(&cleaned);
let with_title = prepend_title_as_h1(&cleaned, document_title);
let with_frontmatter = match frontmatter_yaml {
Some(yaml) => format!("{yaml}{with_title}"),
None => with_title,
};
let mut diagnostics: Vec<String> = plan
.nodes
.iter()
.filter_map(|n| n.diagnostic.clone())
.collect();
diagnostics.extend(plan.root_diagnostics);
Ok(MarkdownExportOutcome {
markdown: with_frontmatter,
diagnostics,
})
}