pulldown-cmark
| Language | Rust |
|---|---|
| License | MIT |
| Repository | github.com/pulldown-cmark/pulldown-cmark |
| Documentation | docs.rs/pulldown-cmark, guide |
| Depends on | bitflags, memchr, unicase; optional getopts (CLI), pulldown-cmark-escape (HTML), serde |
| Read / write | parses CommonMark plus optional extensions; renders HTML |
pulldown-cmark doesn’t build a syntax tree. Parser is an iterator of Events: Start(Tag), End(TagEnd), Text, Code, Html, SoftBreak, Rule and others. You can map, filter or collect the events and pass them to html::push_html. This design makes it fast and memory-efficient and makes simple transformations (rewriting links, collecting headings) a few lines of iterator code.
It passes the CommonMark spec tests. Extensions are Options flags: ENABLE_TABLES, ENABLE_TASKLISTS, ENABLE_STRIKETHROUGH, ENABLE_FOOTNOTES (or ENABLE_OLD_FOOTNOTES), ENABLE_HEADING_ATTRIBUTES, ENABLE_SMART_PUNCTUATION, ENABLE_MATH, ENABLE_GFM (blockquote alerts), ENABLE_YAML_STYLE_METADATA_BLOCKS, ENABLE_DEFINITION_LIST, ENABLE_SUPERSCRIPT, ENABLE_SUBSCRIPT and ENABLE_WIKILINKS. Raw HTML passes through as Event::Html; filter it out for untrusted input.
Installation#
cargo add pulldown-cmark
Basic usage: convert Markdown to HTML#
use pulldown_cmark::{html, Options, Parser};
use std::fs;
fn main() -> std::io::Result<()> {
let markdown = fs::read_to_string("README.md")?;
let mut options = Options::empty();
options.insert(Options::ENABLE_TABLES);
options.insert(Options::ENABLE_STRIKETHROUGH);
options.insert(Options::ENABLE_TASKLISTS);
options.insert(Options::ENABLE_FOOTNOTES);
let parser = Parser::new_ext(&markdown, options);
let mut html_out = String::new();
html::push_html(&mut html_out, parser);
fs::write("README.html", html_out)?;
Ok(())
}
How to list headings and links#
use pulldown_cmark::{Event, Parser, Tag, TagEnd};
let mut in_heading = None;
let mut title = String::new();
for event in Parser::new(&markdown) {
match event {
Event::Start(Tag::Heading { level, .. }) => {
in_heading = Some(level);
title.clear();
}
Event::Text(t) | Event::Code(t) if in_heading.is_some() => title.push_str(&t),
Event::End(TagEnd::Heading(level)) => {
println!("{level}: {title}"); // h1, h2, ...
in_heading = None;
}
Event::Start(Tag::Link { dest_url, .. }) => println!("link: {dest_url}"),
_ => {}
}
}
How to rewrite links while rendering#
use pulldown_cmark::{html, CowStr, Event, Parser, Tag};
let parser = Parser::new(&markdown).map(|event| match event {
Event::Start(Tag::Link { link_type, dest_url, title, id }) => {
let url: CowStr = if dest_url.ends_with(".md") {
dest_url.replace(".md", ".html").into()
} else {
dest_url
};
Event::Start(Tag::Link { link_type, dest_url: url, title, id })
}
other => other,
});
let mut out = String::new();
html::push_html(&mut out, parser);
How to drop raw HTML from untrusted input#
let parser = Parser::new(&untrusted)
.filter(|e| !matches!(e, Event::Html(_) | Event::InlineHtml(_)));
Links can still use javascript: URLs; sanitize the output with a crate like ammonia for full protection.
How to read YAML front matter#
use pulldown_cmark::{Event, MetadataBlockKind, Options, Parser, Tag};
let mut in_meta = false;
let mut yaml = String::new();
for event in Parser::new_ext(&markdown, Options::ENABLE_YAML_STYLE_METADATA_BLOCKS) {
match event {
Event::Start(Tag::MetadataBlock(MetadataBlockKind::YamlStyle)) => in_meta = true,
Event::End(_) if in_meta => in_meta = false,
Event::Text(t) if in_meta => yaml.push_str(&t),
_ => {}
}
}
// parse `yaml` with a YAML crate
Tips#
Parser::into_offset_iter()yields each event with its byte range in the source, for editors and error messages.pulldown-cmark-to-cmark(a separate crate) renders events back to Markdown.- Disable default features (
default-features = false, features = ["html"]) to skip the command line tool’s dependencies.