markdown_ppp/printer/
mod.rs

1//! Markdown pretty-printer for formatting AST back to Markdown
2//!
3//! This module provides functionality to render a Markdown Abstract Syntax Tree (AST)
4//! back to formatted Markdown text. The printer supports configurable formatting
5//! options and produces clean, readable Markdown output.
6//!
7//! # Features
8//!
9//! - **Full AST support**: All CommonMark + GFM elements are supported
10//! - **Configurable formatting**: Control line width, indentation, and spacing
11//! - **Pretty-printing**: Intelligent line wrapping and formatting
12//! - **Round-trip capability**: Parse → Render → Parse produces equivalent AST
13//! - **GitHub extensions**: Tables, task lists, alerts, footnotes, strikethrough
14//!
15//! # Basic Usage
16//!
17//! ```rust
18//! use markdown_ppp::ast::*;
19//! use markdown_ppp::printer::{render_markdown, config::Config};
20//!
21//! let doc = Document {
22//!     blocks: vec![
23//!         Block::Heading(Heading {
24//!             kind: HeadingKind::Atx(1),
25//!             content: vec![Inline::Text("Hello World".to_string())],
26//!         }),
27//!         Block::Paragraph(vec![
28//!             Inline::Text("This is ".to_string()),
29//!             Inline::Strong(vec![Inline::Text("formatted".to_string())]),
30//!             Inline::Text(" text.".to_string()),
31//!         ]),
32//!     ],
33//! };
34//!
35//! let config = Config::default();
36//! let markdown = render_markdown(&doc, config);
37//! println!("{}", markdown);
38//! ```
39//!
40//! # Configuration
41//!
42//! Customize the output format using [`Config`]:
43//!
44//! ```rust
45//! use markdown_ppp::printer::{render_markdown, config::Config};
46//! use markdown_ppp::ast::Document;
47//!
48//! let config = Config::default().with_width(120);
49//! let markdown = render_markdown(&Document::default(), config);
50//! ```
51
52mod block;
53mod blockquote;
54pub mod config;
55mod github_alert;
56mod heading;
57mod inline;
58mod list;
59mod table;
60mod tests;
61
62use crate::ast::*;
63use pretty::{Arena, DocBuilder};
64use std::rc::Rc;
65
66/// Render a Markdown AST back to formatted Markdown text
67///
68/// This function takes a parsed Markdown document (AST) and renders it back
69/// to clean, well-formatted Markdown text. The output follows consistent
70/// formatting rules and can be customized via configuration options.
71///
72/// # Arguments
73///
74/// * `ast` - The Markdown document AST to render
75/// * `config` - Configuration options controlling the output format
76///
77/// # Returns
78///
79/// A `String` containing the formatted Markdown text
80///
81/// # Examples
82///
83/// Basic rendering:
84/// ```rust
85/// use markdown_ppp::ast::*;
86/// use markdown_ppp::printer::{render_markdown, config::Config};
87///
88/// let doc = Document {
89///     blocks: vec![
90///         Block::Paragraph(vec![
91///             Inline::Text("Hello ".to_string()),
92///             Inline::Strong(vec![Inline::Text("world".to_string())]),
93///         ]),
94///     ],
95/// };
96///
97/// let config = Config::default();
98/// let markdown = render_markdown(&doc, config);
99/// assert!(markdown.contains("**world**"));
100/// ```
101///
102/// With custom width:
103/// ```rust
104/// use markdown_ppp::ast::Document;
105/// use markdown_ppp::printer::{render_markdown, config::Config};
106///
107/// let doc = Document { blocks: vec![] };
108/// let config = Config::default().with_width(60);
109/// let markdown = render_markdown(&doc, config);
110/// ```
111///
112/// # Round-trip Guarantee
113///
114/// For most valid Markdown documents, the following property holds:
115/// ```text
116/// parse(render(parse(input))) ≈ parse(input)
117/// ```
118/// Where ≈ means semantically equivalent AST structures.
119pub fn render_markdown(ast: &Document, config: crate::printer::config::Config) -> String {
120    let config = Rc::new(config);
121    let arena = Arena::new();
122    let doc = ast.to_doc(config.clone(), &arena);
123
124    let mut buf = Vec::new();
125    doc.render(config.width, &mut buf).unwrap();
126    String::from_utf8(buf).unwrap()
127}
128
129trait ToDoc<'a> {
130    fn to_doc(
131        &self,
132        config: Rc<crate::printer::config::Config>,
133        arena: &'a Arena<'a>,
134    ) -> DocBuilder<'a, Arena<'a>, ()>;
135}
136
137impl<'a> ToDoc<'a> for Document {
138    fn to_doc(
139        &self,
140        config: Rc<crate::printer::config::Config>,
141        arena: &'a Arena<'a>,
142    ) -> DocBuilder<'a, Arena<'a>, ()> {
143        self.blocks.to_doc(config, arena)
144    }
145}