Skip to main content

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