#markdown #common-mark #serialization #writer

cmark-writer

A CommonMark writer implementation in Rust for serializing AST nodes to CommonMark format

19 releases (6 breaking)

new 0.7.5 May 22, 2025
0.7.4 May 22, 2025
0.6.3 Apr 30, 2025
0.5.0 Apr 27, 2025
0.1.5 Apr 25, 2025

#165 in Text processing

Download history 71/week @ 2025-04-19 614/week @ 2025-04-26 447/week @ 2025-05-03 377/week @ 2025-05-10 819/week @ 2025-05-17

2,289 downloads per month

MIT license

135KB
2.5K SLoC

cmark-writer

CI Status Crates.io License: MIT Downloads Codecov

A CommonMark writer implementation in Rust.

Basic Usage

use cmark_writer::ast::{Node, ListItem};
use cmark_writer::writer::CommonMarkWriter;

// Create a document
let document = Node::Document(vec![
    Node::heading(1, vec![Node::Text("Hello CommonMark".to_string())]),
    Node::Paragraph(vec![
        Node::Text("This is a simple ".to_string()),
        Node::Strong(vec![Node::Text("example".to_string())]),
        Node::Text(".".to_string()),
    ]),
]);

// Render to CommonMark
let mut writer = CommonMarkWriter::new();
writer.write(&document).expect("Failed to write document");
let markdown = writer.into_string();

println!("{}", markdown);

Custom Options

use cmark_writer::options::WriterOptionsBuilder;
use cmark_writer::writer::CommonMarkWriter;

// Use builder pattern for custom options
let options = WriterOptionsBuilder::new()
    .strict(true)
    .hard_break_spaces(false)
    .indent_spaces(2)
    .build();

let mut writer = CommonMarkWriter::with_options(options);

Table Support

use cmark_writer::ast::{Node, tables::TableBuilder};

// Create tables with the builder pattern
let table = TableBuilder::new()
    .headers(vec![
        Node::Text("Name".to_string()), 
        Node::Text("Age".to_string())
    ])
    .add_row(vec![
        Node::Text("John".to_string()),
        Node::Text("30".to_string()),
    ])
    .add_row(vec![
        Node::Text("Alice".to_string()),
        Node::Text("25".to_string()),
    ])
    .build();

GitHub Flavored Markdown (GFM)

Enable GFM features by adding to your Cargo.toml:

[dependencies]
cmark-writer = { version = "0.7.0", features = ["gfm"] }

GFM Support:

  • Tables with column alignment
  • Strikethrough text
  • Task lists
  • Extended autolinks
  • HTML element filtering

HTML Writing

The library provides dedicated HTML writing capabilities through the HtmlWriter class:

use cmark_writer::{HtmlWriter, HtmlWriterOptions, Node};

// Create HTML writer with custom options
let options = HtmlWriterOptions {
    strict: true,
    code_block_language_class_prefix: Some("language-".to_string()),
    #[cfg(feature = "gfm")]
    enable_gfm: true,
    #[cfg(feature = "gfm")]
    gfm_disallowed_html_tags: vec!["script".to_string()],
};

let mut writer = HtmlWriter::with_options(options);

// Write some nodes
let paragraph = Node::Paragraph(vec![Node::Text("Hello HTML".to_string())]);
writer.write_node(&paragraph).unwrap();

// Get resulting HTML
let html = writer.into_string();
assert_eq!(html, "<p>Hello HTML</p>\n");

Custom Nodes

use cmark_writer::{CommonMarkWriter, HtmlWriter, HtmlWriteResult, Node};
use cmark_writer::WriteResult;
use cmark_writer::custom_node;

#[derive(Debug, Clone, PartialEq)]
#[custom_node(block=false, html_impl=true)]
struct HighlightNode {
    content: String,
    color: String,
}

impl HighlightNode {
    // Implementation for CommonMark output
    fn write_custom(&self, writer: &mut CommonMarkWriter) -> WriteResult<()> {
        writer.write_str("<span style=\"background-color: ")?;
        writer.write_str(&self.color)?;
        writer.write_str("\">")?;
        writer.write_str(&self.content)?;
        writer.write_str("</span>")?;
        Ok(())
    }
    
    // Optional HTML-specific implementation
    fn write_html_custom(&self, writer: &mut HtmlWriter) -> HtmlWriteResult<()> {
        writer.start_tag("span")?;
        writer.attribute("style", &format!("background-color: {}", self.color))?;
        writer.finish_tag()?;
        writer.text(&self.content)?;
        writer.end_tag("span")?;
        Ok(())
    }
}

Development

# Build
cargo build

# Run tests
cargo test

License

This project is licensed under the MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Feel free to submit a Pull Request.

Dependencies

~0.5–1MB
~23K SLoC