max / docengine
git clone https://makenot.work/git/max/docengine.git
git clone git@ssh.makenot.work:max/docengine.git
| Name | Size | |
|---|---|---|
| docs/ | ||
| src/ | ||
| .gitignore | 19 B | |
| Cargo.lock | 30.1 KB | |
| Cargo.toml | 752 B | |
| README.md | 5.7 KB |
README
DocEngine
Configurable markdown-to-HTML rendering library with sanitization presets. Built on pulldown-cmark (GFM) and ammonia.
Used by MNW (site docs, blog posts, user-generated content), Multithreaded (forum posts), and the desktop apps (descriptions, notes).
Presets
Four rendering presets, each with different security/feature tradeoffs:
| Preset | Use case | Tables | Images | Raw HTML | Dangerous scheme filter | Sanitization |
|---|---|---|---|---|---|---|
| Permissive | Docs, blog posts (trusted) | Y | Y | Y | N | Default ammonia |
| Standard | App text fields (descriptions) | Y | N | Y | N | Default ammonia |
| Strict | User-generated content (forums) | N | N | N | Y | nofollow on links |
| Sanitize-only | External HTML (RSS feeds) | – | – | – | – | Default ammonia, no markdown parsing |
use docengine::{render_permissive, render_standard, render_strict, sanitize_html};
// Convenience functions
let html = render_permissive("# Hello\n\n**Bold** text");
let html = render_standard("A description with [link](https://example.com)");
let html = render_strict("User post with @mentions and `code`");
let html = sanitize_html("<p>Pre-rendered</p><script>stripped</script>");
// Builder pattern for custom configurations
use docengine::{Renderer, SanitizePreset};
let html = Renderer::permissive()
.with_strip_images(true) // override: strip images even in permissive
.with_footnotes(false)
.render("# Custom config");
// Render with metadata (word count, reading time)
let result = Renderer::standard().render_with_meta("Some article text...");
println!("{} words, ~{} min read", result.word_count, result.reading_time_minutes);
Feature Flags
All optional features are off by default. Enable what you need:
| Flag | Dependencies | Provides |
|---|---|---|
doc-loader | regex | DocLoader – load a directory of .md files into an in-memory page store |
directives | regex-lite | post_process_directives – [!NOTE]/[!TIP]/[!TABS] blockquote alerts and code tabs |
frontmatter | toml | parse_frontmatter – extract TOML frontmatter delimited by +++ |
mentions | regex-lite | extract_mentions, resolve_mentions – @username parsing and linking |
quotes | regex-lite, uuid | post_process_quotes – replace [quote:POST_ID:HASH] markers with author attribution |
media-urls | regex-lite | rewrite_media_paths, img_to_video – CDN path rewriting and video tag conversion |
full | all of the above | Enable everything |
# In Cargo.toml
docengine = { path = "../Shared/docengine" } # Core only
docengine = { path = "../Shared/docengine", features = ["full"] } # Everything
Core API
Types
Renderer– configurable markdown renderer with builder patternRenderResult– rendered HTML plusword_countandreading_time_minutesSanitizePreset–Permissive,Standard,Strict,MinimalTocEntry– heading level, text, and anchor for table of contents
Functions
| Function | Description |
|---|---|
render_permissive(md) | Render with full GFM features |
render_standard(md) | Render without images |
render_strict(md) | Render with all restrictions (UGC-safe) |
sanitize_html(html) | Clean pre-rendered HTML without markdown parsing |
word_count(text) | Count words in raw text |
reading_time_minutes(wc) | Estimate reading time (200 wpm) |
extract_title(md) | Pull the first # Heading from markdown |
strip_first_heading(md) | Remove the first # Heading (for template-rendered titles) |
extract_toc(md) | Build a Vec<TocEntry> from all headings |
render_toc_html(entries) | Render TOC entries as a <nav class="toc"> HTML list |
Feature-gated
| Function / Type | Feature | Description |
|---|---|---|
DocLoader::load(path, config) | doc-loader | Load .md files from disk, render to HTML, build searchable index |
DocPage, DocIndexEntry | doc-loader | Page and index entry types |
post_process_directives(html) | directives | Convert [!NOTE]/[!TIP]/etc. blockquotes to alert divs, [!TABS] to tabbed code blocks |
parse_frontmatter(input) | frontmatter | Parse +++-delimited TOML frontmatter |
Frontmatter | frontmatter | Struct with title, date, tags, section, draft, extra |
extract_mentions(md) | mentions | Find unique @username mentions (skips code blocks) |
resolve_mentions(md, valid, template) | mentions | Replace @user with [@user](/path/to/user) for known usernames |
post_process_quotes(html, authors) | quotes | Replace [quote:UUID:HASH] with clickable attribution |
rewrite_media_paths(md, base, user) | media-urls | Rewrite relative image paths to absolute CDN URLs |
img_to_video(html) | media-urls | Convert <img> tags pointing to video files into <video> elements |
Consumers
| Project | Features used | Preset |
|---|---|---|
| MNW | doc-loader, directives, frontmatter, media-urls | Permissive (docs/blog), Standard (descriptions) |
| Multithreaded | mentions, quotes | Strict (forum posts) |
| GoingsOn | core only | Standard (notes, descriptions) |
| Balanced Breakfast | core only | Sanitize-only (RSS feed content) |
Security
All presets sanitize output through ammonia. The strict preset additionally:
- Strips all raw HTML and images at the parser level (before ammonia)
- Replaces
javascript:,data:,vbscript:URLs with# - Adds
rel="noopener noreferrer nofollow"to all links
Zero unsafe code.
License
PolyForm Noncommercial 1.0.0