| Crates.io | ndg-commonmark |
| lib.rs | ndg-commonmark |
| version | 2.6.0 |
| created_at | 2025-09-15 08:27:23.344978+00 |
| updated_at | 2026-01-12 10:43:44.53101+00 |
| description | Flavored CommonMark processor for Nix-related projects, with support for CommonMark, GFM, and Nixpkgs extensions. |
| homepage | |
| repository | https://github.com/feel-co/ndg |
| max_upload_size | |
| id | 1839619 |
| size | 366,004 |
High-performance, extensible Nixpkgs-flavored CommonMark processor designed specifically for ndg in order to document Nix, NixOS and Nixpkgs adjacent projects.
Features AST-based processing, syntax highlighting, and generous documentation.
ls -la,/etc/nixos/configuration.nix,$XDG_CONFIG_HOMEnix.conf{=include=} blocks for modular documentation{man}
role and a manpage map.[]{#custom-id} syntax for precise linkinguse ndg_commonmark::{MarkdownProcessor, MarkdownOptions};
let processor = MarkdownProcessor::new(MarkdownOptions::default());
let result = processor.render("# Hello World\n\nThis is **bold** text.");
// The entire document
println!("HTML: {}", result.html);
// Fine-grained components
println!("Title: {:?}", result.title);
println!("Headers: {:?}", result.headers);
use ndg_commonmark::{MarkdownProcessor, MarkdownOptions};
let mut options = MarkdownOptions::default();
options.highlight_code = true;
options.highlight_theme = Some("one-dark".to_string());
let processor = MarkdownProcessor::new(options);
let markdown = r#"
# Code Example
```rust
fn main() {
println!("Hello, world!");
}
```
```nix
{ pkgs, ... }: {
environment.systemPackages = with pkgs; [ vim git ];
}
```
"#;
let result = processor.render(markdown);
use ndg_commonmark::MarkdownOptions;
let mut options = MarkdownOptions::default();
// Enable GitHub Flavored Markdown (GFM)
options.gfm = true;
// Enable Nixpkgs-specific extensions
options.nixpkgs = true;
// Configure syntax highlighting
options.highlight_code = true;
options.highlight_theme = Some("gruvbox-dark".to_string());
// Set manpage URL mappings
// NOTE: this is required for {man} role to function correctly
options.manpage_urls_path = Some("manpage-urls.json".to_string());
let processor = MarkdownProcessor::new(options);
Some of ndg-commonmark's features are gated behind feature flags. This allows compile-time control over what will be made available. Namely you can decide which parts of the flavored CommonMark will be supported.
default: Enables ndg-flavoredndg-flavored: All NDG-specific features (gfm + nixpkgs + syntastica)gfm: GitHub Flavored Markdown extensionsnixpkgs: NixOS/Nixpkgs documentation featuressyntastica: Modern tree-sitter based syntax highlighting (recommended)syntect: Legacy, less robust and more lightweight syntax highlighting[!NOTE] The
syntasticaandsyntectfeatures are mutually exclusive. Enable only one at a time.
[dependencies]
# Recommended: Modern syntax highlighting
ndg-commonmark = { version = "1.0", features = ["gfm", "nixpkgs", "syntastica"] }
# Alternative: Legacy syntax highlighting
ndg-commonmark = { version = "1.0", features = ["gfm", "nixpkgs", "syntect"] }
# No syntax highlighting
ndg-commonmark = { version = "1.0", features = ["gfm", "nixpkgs"] }
MarkdownProcessorThe main processor class:
impl MarkdownProcessor {
pub fn new(options: MarkdownOptions) -> Self;
pub fn render(&self, markdown: &str) -> MarkdownResult;
pub fn extract_headers(&self, content: &str) -> (Vec<Header>, Option<String>);
}
MarkdownOptionsConfiguration options:
pub struct MarkdownOptions {
pub gfm: bool, // GitHub Flavored Markdown
pub nixpkgs: bool, // NixOS documentation features
pub highlight_code: bool, // Syntax highlighting
pub manpage_urls_path: Option<String>, // Manpage URL mappings
pub highlight_theme: Option<String>, // Syntax highlighting theme
pub tab_style: TabStyle, // How to handle hard tabs in code blocks
}
TabStyleConfiguration for handling hard tabs in code blocks:
pub enum TabStyle {
None, // Leave hard tabs unchanged
Warn, // Issue a warning when hard tabs are detected
Normalize, // Automatically convert hard tabs to spaces (2 spaces per tab)
}
MarkdownResultProcessing result:
pub struct MarkdownResult {
pub html: String, // Generated HTML
pub headers: Vec<Header>, // Extracted headers
pub title: Option<String>, // Document title (first h1)
}
For advanced syntax highlighting control:
use ndg_commonmark::syntax::{create_default_manager, SyntaxManager};
let manager = create_default_manager()?;
// Highlight code directly
let html = manager.highlight_code("fn main() {}", "rust", Some("one-dark"))?;
// Language detection
if let Some(lang) = manager.highlighter().language_from_filename("script.py") {
println!("Detected: {}", lang);
}
// Available languages and themes
println!("Languages: {:?}", manager.highlighter().supported_languages());
println!("Themes: {:?}", manager.highlighter().available_themes());
use ndg_commonmark::{MarkdownProcessor, MarkdownOptions};
let processor = MarkdownProcessor::new(MarkdownOptions::default());
let markdown = r#"
# Documentation Example
This document demonstrates **ndg-commonmark** features.
## Code Highlighting
```rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert("key", "value");
println!("{:?}", map);
}
```
## NixOS Configuration
```nix
{ config, pkgs, ... }: {
services.nginx = {
enable = true;
virtualHosts."example.com" = {
enableACME = true;
forceSSL = true;
root = "/var/www/example.com";
};
};
}
```
use ndg_commonmark::{MarkdownProcessor, MarkdownOptions};
use std::collections::HashMap;
let mut options = MarkdownOptions::default();
options.gfm = true;
options.nixpkgs = true;
options.highlight_code = true;
options.highlight_theme = Some("gruvbox-dark".to_string());
options.manpage_urls_path = Some("manpages.json".to_string());
let processor = MarkdownProcessor::new(options);
// Process multiple files
let files = ["intro.md", "configuration.md", "examples.md"];
for file in files {
let content = std::fs::read_to_string(file)?;
let result = processor.render(&content);
println!("Title: {:?}", result.title);
println!("Headers: {:?}", result.headers);
// Write HTML output
let output_file = file.replace(".md", ".html");
std::fs::write(output_file, result.html)?;
}
use ndg_commonmark::{MarkdownProcessor, MarkdownOptions};
use std::path::Path;
use walkdir::WalkDir;
fn build_site(input_dir: &Path, output_dir: &Path) -> Result<(), Box<dyn std::error::Error>> {
let mut options = MarkdownOptions::default();
options.gfm = true;
options.nixpkgs = true;
options.highlight_code = true;
let processor = MarkdownProcessor::new(options);
for entry in WalkDir::new(input_dir) {
let entry = entry?;
if entry.path().extension() == Some("md".as_ref()) {
let content = std::fs::read_to_string(entry.path())?;
let result = processor.render(&content);
let output_path = output_dir.join(
entry.path()
.strip_prefix(input_dir)?
.with_extension("html")
);
std::fs::create_dir_all(output_path.parent().unwrap())?;
std::fs::write(output_path, result.html)?;
}
}
Ok(())
}
ndg-commonmark is inspired and powered by various projects. The first and
perhaps the most important one is nixos-render-docs that powers the NixOS
documentation. It filled me with so much spite that I felt compelled to write
this library.
Next, and the second most important is the comrak library that powers most of the Markdown parsing features. ndg-commonmark is enriched by the robustness of this library, so a big thanks to author and contributors.
Last but not least, a big thank you to all the crates that we use under the hood. The Rust ecosystem is great, and this allows for an excellent opportunity to build what I enjoy.
This project is made available under Mozilla Public License (MPL) version 2.0. See LICENSE for more details on the exact conditions. An online copy is provided here.
Nix syntax highlighting is available only if using Syntastica as the highlighting backend. Syntect requires an external parser collection, which we no longer support. ↩