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.source().is_empty());
389/// let data = as_type_data!(arena[para_ref], Block);
390/// assert!(data.source().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/// Helper macro to construct an AST.
449///
450/// # Examples
451/// ```rust
452/// use rushdown::md_ast;
453/// use rushdown::ast::*;
454/// use rushdown::renderer::html;
455///
456/// let mut arena = Arena::new();
457/// let doc = md_ast!(&mut arena, Document::new() => {
458///     Blockquote::new() => {
459///         Paragraph::new(); { |node: &mut Node| {
460///             node.attributes_mut().insert("class", "paragraph".into());
461///         } } => {
462///             Text::new("Hello, World!")
463///         },
464///         Paragraph::new() => {
465///             Text::new("This is a test.")
466///         }
467///     }
468/// });
469/// let renderer = html::Renderer::with_options(html::Options::default());
470/// let mut output = String::new();
471/// renderer.render(&mut output, "", &arena, doc).expect("Failed to render HTML");
472/// assert_eq!(output, "<blockquote>\n<p class=\"paragraph\">Hello, World!</p>\n<p>This is a test.</p>\n</blockquote>\n");
473/// ```
474#[macro_export]
475macro_rules! md_ast {
476    ($arena:expr, $root:expr => { $($children:tt)* }) => {{
477        let __root = $arena.new_node($root);
478        md_ast!(@children $arena, __root, { $($children)* });
479        __root
480    }};
481
482    // make a node, optional post hook written as: ;{ <expr> }
483    (@mk $arena:expr, $spec:expr) => {{
484        $arena.new_node($spec)
485    }};
486    (@mk $arena:expr, $spec:expr, @{ $post:expr }) => {{
487        let __n = $arena.new_node($spec);
488        ($post)(&mut $arena[__n]);
489        __n
490    }};
491
492    (@children $arena:expr, $parent:ident, { }) => {};
493
494    // child with grandchildren
495    (@children $arena:expr, $parent:ident, {
496        $child:expr $( ;{ $post:expr } )? => { $($grand:tt)* } $(, $($rest:tt)*)?
497    }) => {{
498        let __child = md_ast!(@mk $arena, $child $(, @{ $post })?);
499        $parent.append_child($arena, __child);
500
501        md_ast!(@children $arena, __child, { $($grand)* });
502        md_ast!(@children $arena, $parent, { $($($rest)*)? });
503    }};
504
505    // leaf child
506    (@children $arena:expr, $parent:ident, {
507        $child:expr $( ;{ $post:expr } )? $(, $($rest:tt)*)?
508    }) => {{
509        let __child = md_ast!(@mk $arena, $child $(, ;{ $post })?);
510        $parent.append_child($arena, __child);
511
512        md_ast!(@children $arena, $parent, { $($($rest)*)? });
513    }};
514}
515
516/// Helper macro to traverse the AST by calling methods on nodes.
517///
518/// # Examples
519/// ```rust
520/// use rushdown::md_ast;
521/// use rushdown::node_path;
522/// use rushdown::as_kind_data;
523/// use rushdown::ast::*;
524/// use rushdown::renderer::html;
525///
526/// let mut arena = Arena::new();
527/// let doc = md_ast!(&mut arena, Document::new() => {
528///     Blockquote::new() => {
529///         Paragraph::new(); { |node: &mut Node| {
530///             node.attributes_mut().insert("class", "paragraph".into());
531///         } } => {
532///             Text::new("Hello, World!")
533///         },
534///         Paragraph::new() => {
535///             Text::new("This is a test.")
536///         }
537///     }
538/// });
539/// let text_opt = node_path!(arena, doc, first_child, first_child, first_child);
540/// assert_eq!(as_kind_data!(arena, text_opt.unwrap(), Text).str(""), "Hello, World!");
541/// ```
542#[macro_export]
543macro_rules! node_path {
544    ($arena:expr, $start:expr $(, $method:ident )* ) => {{
545        let mut node_opt = Some($start);
546        $(
547            node_opt = node_opt.and_then(|n| $arena[n].$method());
548        )*
549        node_opt
550    }};
551}
552
553// }}} macros
554
555// debug stuff {{{
556
557#[cfg(not(feature = "std"))]
558pub mod debug {
559    #[cfg(feature = "no-std-unix-debug")]
560    extern crate libc;
561
562    use core::fmt::{self, Write};
563
564    #[allow(dead_code)]
565    pub struct Stdout;
566
567    impl Write for Stdout {
568        #[allow(unreachable_code, unused)]
569        fn write_str(&mut self, s: &str) -> fmt::Result {
570            #[cfg(feature = "no-std-unix-debug")]
571            unsafe {
572                libc::write(1, s.as_ptr() as *const _, s.len());
573            }
574            Ok(())
575        }
576    }
577
578    #[macro_export]
579    macro_rules! print {
580        ($($arg:tt)*) => {{
581            use core::fmt::Write;
582            let mut out = $crate::debug::Stdout;
583            core::write!(&mut out, $($arg)*).ok();
584        }};
585    }
586
587    #[macro_export]
588    macro_rules! println {
589        ($($arg:tt)*) => {{
590            $crate::print!("{}\n", format_args!($($arg)*));
591        }};
592    }
593}
594// }}}