Skip to main content

rushdown/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4extern crate alloc;
5
6pub mod ast;
7pub mod context;
8pub mod parser;
9pub mod renderer;
10pub mod test;
11pub mod text;
12pub mod util;
13
14#[cfg(feature = "html-entities")]
15mod html_entity;
16
17mod scanner;
18
19mod error;
20use alloc::string::String;
21pub use error::Error;
22pub use error::Result;
23
24use crate::parser::Parser;
25use crate::parser::ParserExtension;
26use crate::renderer::html;
27use crate::renderer::TextWrite;
28use crate::text::BasicReader;
29
30/// Trait for converting Markdown to HTML.
31///
32/// # Errors
33/// Parsing phase will never fail, so the only possible errors are I/O errors during rendering.
34pub trait MarkdownToHtml<W: TextWrite = String> {
35    /// Converts the given Markdown source to HTML and writes it to the output.
36    fn markdown_to_html(&self, out: &mut W, source: &str) -> Result<()>;
37}
38
39impl<W: TextWrite, F> MarkdownToHtml<W> for F
40where
41    F: Fn(&mut W, &str) -> Result<()>,
42{
43    fn markdown_to_html(&self, out: &mut W, source: &str) -> Result<()> {
44        (self)(out, source)
45    }
46}
47
48/// Creates a function that converts Markdown to HTML using the specified parser and renderer.
49///
50/// # Arguments
51/// - `parser_options`: Options for the Markdown parser.
52/// - `renderer_options`: Options for the HTML renderer.
53/// - `parser_extension`: Extension for the Markdown parser. If no extensions are needed, use [`crate::parser::NO_EXTENSIONS`].
54/// - `renderer_extension`: Extension for the HTML renderer. If no extensions are needed, use [`crate::renderer::html::NO_EXTENSIONS`].
55///
56/// # Examples
57/// ```
58/// use core::fmt::Write;
59/// use rushdown::{
60///     new_markdown_to_html,
61///     parser::{self, ParserExtension},
62///     renderer::html::{self, RendererExtension},
63///     Result,
64/// };
65///
66/// let markdown_to_html = new_markdown_to_html(
67///     parser::Options::default(),
68///     html::Options::default(),
69///     parser::gfm_table().and(parser::gfm_task_list_item()),
70///     html::NO_EXTENSIONS,
71/// );
72/// let mut output = String::new();
73/// let input = "# Hello, World!\n\nThis is a **Markdown** document.";
74/// match markdown_to_html(&mut output, input) {
75///     Ok(_) => {
76///         println!("HTML output:\n{}", output);
77///     }
78///     Err(e) => {
79///         println!("Error: {:?}", e);
80///     }
81/// }
82/// ```
83pub fn new_markdown_to_html<'r, W>(
84    parser_options: parser::Options,
85    renderer_options: html::Options,
86    parser_extension: impl ParserExtension,
87    renderer_extension: impl html::RendererExtension<'r, W>,
88) -> impl Fn(&mut W, &str) -> Result<()> + 'r
89where
90    W: TextWrite + 'r,
91{
92    let parser = Parser::with_extensions(parser_options, parser_extension);
93    let renderer = html::Renderer::<'r, W>::with_extensions(renderer_options, renderer_extension);
94    move |output: &mut W, source: &str| {
95        let mut reader = BasicReader::new(source);
96        let (arena, document_ref) = parser.parse(&mut reader);
97        renderer.render(output, source, &arena, document_ref)
98    }
99}
100
101/// Creates a function that converts Markdown to HTML using the specified parser and renderer,
102/// with output written to a `String`.
103pub fn new_markdown_to_html_string<'r>(
104    parser_options: parser::Options,
105    renderer_options: html::Options,
106    parser_extension: impl ParserExtension,
107    renderer_extension: impl html::RendererExtension<'r, String>,
108) -> impl Fn(&mut String, &str) -> Result<()> + 'r {
109    new_markdown_to_html::<String>(
110        parser_options,
111        renderer_options,
112        parser_extension,
113        renderer_extension,
114    )
115}
116
117/// Converts Markdown(CommonMark) to HTML using default parser and renderer options.
118///
119/// # Examples
120/// ```
121/// use rushdown::markdown_to_html_string;
122/// let mut output = String::new();
123/// let input = "# Hello, World!\n\nThis is a **Markdown** document.";
124/// match markdown_to_html_string(&mut output, input) {
125///     Ok(_) => {
126///         println!("HTML output:\n{}", output);
127///     }
128///     Err(e) => {
129///     println!("Error: {:?}", e);
130///     }
131///  };
132///  ```
133pub fn markdown_to_html_string(output: &mut String, source: &str) -> Result<()> {
134    let parser = Parser::with_options(parser::Options::default());
135    let renderer = html::Renderer::with_options(html::Options::default());
136    let mut reader = BasicReader::new(source);
137    let (arena, document_ref) = parser.parse(&mut reader);
138    renderer.render(output, source, &arena, document_ref)
139}
140
141// macros {{{
142
143/// Helper macro to match kind data.
144///
145/// # Examples
146/// ```
147/// use rushdown::ast::{Arena, NodeRef, KindData, Paragraph};
148/// use rushdown::matches_kind;
149///
150/// let mut arena = Arena::new();
151/// let para_ref: NodeRef = arena.new_node(Paragraph::new());
152/// assert!(matches_kind!(arena, para_ref, Paragraph));
153/// assert!(matches_kind!(arena[para_ref], Paragraph));
154/// ```
155#[macro_export]
156macro_rules! matches_kind {
157    ($arena:expr, $node_ref:expr, $variant:ident) => {
158        matches!(
159            $arena[$node_ref].kind_data(),
160            $crate::ast::KindData::$variant(_)
161        )
162    };
163    ($node:expr, $variant:ident) => {
164        matches!($node.kind_data(), $crate::ast::KindData::$variant(_))
165    };
166}
167
168/// Helper macro to match extension kind.
169///
170/// # Examples
171/// ```
172/// use core::fmt::{self, Write};
173/// use rushdown::ast::{Arena, NodeRef, NodeType, NodeKind, KindData, PrettyPrint, pp_indent};
174/// use rushdown::matches_extension_kind;
175///
176/// #[derive(Debug)]
177/// struct Admonition {
178///     kind: String,
179/// }
180///
181/// impl NodeKind for Admonition {
182///     fn typ(&self) -> NodeType { NodeType::ContainerBlock }
183///
184///     fn kind_name(&self) -> &'static str { "Admonition" }
185/// }
186///
187/// impl PrettyPrint for Admonition {
188///     fn pretty_print(&self, w: &mut dyn Write, _source: &str, level: usize) -> fmt::Result {
189///         writeln!(w, "{}kind: {}", pp_indent(level), self.kind)
190///     }
191/// }
192///
193/// impl From<Admonition> for KindData {
194///     fn from(e: Admonition) -> Self { KindData::Extension(Box::new(e)) }
195/// }
196///
197/// let mut arena = Arena::new();
198/// let ext_ref: NodeRef = arena.new_node(Admonition{kind: "note".to_string()});
199/// assert!(matches_extension_kind!(arena, ext_ref, Admonition));
200/// assert!(matches_extension_kind!(arena[ext_ref], Admonition));
201/// ```
202///
203#[macro_export]
204macro_rules! matches_extension_kind {
205    ($arena:expr, $ref:expr, $ext_type:ty) => {
206        (if let $crate::ast::KindData::Extension(ref d) = $arena[$ref].kind_data() {
207            (d.as_ref() as &dyn ::core::any::Any)
208                .downcast_ref::<$ext_type>()
209                .is_some()
210        } else {
211            false
212        })
213    };
214    ($node:expr, $ext_type:ty) => {
215        (if let $crate::ast::KindData::Extension(ref d) = $node.kind_data() {
216            (d.as_ref() as &dyn ::core::any::Any)
217                .downcast_ref::<$ext_type>()
218                .is_some()
219        } else {
220            false
221        })
222    };
223}
224
225/// Helper macro to downcast extension data.
226///
227/// # Examples
228/// ```
229/// use core::fmt::{self, Write};
230/// use rushdown::ast::{Arena, NodeRef, NodeType, NodeKind, KindData, PrettyPrint, pp_indent};
231/// use rushdown::as_extension_data;
232///
233/// #[derive(Debug)]
234/// struct Admonition {
235///     kind: String,
236/// }
237///
238/// impl NodeKind for Admonition {
239///     fn typ(&self) -> NodeType { NodeType::ContainerBlock }
240///
241///     fn kind_name(&self) -> &'static str { "Admonition" }
242/// }
243///
244/// impl PrettyPrint for Admonition {
245///     fn pretty_print(&self, w: &mut dyn Write, _source: &str, level: usize) -> fmt::Result {
246///         writeln!(w, "{}kind: {}", pp_indent(level), self.kind)
247///     }
248/// }
249///
250/// impl From<Admonition> for KindData {
251///     fn from(e: Admonition) -> Self { KindData::Extension(Box::new(e)) }
252/// }
253///
254/// let mut arena = Arena::new();
255/// let ext_ref: NodeRef = arena.new_node(Admonition{kind: "note".to_string()});
256/// let ext_data = as_extension_data!(arena, ext_ref, Admonition);
257/// assert_eq!(ext_data.kind, "note");
258/// let ext_data = as_extension_data!(arena[ext_ref], Admonition);
259/// assert_eq!(ext_data.kind, "note");
260/// ```
261///
262#[macro_export]
263macro_rules! as_extension_data {
264    ($arena:expr, $ref:expr, $ext_type:ty) => {
265        (if let $crate::ast::KindData::Extension(ref d) = $arena[$ref].kind_data() {
266            (d.as_ref() as &dyn ::core::any::Any)
267                .downcast_ref::<$ext_type>()
268                .expect("Failed to downcast extension data")
269        } else {
270            panic!("Node is not an extension node")
271        })
272    };
273    ($node:expr, $ext_type:ty) => {
274        (if let $crate::ast::KindData::Extension(ref d) = $node.kind_data() {
275            (d.as_ref() as &dyn ::core::any::Any)
276                .downcast_ref::<$ext_type>()
277                .expect("Failed to downcast extension data")
278        } else {
279            panic!("Node is not an extension node")
280        })
281    };
282}
283
284/// Helper macro to downcast mutable extension data.
285///
286/// See [`as_extension_data!`] for examples.
287#[macro_export]
288macro_rules! as_extension_data_mut {
289    ($arena:expr, $ref:expr, $ext_type:ty) => {
290        (if let $crate::ast::KindData::Extension(ref mut d) = $arena[$ref].kind_data_mut() {
291            (d.as_mut() as &mut dyn ::core::any::Any)
292                .downcast_mut::<$ext_type>()
293                .expect("Failed to downcast extension data")
294        } else {
295            panic!("Node is not an extension node")
296        })
297    };
298    ($node:expr, $ext_type:ty) => {
299        (if let $crate::ast::KindData::Extension(ref mut d) = $node.kind_data_mut() {
300            (d.as_mut() as &mut dyn ::core::any::Any)
301                .downcast_mut::<$ext_type>()
302                .expect("Failed to downcast extension data")
303        } else {
304            panic!("Node is not an extension node")
305        })
306    };
307}
308
309/// Helper macro to work with kind data.
310///
311/// # Examples
312/// ```
313/// use rushdown::ast::{Arena, NodeRef, KindData, Emphasis};
314/// use rushdown::as_kind_data;
315///
316/// let mut arena = Arena::new();
317/// let para_ref: NodeRef = arena.new_node(Emphasis::new(1));
318/// let data = as_kind_data!(arena, para_ref, Emphasis);
319/// assert_eq!(data.level(), 1);
320/// let data = as_kind_data!(arena[para_ref], Emphasis);
321/// assert_eq!(data.level(), 1);
322/// ```
323#[macro_export]
324macro_rules! as_kind_data {
325    ($arena:expr, $node_ref:expr, $variant:ident) => {
326        (if let $crate::ast::KindData::$variant(ref d) = $arena[$node_ref].kind_data() {
327            d
328        } else {
329            panic!(
330                "Expected kind data variant {} but found {:?}",
331                stringify!($variant),
332                $arena[$node_ref].kind_data()
333            )
334        })
335    };
336    ($node:expr, $variant:ident) => {
337        (if let $crate::ast::KindData::$variant(ref d) = $node.kind_data() {
338            d
339        } else {
340            panic!(
341                "Expected kind data variant {} but found {:?}",
342                stringify!($variant),
343                $node.kind_data()
344            )
345        })
346    };
347}
348
349/// Helper macro to work with mutable kind data.
350///
351/// See [`as_kind_data!`] for examples.
352#[macro_export]
353macro_rules! as_kind_data_mut {
354    ($arena:expr, $node_ref:expr, $variant:ident) => {
355        (if let $crate::ast::KindData::$variant(ref mut d) = $arena[$node_ref].kind_data_mut() {
356            d
357        } else {
358            panic!(
359                "Expected kind data variant {} but found {:?}",
360                stringify!($variant),
361                $arena[$node_ref].kind_data()
362            )
363        })
364    };
365    ($node:expr, $variant:ident) => {
366        (if let $crate::ast::KindData::$variant(ref mut d) = $node.kind_data_mut() {
367            d
368        } else {
369            panic!(
370                "Expected kind data variant {} but found {:?}",
371                stringify!($variant),
372                $node.kind_data()
373            )
374        })
375    };
376}
377
378/// Helper macro to work with type data.
379///
380/// # Examples
381/// ```
382/// use rushdown::ast::{Arena, NodeRef, TypeData, Block, Paragraph};
383/// use rushdown::as_type_data;
384///
385/// let mut arena = Arena::new();
386/// let para_ref: NodeRef = arena.new_node(Paragraph::new());
387/// let data = as_type_data!(arena, para_ref, Block);
388/// assert!(data.lines().is_empty());
389/// let data = as_type_data!(arena[para_ref], Block);
390/// assert!(data.lines().is_empty());
391/// ```
392///
393#[macro_export]
394macro_rules! as_type_data {
395    ($arena:expr, $node_ref:expr, $variant:ident) => {
396        (if let $crate::ast::TypeData::$variant(ref d) = $arena[$node_ref].type_data() {
397            d
398        } else {
399            panic!(
400                "Expected type data variant {} but found {:?}",
401                stringify!($variant),
402                $arena[$node_ref].type_data()
403            )
404        })
405    };
406    ($node:expr, $variant:ident) => {
407        (if let $crate::ast::TypeData::$variant(ref d) = $node.type_data() {
408            d
409        } else {
410            panic!(
411                "Expected type data variant {} but found {:?}",
412                stringify!($variant),
413                $node.type_data()
414            )
415        })
416    };
417}
418
419/// Helper macro to work with mutable type data.
420///
421/// See [`as_type_data!`] for examples.
422#[macro_export]
423macro_rules! as_type_data_mut {
424    ($arena:expr, $node_ref:expr, $variant:ident) => {
425        (if let $crate::ast::TypeData::$variant(ref mut d) = $arena[$node_ref].type_data_mut() {
426            d
427        } else {
428            panic!(
429                "Expected type data variant {} but found {:?}",
430                stringify!($variant),
431                $arena[$node_ref].type_data()
432            )
433        })
434    };
435    ($node:expr, $variant:ident) => {
436        (if let $crate::ast::TypeData::$variant(ref mut d) = $node.type_data_mut() {
437            d
438        } else {
439            panic!(
440                "Expected type data variant {} but found {:?}",
441                stringify!($variant),
442                $node.type_data()
443            )
444        })
445    };
446}
447
448// }}} macros
449
450// debug stuff {{{
451
452#[cfg(all(not(feature = "std"), feature = "no-std-unix-debug"))]
453pub mod debug {
454    extern crate libc;
455    use core::fmt::{self, Write};
456
457    #[allow(dead_code)]
458    pub struct Stdout;
459
460    impl Write for Stdout {
461        fn write_str(&mut self, s: &str) -> fmt::Result {
462            unsafe {
463                libc::write(1, s.as_ptr() as *const _, s.len());
464            }
465            Ok(())
466        }
467    }
468
469    #[macro_export]
470    macro_rules! print {
471        ($($arg:tt)*) => {{
472            use core::fmt::Write;
473            let mut out = $crate::debug::Stdout;
474            core::write!(&mut out, $($arg)*).ok();
475        }};
476    }
477
478    #[macro_export]
479    macro_rules! println {
480        ($($arg:tt)*) => {{
481            $crate::print!("{}\n", format_args!($($arg)*));
482        }};
483    }
484}
485
486// }}}