markdown_ppp/plaintext_printer/mod.rs
1//! Plaintext renderer for converting Markdown AST to plain text
2//!
3//! This module strips all Markdown formatting and produces clean plaintext output.
4//!
5//! # Basic Usage
6//!
7//! ```rust
8//! use markdown_ppp::ast::*;
9//! use markdown_ppp::plaintext_printer::{render_plaintext, config::Config};
10//!
11//! let doc = Document {
12//! blocks: vec![
13//! Block::Heading(Heading {
14//! kind: HeadingKind::Atx(1),
15//! content: vec![Inline::Text("Hello World".to_string())],
16//! }),
17//! Block::Paragraph(vec![
18//! Inline::Text("This is ".to_string()),
19//! Inline::Strong(vec![Inline::Text("bold".to_string())]),
20//! Inline::Text(" text.".to_string()),
21//! ]),
22//! ],
23//! };
24//!
25//! let config = Config::default();
26//! let text = render_plaintext(&doc, config);
27//! assert_eq!(text, "Hello World\n\nThis is bold text.");
28//! ```
29
30mod block;
31
32/// Configuration options for plaintext rendering.
33pub mod config;
34mod inline;
35
36#[cfg(test)]
37mod tests;
38
39use crate::ast::*;
40use config::Config;
41use pretty::{Arena, DocBuilder};
42use std::collections::HashMap;
43
44/// Internal rendering state for plaintext generation
45pub(crate) struct State<'a> {
46 arena: Arena<'a>,
47 config: Config,
48 footnote_index: HashMap<String, usize>,
49}
50
51impl State<'_> {
52 pub fn new(config: Config, ast: &Document) -> Self {
53 let footnote_index = crate::ast::index::get_footnote_indices(ast);
54 let arena = Arena::new();
55 Self {
56 arena,
57 config,
58 footnote_index,
59 }
60 }
61
62 pub fn get_footnote_index(&self, label: &str) -> Option<&usize> {
63 self.footnote_index.get(label)
64 }
65}
66
67/// Render a Markdown AST to plain text, stripping all formatting
68///
69/// # Arguments
70///
71/// * `ast` - The Markdown document AST to render
72/// * `config` - Configuration options controlling the output
73///
74/// # Returns
75///
76/// A `String` containing the plain text
77///
78/// # Examples
79///
80/// ```rust
81/// use markdown_ppp::ast::*;
82/// use markdown_ppp::plaintext_printer::{render_plaintext, config::Config};
83///
84/// let doc = Document {
85/// blocks: vec![Block::Paragraph(vec![
86/// Inline::Text("Hello ".to_string()),
87/// Inline::Strong(vec![Inline::Text("world".to_string())]),
88/// ])],
89/// };
90///
91/// let text = render_plaintext(&doc, Config::default());
92/// assert_eq!(text, "Hello world");
93/// ```
94pub fn render_plaintext(ast: &Document, config: Config) -> String {
95 let state = State::new(config, ast);
96 let doc = ast.to_doc(&state);
97
98 let mut buf = Vec::new();
99 doc.render(state.config.width, &mut buf)
100 .expect("Vec<u8> write is infallible");
101 String::from_utf8(buf).expect("pretty crate always produces valid UTF-8")
102}
103
104trait ToDoc<'a> {
105 fn to_doc(&self, state: &'a State<'a>) -> DocBuilder<'a, Arena<'a>, ()>;
106}
107
108impl<'a> ToDoc<'a> for Document {
109 fn to_doc(&self, state: &'a State<'a>) -> DocBuilder<'a, Arena<'a>, ()> {
110 self.blocks.to_doc(state)
111 }
112}
113