markdown_fmt/
lib.rs

1//! Easily format Markdown. [markdown_fmt] supports [CommonMark] and [GitHub Flavored Markdown].
2//!
3//! [markdown_fmt]: index.html
4//! [CommonMark]: https://spec.commonmark.org/
5//! [GitHub Flavored Markdown]: https://github.github.com/gfm/
6//!
7//! # Getting Started
8//!
9//! ```rust
10//! use markdown_fmt::rewrite_markdown;
11//!
12//! let markdown = r##" # Getting Started
13//! 1. numbered lists
14//! 1.  are easy!
15//! "##;
16//!
17//! let formatted = r##"# Getting Started
18//! 1. numbered lists
19//! 1. are easy!
20//! "##;
21//!
22//! let output = rewrite_markdown(markdown)?;
23//! # assert_eq!(output, formatted);
24//! # Ok::<(), std::fmt::Error>(())
25//! ```
26//!
27//! # Using the [Builder](builder::FormatterBuilder)
28//!
29//! The builder gives you more control to configure Markdown formatting.
30//!
31//! ````rust
32//! use markdown_fmt::{rewrite_markdown, rewrite_markdown_with_builder, FormatterBuilder};
33//!
34//! let builder = FormatterBuilder::with_code_block_formatter(|info_string, code_block| {
35//!     match info_string.to_lowercase().as_str() {
36//!         "markdown" => rewrite_markdown(&code_block).unwrap_or(code_block),
37//!         _ => code_block
38//!     }
39//! });
40//!
41//! let markdown = r##" # Using the Builder
42//! + markdown code block nested in a list
43//!   ```markdown
44//!   A nested markdown snippet!
45//!
46//!    * unordered lists
47//!    are also pretty easy!
48//!    - `-` or `+` can also be used as unordered list markers.
49//!    ```
50//! "##;
51//!
52//! let formatted = r##"# Using the Builder
53//! + markdown code block nested in a list
54//!   ```markdown
55//!   A nested markdown snippet!
56//!
57//!   * unordered lists
58//!     are also pretty easy!
59//!   - `-` or `+` can also be used as unordered list markers.
60//!   ```
61//! "##;
62//!
63//! let output = rewrite_markdown_with_builder(markdown, builder)?;
64//! # assert_eq!(output, formatted);
65//! # Ok::<(), std::fmt::Error>(())
66//! ````
67
68use std::borrow::Cow;
69use std::collections::VecDeque;
70use std::fmt::Write;
71use std::iter::Peekable;
72use std::num::ParseIntError;
73use std::ops::Range;
74use std::str::FromStr;
75
76use itertools::{EitherOrBoth, Itertools};
77use pulldown_cmark::{
78    Alignment, CodeBlockKind, CowStr, Event, HeadingLevel, LinkType, Options, Parser, Tag, TagEnd,
79};
80use textwrap::Options as TextWrapOptions;
81use unicode_segmentation::UnicodeSegmentation;
82
83mod adapters;
84mod builder;
85mod config;
86mod escape;
87mod formatter;
88mod html_block;
89mod links;
90pub mod list;
91mod paragraph;
92mod table;
93#[cfg(test)]
94mod test;
95mod utils;
96
97use crate::{
98    adapters::LooseListExt, builder::CodeBlockFormatter, formatter::FormatState, table::TableState,
99    utils::unicode_str_width,
100};
101pub use crate::{
102    builder::FormatterBuilder,
103    config::Config,
104    formatter::MarkdownFormatter,
105    html_block::PreservingHtmlBlock,
106    list::{ListMarker, OrderedListMarker, ParseListMarkerError, UnorderedListMarker},
107    paragraph::{Paragraph, ParagraphFormatter},
108};
109
110/// Reformat a markdown snippet with all the default settings.
111///
112/// ```rust
113/// # use markdown_fmt::rewrite_markdown;
114/// let markdown = r##"  #   Learn Rust Checklist!
115/// 1. Read [The Book]
116///  2.  Watch tutorials
117///   3.   Write some code!
118///
119/// [The Book]: https://doc.rust-lang.org/book/
120/// "##;
121///
122/// let formatted_markdown = r##"# Learn Rust Checklist!
123/// 1. Read [The Book]
124/// 2. Watch tutorials
125/// 3. Write some code!
126///
127/// [The Book]: https://doc.rust-lang.org/book/
128/// "##;
129///
130/// let output = rewrite_markdown(markdown).unwrap();
131/// assert_eq!(output, formatted_markdown);
132/// ```
133pub fn rewrite_markdown(input: &str) -> Result<String, std::fmt::Error> {
134    rewrite_markdown_with_builder(input, FormatterBuilder::default())
135}
136
137/// Reformat a markdown snippet based on Steven Hé (Sīchàng)'s opinion.
138///
139/// ```rust
140/// # use markdown_fmt::rewrite_markdown_sichanghe_opinion;
141/// let markdown = r##"  #   Learn Rust Checklist!
142/// 1. Read [The Book]
143///  2.  Watch tutorials
144///   3.   Write some code!
145///
146/// [The Book]: https://doc.rust-lang.org/book/
147/// "##;
148///
149/// let formatted_markdown = r##"# Learn Rust Checklist!
150/// 1. Read [The Book]
151/// 1. Watch tutorials
152/// 1. Write some code!
153///
154/// [The Book]: https://doc.rust-lang.org/book/
155/// "##;
156///
157/// let output = rewrite_markdown_sichanghe_opinion(markdown).unwrap();
158/// assert_eq!(output, formatted_markdown);
159/// ```
160pub fn rewrite_markdown_sichanghe_opinion(input: &str) -> Result<String, std::fmt::Error> {
161    let mut builder = FormatterBuilder::default();
162    builder.sichanghe_config();
163    rewrite_markdown_with_builder(input, builder)
164}
165
166/// Reformat a markdown snippet with user specified settings
167///
168/// ```rust
169/// # use markdown_fmt::{rewrite_markdown_with_builder, FormatterBuilder};
170/// let markdown = r##"  #   Learn Rust Checklist!
171/// 1. Read [The Book]
172///  2.  Watch tutorials
173///   3.   Write some code!
174///
175/// [The Book]: https://doc.rust-lang.org/book/
176/// "##;
177///
178/// let formatted_markdown = r##"# Learn Rust Checklist!
179/// 1. Read [The Book]
180/// 2. Watch tutorials
181/// 3. Write some code!
182///
183/// [The Book]: https://doc.rust-lang.org/book/
184/// "##;
185///
186/// let builder = FormatterBuilder::default();
187/// let output = rewrite_markdown_with_builder(markdown, builder).unwrap();
188/// assert_eq!(output, formatted_markdown);
189/// ```
190pub fn rewrite_markdown_with_builder(
191    input: &str,
192    builder: FormatterBuilder,
193) -> Result<String, std::fmt::Error> {
194    tracing::trace!(?builder);
195    let formatter = builder.build();
196    formatter.format(input)
197}