Skip to main content

socketry_markdown/
lib.rs

1// Released under the MIT License.
2// Copyright, 2022-2025, by Titus Wormer.
3// Copyright, 2023, by Rafael Bachmann.
4// Copyright, 2024, by Bnchi.
5// Copyright, 2026, by Samuel Williams.
6
7//! Public API of `socketry-markdown`.
8//!
9//! This module exposes primarily [`to_html()`][].
10//! It also exposes [`to_html_with_options()`][] and [`to_mdast()`][].
11//!
12//! * [`to_html()`][]
13//!   — safe way to transform (untrusted?) markdown into HTML
14//! * [`to_html_with_options()`][]
15//!   — like `to_html` but lets you configure how markdown is turned into
16//!   HTML, such as allowing dangerous HTML or turning on/off different
17//!   constructs (GFM, MDX, and the like)
18//! * [`to_mdast()`][]
19//!   — turn markdown into a syntax tree
20//! * [`Renderer`][]
21//!   — render AST nodes and fragments with a user-defined renderer
22//! * [`HTMLRenderer`][]
23//!   — render an AST node or fragment directly as HTML
24//! * [`MarkdownRenderer`][]
25//!   — render an AST node or fragment back to Markdown
26//!
27//! ## Features
28//!
29//! * **`default`**
30//!   — nothing is enabled by default
31//! * **`log`**
32//!   — enable logging (includes `dep:log`);
33//!   you can show logs with `RUST_LOG=debug`
34//! * **`serde`**
35//!   — enable serde to serialize ASTs and configuration (includes `dep:serde`)
36#![no_std]
37#![deny(clippy::pedantic)]
38#![allow(clippy::doc_link_with_quotes)]
39#![allow(clippy::missing_panics_doc)]
40#![allow(clippy::must_use_candidate)]
41#![allow(clippy::too_many_lines)]
42#![allow(clippy::result_large_err)]
43#![doc(
44    html_logo_url = "https://raw.githubusercontent.com/wooorm/markdown-rs/8924580/media/logo-monochromatic.svg?sanitize=true"
45)]
46
47extern crate alloc;
48mod configuration;
49mod construct;
50mod event;
51pub mod markdown;
52mod parser;
53pub mod renderer;
54mod resolve;
55mod state;
56mod subtokenize;
57mod to_html;
58mod to_mdast;
59mod tokenizer;
60mod util;
61
62pub mod mdast; // To do: externalize?
63pub mod message; // To do: externalize.
64pub mod unist; // To do: externalize.
65
66#[doc(hidden)]
67pub use util::character_reference::{decode_named, decode_numeric};
68
69#[doc(hidden)]
70pub use util::identifier::{id_cont, id_start};
71
72#[doc(hidden)]
73pub use util::sanitize_uri::sanitize;
74
75#[doc(hidden)]
76pub use util::location::Location;
77
78pub use util::line_ending::LineEnding;
79
80pub use util::mdx::{
81    EsmParse as MdxEsmParse, ExpressionKind as MdxExpressionKind,
82    ExpressionParse as MdxExpressionParse, Signal as MdxSignal,
83};
84
85pub use configuration::{CompileOptions, Constructs, Options, ParseOptions};
86pub use markdown::{to_markdown, to_markdown_with_options};
87pub use markdown::{IndentOptions, Options as MarkdownOptions};
88pub use renderer::{HTMLRenderer, MarkdownRenderer, Renderer};
89
90use alloc::string::String;
91
92/// Turn markdown into HTML.
93///
94/// Compiles markdown to HTML according to `CommonMark`.
95/// Use [`to_html_with_options()`][] to configure how markdown is turned into
96/// HTML.
97///
98/// ## Examples
99///
100/// ```
101/// use socketry_markdown::to_html;
102///
103/// assert_eq!(to_html("# Hi Mercury!"), "<h1>Hi Mercury!</h1>");
104/// ```
105pub fn to_html(value: &str) -> String {
106    to_html_with_options(value, &Options::default()).unwrap()
107}
108
109/// Turn markdown into HTML, with configuration.
110///
111/// ## Errors
112///
113/// `to_html_with_options()` never errors with normal markdown because markdown
114/// does not have syntax errors, so feel free to `unwrap()`.
115/// However, MDX does have syntax errors.
116/// When MDX is turned on, there are several errors that can occur with how
117/// expressions, ESM, and JSX are written.
118///
119/// ## Examples
120///
121/// ```
122/// use socketry_markdown::{to_html_with_options, CompileOptions, Options};
123/// # fn main() -> Result<(), socketry_markdown::message::Message> {
124///
125/// // Use GFM:
126/// let result = to_html_with_options("~Venus~Mars!", &Options::gfm())?;
127///
128/// assert_eq!(result, "<p><del>Venus</del>Mars!</p>");
129///
130/// // Live dangerously / trust the author:
131/// let result = to_html_with_options("<div>\n\n# Hi Jupiter!\n\n</div>", &Options {
132///     compile: CompileOptions {
133///       allow_dangerous_html: true,
134///       allow_dangerous_protocol: true,
135///       ..CompileOptions::default()
136///     },
137///     ..Options::default()
138/// })?;
139///
140/// assert_eq!(result, "<div>\n<h1>Hi Jupiter!</h1>\n</div>");
141/// # Ok(())
142/// # }
143/// ```
144pub fn to_html_with_options(value: &str, options: &Options) -> Result<String, message::Message> {
145    let (events, parse_state) = parser::parse(value, &options.parse)?;
146    to_html::compile(
147        &events,
148        parse_state.bytes,
149        &options.compile,
150        options.parse.inline_code_info,
151    )
152}
153
154/// Turn markdown into a syntax tree.
155///
156/// ## Errors
157///
158/// `to_mdast()` never errors with normal markdown because markdown does not
159/// have syntax errors, so feel free to `unwrap()`.
160/// However, MDX does have syntax errors.
161/// When MDX is turned on, there are several errors that can occur with how
162/// JSX, expressions, or ESM are written.
163///
164/// ## Examples
165///
166/// ```
167/// use socketry_markdown::{to_mdast, ParseOptions};
168/// # fn main() -> Result<(), socketry_markdown::message::Message> {
169///
170/// let tree = to_mdast("# Hi *Earth*!", &ParseOptions::default())?;
171///
172/// println!("{:?}", tree);
173/// // => Root { children: [Heading { children: [Text { value: "Hi ", position: Some(1:3-1:6 (2-5)) }, Emphasis { children: [Text { value: "Earth", position: Some(1:7-1:12 (6-11)) }], position: Some(1:6-1:13 (5-12)) }, Text { value: "!", position: Some(1:13-1:14 (12-13)) }], position: Some(1:1-1:14 (0-13)), depth: 1 }], position: Some(1:1-1:14 (0-13)) }
174/// # Ok(())
175/// # }
176/// ```
177pub fn to_mdast(value: &str, options: &ParseOptions) -> Result<mdast::Node, message::Message> {
178    let (events, parse_state) = parser::parse(value, options)?;
179    let node = to_mdast::compile(&events, parse_state.bytes, options.inline_code_info)?;
180    Ok(node)
181}