5 releases (breaking)

0.5.0 Dec 6, 2024
0.4.0 Dec 1, 2024
0.3.0 Nov 30, 2024
0.2.0 Nov 29, 2024
0.1.0 Nov 29, 2024

#852 in Text processing

Download history 300/week @ 2024-11-28 157/week @ 2024-12-05 24/week @ 2024-12-12 1/week @ 2024-12-19

194 downloads per month
Used in pulldown-html-ext-cli

MIT license

79KB
2K SLoC

pulldown-html-ext

A configurable Markdown to HTML renderer that extends pulldown-cmark. This library provides a flexible HTML rendering system with extensive configuration options, custom styling support, and attribute handling capabilities.

Features

  • Configurable HTML rendering with extensive options
  • Custom attribute mapping for HTML elements
  • Support for heading IDs and custom classes
  • Customizable code block rendering
  • External link handling with nofollow and target="_blank" options
  • Table support with alignment controls
  • Footnote rendering
  • Task list support
  • XHTML-style output option
  • Pretty printing support
  • Syntect-based syntax highlighting for code blocks

Installation

Add this to your Cargo.toml:

[dependencies]
pulldown-html-ext = "0.1.0"

Quick Start

Here's a simple example of converting Markdown to HTML using default settings:

use pulldown_html_ext::{HtmlConfig, push_html};
use pulldown_cmark::Parser;

let config = HtmlConfig::default();
let markdown = "# Hello\nThis is *markdown*";
let mut output = String::new();
let parser = Parser::new(markdown);
let html = push_html(&mut output, parser, &config);

Configuration

The library provides extensive configuration options through the HtmlConfig struct:

let mut config = HtmlConfig::default();

// Configure HTML options
config.html.escape_html = true;
config.html.break_on_newline = true;
config.html.xhtml_style = false;
config.html.pretty_print = true;

// Configure heading options
config.elements.headings.add_ids = true;
config.elements.headings.id_prefix = "heading-".to_string();

// Configure link options
config.elements.links.nofollow_external = true;
config.elements.links.open_external_blank = true;

// Configure code block options
config.elements.code_blocks.default_language = Some("rust".to_string());
config.elements.code_blocks.line_numbers = false;

// Configure syntax highlighting ( If feature is enabled )
config.syntect = Some(SyntectConfigStyle {
    theme: "base16-ocean.dark".to_string(),
    class_style: ClassStyle::Spaced,
    inject_css: true,
});

Custom Attribute Mapping

You can add custom attributes to HTML elements:

use std::collections::HashMap;

let mut config = HtmlConfig::default();
let mut attrs = HashMap::new();
attrs.insert("class".to_string(), "custom-paragraph".to_string());
config.attributes.element_attributes.insert("p".to_string(), attrs);

Custom Writers

Create custom HTML writers by implementing the HtmlWriter trait. This allows you to customize how specific Markdown elements are rendered to HTML:

use pulldown_html_ext::{HtmlConfig, HtmlWriter, HtmlState, HtmlRenderer};
use pulldown_cmark_escape::{StrWrite, FmtWriter};
use pulldown_cmark::{HeadingLevel, Parser};

struct CustomWriter<W: StrWrite> {
    writer: W,
    config: HtmlConfig,
    state: HtmlState,
}

impl<W: StrWrite> CustomWriter<W> {
    fn new(writer: W, config: HtmlConfig) -> Self {
        Self {
            writer,
            config,
            state: HtmlState::new(),
        }
    }
}

impl<W: StrWrite> HtmlWriter<W> for CustomWriter<W> {
    fn get_writer(&mut self) -> &mut W {
        &mut self.writer
    }

    fn get_config(&self) -> &HtmlConfig {
        &self.config
    }

    fn get_state(&mut self) -> &mut HtmlState {
        &mut self.state
    }

    // Override heading rendering to add emoji markers and custom classes
    fn start_heading(&mut self, level: HeadingLevel, _id: Option<&str>, classes: Vec<&str>) {
        let level_num = self.heading_level_to_u8(level);
        let emoji = match level_num {
            1 => "🎯",
            2 => "💫",
            _ => "",
        };
        
        self.write_str(&format!("<h{} class=\"fancy-heading level-{}", level_num, level_num));
        if !classes.is_empty() {
            self.write_str(" ");
            self.write_str(&classes.join(" "));
        }
        self.write_str("\">");
        self.write_str(emoji);
        self.write_str(" ");
    }

    fn end_heading(&mut self, level: HeadingLevel) {
        let level_num = self.heading_level_to_u8(level);
        self.write_str(&format!(" </h{}>", level_num));
    }
}

// Usage example:
fn main() {
    let mut output = String::new();
    let writer = CustomWriter::new(FmtWriter(&mut output), HtmlConfig::default());
    let mut renderer = HtmlRenderer::new(writer);
    
    let markdown = "# Main Title\n## Subtitle\n### Section";
    let parser = Parser::new(markdown);
    renderer.run(parser);
    
    println!("{}", output);
    // Output:
    // <h1 class="fancy-heading level-1">🎯 Main Title </h1>
    // <h2 class="fancy-heading level-2">💫 Subtitle </h2>
    // <h3 class="fancy-heading level-3">✨ Section </h3>
}

Syntect-based Syntax Highlighting

The library provides an optional feature to enable syntax highlighting for code blocks using the Syntect library. To use this, you can enable the syntect feature in your Cargo.toml:

[dependencies]
pulldown-html-ext = { version = "0.1.0", features = ["syntect"] }

Then, you can configure the syntax highlighting options in your HtmlConfig:

let mut config = HtmlConfig::default();
config.syntect = Some(SyntectConfigStyle {
    theme: "base16-ocean.dark".to_string(),
    class_style: ClassStyle::Spaced,
    inject_css: true,
});

This will add syntax highlighting to your code blocks using the specified theme and class style.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

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

Dependencies

~4–11MB
~119K SLoC