README.md
August 16, 2026 · View on GitHub

This is Marvdown ⚡️ A stupid simple Markdown parser
nimble install marvdown
Note
Marv is still in early development. Some features are not fully implemented yet. Contributions are welcome!
😍 Key Features
- Extremely Fast & Lightweight! Check benchmarks
- Compiled cross-platform CLI app
- Nim library for easy integration in your 👑 Nim projects
- Markdown to HTML
- Auto-generate heading IDs for anchor links
- Table of contents data via
getSelectorItems
- Markdown to JSON (AST)
- GitHub Flavored Markdown (partial): strikethrough, tables, task lists, autolinks, alerts
- GitHub-style alerts (
NOTE,TIP,IMPORTANT,WARNING,CAUTION) - Footnotes
- Reference-style links
- Bare URL & email autolinks
- YAML front matter
- Components:
@includefiles,@attrprops &$variableinterpolation - Lazy-loading for iframes, videos & images
- Custom per-line transform hook (
customTransform) - Markdown to PDF
About
Marv is a stupid simple markdown parser written in Nim. It can be used as a library in your Nim projects or as a CLI tool to convert markdown files to HTML. It supports headings, paragraphs, bold, italic, strikethrough, links, images, lists (incl. task lists), blockquotes (incl. GitHub alerts), code blocks, inline code, tables (with optional footer), footnotes, reference links, raw HTML, autolinks, YAML front matter and more.
Installing
Install Marvdown via Nimble
nimble install marvdown
Quick Start
From the command line
marvdown html sample.md --optAnchors
marvdown html sample.md --optAnchors --output out.html
marvdown json sample.md
--optAnchors— generate heading anchors (with a 🔗 link icon)--bench— print timing stats--components— enable@include/@attr/$variablecomponents
As a Nim library
import marvdown
# one-liner
echo marvdown.toHtml(readFile("sample.md"))
# with custom options
let opts = MarkdownOptions(
allowed: @[tagP, tagStrong, tagEm, tagA, tagCode, tagPre],
enableAnchors: true,
anchorIcon: "🔗"
)
var md = newMarkdown(readFile("sample.md"), opts)
echo md.toHtml()
Settings (MarkdownOptions)
All knobs live on the MarkdownOptions object passed to newMarkdown:
let opts = MarkdownOptions(
# Which raw HTML tags are allowed. Empty `@[]` means NO raw HTML.
allowed: @[tagA, tagDiv, tagSpan, tagImg, tagP, tagPre, tagCode],
# …or allow tags by category instead:
allowTagsByType: none(TagType), # tagNone | tagInline | tagBlock | tagAll
allowInlineStyle: false, # allow `style` attributes/tags
allowHtmlAttributes: false, # allow attributes like `width`, `title`
enableAnchors: true, # add id="…" + anchor link to headings
anchorIcon: "🔗", # icon used inside the anchor link
showFootnotes: true, # render footnotes at the end of the doc
htmlTableClasses: none(seq[string]), # e.g. some(@["table", "table-striped"])
enableEmailAutolinks: false, # `<user@example.com>` → mailto link
enableComponents: false, # enable @include / @attr / $variable
componentBaseDir: "", # base dir for @include paths
customTransform: nil, # proc(line: string): string per-line hook
lazyloadIframes: false, # <iframe src> → data-src
lazyloadVideos: false, # <video>/<audio>/<source> src → data-src
lazyloadImages: false # <img> &  src → data-src
)
Features
Headings & anchors
# Hello World
## Sub section
<h1 id="hello-world"><a href="#hello-world" class="anchor-link">🔗</a>Hello World</h1>
<h2 id="sub-section"><a href="#sub-section" class="anchor-link">🔗</a>Sub section</h2>
Build a table of contents from the generated anchors:
for item in md.getSelectorItems(): # seq[(level, anchor, title)]
echo item.level, ". ", item.title, " #", item.anchor
Inline formatting
**bold**, *italic*, __strong__, _italic_, ~~strikethrough~~ and `code`.
<p><strong>bold</strong>, <em>italic</em>, <strong>strong</strong>, <em>italic</em>,
<del>strikethrough</del> and <code>code</code>.</p>
Links & autolinks
[inline link](https://example.com "title")
Bare https://example.com auto-links.
<user@example.com> <!-- enable enableEmailAutolinks -->
<p><a href="https://example.com" title="title">inline link</a></p>
<p>Bare <a href="https://example.com">https://example.com</a> auto-links.</p>
<p><a href="mailto:user@example.com">user@example.com</a></p>
Images

<img src="https://example.com/logo.png" alt="Marvdown logo" title="Logo" />
Lists & task lists
- unordered item
1. ordered one
- [x] completed task
- [ ] pending task
<ul><li>unordered item</li></ul>
<ol><li>ordered one</li></ol>
<ul>
<li><input type="checkbox" checked disabled>completed task</li>
<li><input type="checkbox" disabled>pending task</li>
</ul>
Blockquotes & alerts
> A wise quote with `code`.
> [!WARNING]
> Watch out!
<blockquote>A wise quote with <code>code</code>.</blockquote>
<div class="alert alert-warning rounded-4" role="alert">
<div class="alert-content">Watch out!</div>
</div>
Supported markers: NOTE, TIP, IMPORTANT, WARNING, CAUTION.
Code blocks
```nim
proc hello =
echo "hi"
```
<pre><code class="language-nim">proc hello =
echo "hi"</code></pre>
Indented code blocks (4 spaces) are supported too.
Tables (with optional footer)
| Name | Role |
| ---- | ---- |
| Ana | CEO |
| Bob | CTO |
|------|------|
| Total | 2 |
<table>
<thead><tr><th>Name</th><th>Role</th></tr></thead>
<tbody><tr><td>Ana</td><td>CEO</td></tr><tr><td>Bob</td><td>CTO</td></tr></tbody>
<tfoot><tr><td>Total</td><td>2</td></tr></tfoot>
</table>
Add CSS classes with htmlTableClasses: some(@["table", "table-striped"]).
Footnotes
This needs a citation[^1].
[^1]: The footnote body.
<p>This needs a citation<sup class="footnote-ref"><a href="#fn-1">1</a></sup>.</p>
<hr><div class="footnotes"><div class="footnote" id="fn-1"><sup>1</sup> The footnote body.</div></div>
Reference links
See the [project][repo].
[repo]: https://github.com/openpeeps/marvdown
<p>See the <a href="https://github.com/openpeeps/marvdown">project</a>.</p>
Supports explicit [text][ref], collapsed [text][] and shortcut [text] references.
Raw HTML
<div class="hero"><p>custom block</p></div>
<div class="hero"><p>custom block</p></div>
Raw HTML is gated by the allowed / allowTagsByType options.
Components (@include, @attr, $variable)
Include other files (markdown or HTML) and use props & variables in the HTML:
card.html:
<div @title="Marvdown" @badge="v0.1.4">
<h2>$title</h2>
<p>Release <code>$badge</code></p>
</div>
page.md:
@include("card.html")
<div>
<h2>Marvdown</h2>
<p>Release <code>v0.1.4</code></p>
</div>
@attr="value"is captured into a global scope and stripped from the output$variableis resolved from the scope; unknown variables stay literal\$escapes to a literal$- Enable with
enableComponents: trueand setcomponentBaseDirto the folder containing the includes
customTransform
Hook every body line before it is parsed — great for custom syntax:
let opts = MarkdownOptions(
customTransform: proc(line: string): string =
if line == "@card": "<div class=\"card\">Custom card</div>"
else: line
)
Lazy-loading media
MarkdownOptions(lazyloadIframes: true)
MarkdownOptions(lazyloadVideos: true)
MarkdownOptions(lazyloadImages: true)
<iframe src="…">, <video src>, <audio src>, <source src> and <img src> (both raw HTML and ) are rewritten from src to data-src, ready for an IntersectionObserver.
YAML front matter
---
title: Marvdown
author: OpenPeeps
---
# Body
let header = md.getHeader() # YAMLObject (OrderedTable)
echo yamlmod.getStr(header["title"]) # "Marvdown"
AST / JSON
echo marvdown.getAst(readFile("sample.md"))
[{"kind":"mdkHeading","level":1,"textAnchor":null,
"children":{"items":[{"kind":"mdkText","text":"Hello","children":null,"line":1}]},
"line":1}]
Examples
Check out the examples/ folder for runnable code:
# run the comprehensive feature example
nim c -r examples/example.nim
# or use the CLI on a feature-rich sample document
nim c src/marvdown.nim
./marvdown html examples/sample.md --optAnchors
./marvdown json examples/sample.md
Benchmarks
Marvdown is super fast! It can parse large markdown files in milliseconds. Here is a quick benchmark over 100K lines of markdown text (~5.3 MB)
Benchmark 1: marvdown html bigdoc.md
Time (abs ≡): 188.1 ms [User: 166.9 ms, System: 19.8 ms]
Benchmark made with hyperfine
❤ Contributions & Support
- 🐛 Found a bug? Create a new Issue
- 👋 Wanna help? Fork it!
Credits
Original illustration made by 💙 Olha remixed with Sora.
🎩 License
Marv | MIT License.
Made by Humans from OpenPeeps.
Copyright © 2024 OpenPeeps & Contributors — All rights reserved.