RedQuilt
A modern Markdown document processor in pure Ruby, with an arena-style AST. Passes the full CommonMark spec test suite, and generally faster than kramdown.
Installation
Add this line to Gemfile:
gem "red_quilt"Quick Start
Parsing and rendering
require "red_quilt"
# Parse Markdown to a document
doc = RedQuilt.parse("# Hello\n\nThis is **bold**.")
html = doc.to_html
# => "<h1>Hello</h1>\n<p>This is <strong>bold</strong>.</p>\n"
# Or render directly (without building AST)
html = RedQuilt.render_html("# Hello\n\n**bold**")HTML is safe by default
RedQuilt.render_html("Hi <em>tag</em>")
# => "<p>Hi <em>tag</em></p>\n"
RedQuilt.render_html("Hi <em>tag</em>", allow_html: true)
# => "<p>Hi <em>tag</em></p>\n"Options
RedQuilt.parse and RedQuilt.render_html accept:
| Option | Default | Effect |
|---|---|---|
allow_html: |
false |
Pass raw HTML through instead of escaping it |
disallow_raw_html: |
false |
With allow_html, still neutralize GFM's dangerous tags (<script>, <iframe>, …) |
extended_autolinks: |
false |
GFM: linkify bare http(s):// / www. / email addresses |
footnotes: |
false |
GFM footnotes (see below) |
lint: |
false |
Collect lint diagnostics (empty links, missing image alt, heading-level skips) |
frontmatter: |
false |
Parse leading YAML frontmatter (---) as metadata (see below) |
Footnotes (opt-in)
RedQuilt.render_html(<<~MD, footnotes: true)
Here is a reference.[^1]
[^1]: And the footnote text.
MD
# The reference becomes a superscript link, and a trailing
# <section class="footnotes"> lists the referenced definitions (in
# first-reference order) with backrefs.Diagnostics
Parsing never raises on malformed input; warnings are collected on the document.
doc = RedQuilt.parse("[x](javascript:alert(1))", lint: true)
doc.diagnostics.map(&:rule) # => [:unsafe_url]
doc.diagnostics.first.severity # => :warningHeading anchors (opt-in)
render_html / to_html accept heading_ids: to give every heading a
slugified id for anchor links.
RedQuilt.render_html("# Hello World\n\n## はじめに", heading_ids: true)
# => "<h1 id=\"hello-world\">Hello World</h1>\n<h2 id=\"はじめに\">はじめに</h2>\n"Mermaid diagrams (opt-in)
render_html / to_html accept mermaid: to render ```mermaid fenced
code blocks for mermaid.js.
In standalone output the mermaid.js runtime is also loaded from a CDN.
RedQuilt.render_html("```mermaid\ngraph LR\n A --> B\n```", mermaid: true)
# => "<pre class=\"mermaid\">graph LR\n A --> B\n</pre>\n"
# Full page that renders the diagram in a browser (CDN script included):
RedQuilt.parse("```mermaid\ngraph LR\n A --> B\n```")
.to_html(standalone: true, mermaid: true)In standalone output each diagram is made interactive with svg-pan-zoom (loaded from a CDN).
YAML frontmatter (opt-in)
parse / render_html accept frontmatter: to extract a leading YAML
frontmatter block (the --- … --- fences used by Jekyll, Hugo).
The block is parsed with Psych.safe_load and removed from the rendered body;
the parsed Hash is exposed as Document#frontmatter.
doc = RedQuilt.parse(<<~MD, frontmatter: true)
---
title: My Page
lang: ja
---
# Body
MD
doc.frontmatter # => {"title" => "My Page", "lang" => "ja"}
doc.to_html # => "<h1>Body</h1>\n" (frontmatter stripped)In standalone output the frontmatter's title / lang fill in <title> /
<html lang> when no explicit argument is given (explicit argument >
frontmatter > default):
doc.to_html(standalone: true)
# <html lang="ja"> … <title>My Page</title> …The feature is opt-in, so a bare --- is never mistaken for frontmatter
unless frontmatter: true is passed. Frontmatter lines are blanked rather
than deleted, so body source spans and diagnostic line numbers stay relative
to the start of the file. Invalid YAML records a :frontmatter warning
diagnostic and leaves Document#frontmatter as nil without raising.
Tilt integration
RedQuilt ships a Tilt adapter.
NOTE: It is not loaded by default; require it explicitly and add tilt to your own bundle:
require "red_quilt/tilt"
Tilt.new("page.md").render # => HTML
Tilt.new("page.md", footnotes: true).renderNative options (allow_html:, footnotes:, …) pass straight through; Tilt's escape_html: convention is also honored.
Documentation
-
API reference —
Document/NodeRef/SourceSpan, supported syntax, and usage examples - Architecture overview (日本語)
- Arena usage guide (日本語)
- CommonMark conformance notes (日本語)
CommonMark Compatibility
RedQuilt achieves 100% compliance with the CommonMark v0.31.2 test cases. See the conformance notes for GFM extensions and intentional deviations.
Command-line Tool
RedQuilt ships with a redquilt CLI for converting Markdown files to HTML or inspecting the AST.
Basic usage
# Convert Markdown file to HTML
redquilt input.md > output.html
# Convert from stdin
echo "# Hello" | redquilt
# Output as AST (for debugging)
redquilt --format ast input.md
# Output as MDAST-compatible JSON (for external tools)
redquilt --format json input.md
# Standalone HTML document with title
redquilt --standalone --title "My Document" input.md
# Enable GFM extended autolinks / footnotes
redquilt --extended-autolinks --footnotes input.md
# Standalone page with the bare template (no embedded CSS)
redquilt --theme none input.md
# Write HTML to a file instead of stdout
redquilt -o output.html input.md
# Render and open the result in the default browser
redquilt --open input.md
# Render mermaid code blocks as diagrams (loads mermaid.js from a CDN)
redquilt --mermaid --open input.mdOptions
--format FORMAT Output format: html (default), ast, json
--allow-html Pass raw HTML through to the output
--disallow-raw-html With --allow-html, filter GFM's dangerous tags
--extended-autolinks Linkify bare URLs and email addresses (GFM)
--footnotes Enable GFM footnotes
--lint Collect lint diagnostics
--[no-]standalone Wrap HTML in full document (default: on)
--auto-title Use the first heading's text as <title>
--title TITLE Explicit <title> text
--lang LANG html lang attribute (default: "en")
--css URL Add a stylesheet link
--theme THEME Embedded stylesheet: default (default) or none
-o, --output FILE Write HTML to FILE instead of stdout
--open Write HTML to a file and open it in the default
browser (forces --standalone; uses a file under
Dir.tmpdir when -o is not given)
--mermaid Render `mermaid` code blocks as diagrams (loads
mermaid.js from a CDN in standalone output)
--frontmatter Parse leading YAML frontmatter (---) as metadata;
fills <title>/lang in standalone output
--diagnostics Print diagnostics to stderr
--diagnostics-only Print diagnostics only (suppress output)
-h, --help Show help
-v, --version Show version
Exit code is 0 on success, 1 if errors are detected.
Safe-by-Default HTML Rendering
Security model
RedQuilt prioritizes security by default:
# Default: All HTML is escaped, dangerous URLs blocked
RedQuilt.render_html("<script>alert('xss')</script>")
# => "<p><script>alert('xss')</script></p>"
RedQuilt.render_html("[click](javascript:alert(1))")
# => "<p><a href=\"\">click</a></p>"Allowed URL schemes
In link/image destinations, only these schemes are permitted:
- Absolute:
http://,https://,ftp://,tel:,ssh:// - Relative:
/path,#anchor,path/to/file - Special:
mailto:(autolinks only)
All other schemes (javascript:, data:, vbscript:, etc.) are blocked by replacing the URL with an empty string.
Autolinks (<scheme:...>) follow CommonMark and allow arbitrary schemes, so they use a denylist instead: only the script-executing schemes javascript:, vbscript:, and data: are blocked.
Opting into HTML pass-through
# Allow raw HTML (use with trusted input only)
RedQuilt.render_html(user_markdown, allow_html: true)
# This passes HTML blocks and inline tags through unchangedDevelopment
Running tests
bundle exec rake specRuns the full CommonMark 0.31.2 conformance suite (all 652 official examples,
parsed directly from spec/fixtures/cmark_spec-0.31.2.md) plus RedQuilt's own
feature tests — 1000 examples in total.
Benchmark
ruby spec/bench_inline.rb
ruby spec/bench_block.rbProfiles parse performance on various Markdown patterns.
Performance (v0.6.1, Ruby 4.0.5)
Comparison against kramdown on arm64-darwin (Apple Silicon), measured with spec/bench_vs_kramdown.rb (benchmark-ips):
| Fixture | Size | RedQuilt (i/s) | kramdown (i/s) | RedQuilt vs kramdown |
|---|---|---|---|---|
| short_paragraph | 49 B | 26,531 | 5,416 | 4.90x faster |
| long_paragraph | 1.4 KB | 981 | 926 | within error |
| nested_emphasis | 1.4 KB | 999 | 689 | 1.45x faster |
| many_links | 2.0 KB | 1,131 | 794 | 1.43x faster |
| mixed_markup | 1.8 KB | 1,028 | 729 | 1.41x faster |
| deep_nesting | 800 B | 827 | 349 | 2.37x faster |
| cmark_spec | 205 KB | 39.1 | 30.2 | 1.30x faster |
Reproduce locally:
bundle exec ruby spec/bench_vs_kramdown.rbLicense
MIT