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 configuration:
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;
54
55/// Configuration options for Markdown pretty-printing.
56pub mod config;
57mod github_alert;
58mod heading;
59mod inline;
60mod list;
61mod table;
62mod tests;
63
64use crate::ast::*;
65use pretty::{Arena, DocBuilder};
66use std::rc::Rc;
67
68/// Render a Markdown AST back to formatted Markdown text
69///
70/// This function takes a parsed Markdown document (AST) and renders it back
71/// to clean, well-formatted Markdown text. The output follows consistent
72/// formatting rules and can be customized via configuration options.
73///
74/// # Arguments
75///
76/// * `ast` - The Markdown document AST to render
77/// * `config` - Configuration options controlling the output format
78///
79/// # Returns
80///
81/// A `String` containing the formatted Markdown text
82///
83/// # Examples
84///
85/// Basic rendering:
86/// ```rust
87/// use markdown_ppp::ast::*;
88/// use markdown_ppp::printer::{render_markdown, config::Config};
89///
90/// let doc = Document {
91/// blocks: vec![
92/// Block::Paragraph(vec![
93/// Inline::Text("Hello ".to_string()),
94/// Inline::Strong(vec![Inline::Text("world".to_string())]),
95/// ]),
96/// ],
97/// };
98///
99/// let config = Config::default();
100/// let markdown = render_markdown(&doc, config);
101/// assert!(markdown.contains("**world**"));
102/// ```
103///
104/// With custom width:
105/// ```rust
106/// use markdown_ppp::ast::Document;
107/// use markdown_ppp::printer::{render_markdown, config::Config};
108///
109/// let doc = Document { blocks: vec![] };
110/// let config = Config::default().with_width(60);
111/// let markdown = render_markdown(&doc, config);
112/// ```
113///
114/// # Round-trip Guarantee
115///
116/// For most valid Markdown documents, the following property holds:
117/// ```text
118/// parse(render(parse(input))) ≈ parse(input)
119/// ```
120/// Where ≈ means semantically equivalent AST structures.
121pub fn render_markdown(ast: &Document, config: crate::printer::config::Config) -> String {
122 let config = Rc::new(config);
123 let arena = Arena::new();
124 let doc = ast.to_doc(config.clone(), &arena);
125
126 let mut buf = Vec::new();
127 doc.render(config.width, &mut buf).unwrap();
128 String::from_utf8(buf).unwrap()
129}
130
131trait ToDoc<'a> {
132 fn to_doc(
133 &self,
134 config: Rc<crate::printer::config::Config>,
135 arena: &'a Arena<'a>,
136 ) -> DocBuilder<'a, Arena<'a>, ()>;
137}
138
139impl<'a> ToDoc<'a> for Document {
140 fn to_doc(
141 &self,
142 config: Rc<crate::printer::config::Config>,
143 arena: &'a Arena<'a>,
144 ) -> DocBuilder<'a, Arena<'a>, ()> {
145 self.blocks.to_doc(config, arena)
146 }
147}