Packages
mdex
0.8.6
0.13.3
0.13.2
0.13.1
0.13.0
0.12.5
retired
0.12.4
0.12.3
0.12.2
0.12.1
0.12.0
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.18
0.1.17
0.1.16
0.1.15
0.1.14
0.1.13
0.1.12
0.1.11
0.1.10
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
Fast and extensible Markdown for Elixir
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
native/comrak_nif/src/autumnus_adapter.rs
use anyhow::{Context, Result};
use autumnus::formatter::html_inline::{
HighlightLines as HtmlInlineHighlightLines, HighlightLinesStyle,
};
use autumnus::formatter::html_linked::HighlightLines as HtmlLinkedHighlightLines;
use autumnus::formatter::{
Formatter, HtmlFormatter, HtmlInlineBuilder, HtmlLinkedBuilder, TerminalBuilder,
};
use autumnus::languages::Language;
use autumnus::FormatterOption;
use comrak::adapters::SyntaxHighlighterAdapter;
use std::collections::HashMap;
use std::io::{self, Write};
use std::sync::Mutex;
pub struct AutumnusAdapter<'a> {
formatter: FormatterOption<'a>,
stored_attrs: Mutex<Option<HashMap<String, String>>>,
stored_lang: Mutex<Option<Language>>,
}
impl Default for AutumnusAdapter<'_> {
fn default() -> Self {
Self {
formatter: FormatterOption::HtmlInline {
theme: None,
pre_class: None,
italic: false,
include_highlights: false,
highlight_lines: None,
header: None,
},
stored_attrs: Mutex::new(None),
stored_lang: Mutex::new(None),
}
}
}
impl<'a> AutumnusAdapter<'a> {
pub fn new(formatter: FormatterOption<'a>) -> Self {
Self {
formatter,
stored_attrs: Mutex::new(None),
stored_lang: Mutex::new(None),
}
}
fn parse_custom_attributes(info_string: &str) -> Option<HashMap<String, String>> {
let tokens = shlex::split(info_string)?;
if tokens.is_empty() {
return None;
}
let mut attributes = HashMap::with_capacity(tokens.len());
for token in tokens {
if let Some((key, value)) = token.split_once('=') {
attributes.insert(key.trim().to_string(), value.to_string());
} else {
// Handle standalone flags (e.g., "include_highlights" without "=true")
attributes.insert(token.trim().to_string(), "true".to_string());
}
}
if attributes.is_empty() {
None
} else {
Some(attributes)
}
}
fn parse_highlight_lines(&self, lines_str: &str) -> Vec<std::ops::RangeInclusive<usize>> {
lines_str
.split(',')
.filter_map(|part| {
let part = part.trim();
if let Some((start, end)) = part.split_once('-') {
match (start.trim().parse::<usize>(), end.trim().parse::<usize>()) {
(Ok(start_num), Ok(end_num)) => Some(start_num..=end_num),
_ => None,
}
} else {
part.parse::<usize>().ok().map(|n| n..=n)
}
})
.collect()
}
fn resolve_theme(
attrs: &HashMap<String, String>,
) -> Result<Option<&'static autumnus::themes::Theme>> {
match attrs.get("theme") {
Some(theme_name) => autumnus::themes::get(theme_name)
.map(Some)
.with_context(|| format!("Invalid theme: {theme_name}")),
None => Ok(None),
}
}
fn configure_html_inline_builder(
&self,
base_config: &FormatterOption<'a>,
lang: Option<Language>,
source: Option<&'a str>,
custom_attrs: Option<&'a HashMap<String, String>>,
) -> Result<HtmlInlineBuilder<'a>, io::Error> {
let mut builder = HtmlInlineBuilder::default();
let custom_theme = custom_attrs.and_then(|attrs| Self::resolve_theme(attrs).ok().flatten());
if let FormatterOption::HtmlInline {
theme,
pre_class,
italic,
include_highlights,
highlight_lines,
header,
} = base_config
{
builder.italic(*italic);
builder.include_highlights(*include_highlights);
if let Some(theme) = custom_theme.or(*theme) {
builder.theme(Some(theme));
}
if let Some(pre_class) = pre_class {
builder.pre_class(Some(pre_class));
}
if let Some(highlight_lines) = highlight_lines {
builder.highlight_lines(Some(highlight_lines.clone()));
}
if let Some(header) = header {
builder.header(Some(header.clone()));
}
}
if let Some(attrs) = custom_attrs {
if let Some(pre_class) = attrs.get("pre_class") {
builder.pre_class(Some(pre_class));
}
if let Some(include_highlights_str) = attrs.get("include_highlights") {
if include_highlights_str == "true" {
builder.include_highlights(true);
}
}
if let Some(highlight_lines_str) = attrs.get("highlight_lines") {
let lines = self.parse_highlight_lines(highlight_lines_str);
let style = match attrs.get("highlight_lines_style") {
Some(style) if style == "theme" => Some(HighlightLinesStyle::Theme),
Some(custom_style) => Some(HighlightLinesStyle::Style(custom_style.clone())),
None => Some(HighlightLinesStyle::Theme),
};
let class = attrs.get("highlight_lines_class").cloned();
let highlight_lines = HtmlInlineHighlightLines {
lines,
style,
class,
};
builder.highlight_lines(Some(highlight_lines));
}
}
if let Some(lang) = lang {
builder.lang(lang);
}
if let Some(source) = source {
builder.source(source);
}
Ok(builder)
}
fn configure_html_linked_builder(
&self,
base_config: &FormatterOption<'a>,
lang: Option<Language>,
source: Option<&'a str>,
custom_attrs: Option<&'a HashMap<String, String>>,
) -> Result<HtmlLinkedBuilder<'a>, io::Error> {
let mut builder = HtmlLinkedBuilder::default();
if let FormatterOption::HtmlLinked {
pre_class,
highlight_lines,
header,
} = base_config
{
if let Some(pre_class) = pre_class {
builder.pre_class(Some(pre_class));
}
if let Some(highlight_lines) = highlight_lines {
builder.highlight_lines(Some(highlight_lines.clone()));
}
if let Some(header) = header {
builder.header(Some(header.clone()));
}
}
if let Some(attrs) = custom_attrs {
if let Some(pre_class) = attrs.get("pre_class") {
builder.pre_class(Some(pre_class));
}
if let Some(highlight_lines_str) = attrs.get("highlight_lines") {
let lines = self.parse_highlight_lines(highlight_lines_str);
let mut highlight_lines = HtmlLinkedHighlightLines {
lines,
..Default::default()
};
if let Some(class_str) = attrs.get("highlight_lines_class") {
highlight_lines.class = class_str.clone();
}
builder.highlight_lines(Some(highlight_lines));
}
}
if let Some(lang) = lang {
builder.lang(lang);
}
if let Some(source) = source {
builder.source(source);
}
Ok(builder)
}
fn configure_terminal_builder(
&self,
base_config: &FormatterOption<'a>,
lang: Option<Language>,
source: Option<&'a str>,
custom_attrs: Option<&'a HashMap<String, String>>,
) -> Result<TerminalBuilder<'a>, io::Error> {
let mut builder = TerminalBuilder::default();
let custom_theme = custom_attrs.and_then(|attrs| Self::resolve_theme(attrs).ok().flatten());
if let FormatterOption::Terminal { theme } = base_config {
if let Some(theme) = custom_theme.or(*theme) {
builder.theme(Some(theme));
}
}
if let Some(lang) = lang {
builder.lang(lang);
}
if let Some(source) = source {
builder.source(source);
}
Ok(builder)
}
fn get_custom_attrs(attributes: &HashMap<String, String>) -> Option<HashMap<String, String>> {
attributes
.get("data-meta")
.and_then(|info| Self::parse_custom_attributes(info))
}
fn get_language(attributes: &HashMap<String, String>) -> Language {
if let Some(lang) = attributes.get("lang") {
Language::guess(lang, "")
} else if let Some(class) = attributes.get("class") {
let language = class.strip_prefix("language-").unwrap_or("plaintext");
Language::guess(language, "")
} else {
Language::guess("plaintext", "")
}
}
}
impl SyntaxHighlighterAdapter for AutumnusAdapter<'_> {
fn write_pre_tag(
&self,
output: &mut dyn Write,
attributes: HashMap<String, String>,
) -> io::Result<()> {
let custom_attrs = Self::get_custom_attrs(&attributes);
let lang = attributes.get("lang").map(|l| Language::guess(l, ""));
if let Some(attrs) = &custom_attrs {
*self.stored_attrs.lock().unwrap() = Some(attrs.clone());
}
if let Some(language) = lang {
*self.stored_lang.lock().unwrap() = Some(language);
}
match &self.formatter {
FormatterOption::HtmlInline { .. } => {
let builder = self.configure_html_inline_builder(
&self.formatter,
lang,
None,
custom_attrs.as_ref(),
)?;
let formatter = builder.build().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Build error: {e}"))
})?;
formatter.open_pre_tag(output)
}
FormatterOption::HtmlLinked { .. } => {
let builder = self.configure_html_linked_builder(
&self.formatter,
lang,
None,
custom_attrs.as_ref(),
)?;
let formatter = builder.build().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Build error: {e}"))
})?;
formatter.open_pre_tag(output)
}
FormatterOption::Terminal { .. } => Ok(()),
}
}
fn write_code_tag(
&self,
output: &mut dyn Write,
attributes: HashMap<String, String>,
) -> io::Result<()> {
let custom_attrs = Self::get_custom_attrs(&attributes);
let lang = Self::get_language(&attributes);
if let Some(attrs) = &custom_attrs {
*self.stored_attrs.lock().unwrap() = Some(attrs.clone());
}
if !attributes.is_empty() {
*self.stored_lang.lock().unwrap() = Some(lang);
}
let stored_attrs = self.stored_attrs.lock().unwrap().clone();
let stored_lang = *self.stored_lang.lock().unwrap();
let effective_lang = stored_lang.or(Some(lang));
match &self.formatter {
FormatterOption::HtmlInline { .. } => {
let builder = self.configure_html_inline_builder(
&self.formatter,
effective_lang,
None,
stored_attrs.as_ref(),
)?;
let formatter = builder.build().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Build error: {e}"))
})?;
formatter.open_code_tag(output)
}
FormatterOption::HtmlLinked { .. } => {
let builder = self.configure_html_linked_builder(
&self.formatter,
effective_lang,
None,
stored_attrs.as_ref(),
)?;
let formatter = builder.build().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Build error: {e}"))
})?;
formatter.open_code_tag(output)
}
FormatterOption::Terminal { .. } => Ok(()),
}
}
fn write_highlighted(
&self,
output: &mut dyn Write,
lang: Option<&str>,
source: &str,
) -> io::Result<()> {
let stored_attrs = self.stored_attrs.lock().unwrap().clone();
let stored_lang = *self.stored_lang.lock().unwrap();
let language = if let Some(stored_lang) = stored_lang {
stored_lang
} else if let Some(lang_str) = lang {
Language::guess(lang_str, source)
} else {
Language::guess("plaintext", source)
};
match &self.formatter {
FormatterOption::HtmlInline { .. } => {
let builder = self.configure_html_inline_builder(
&self.formatter,
Some(language),
Some(source),
stored_attrs.as_ref(),
)?;
let formatter = builder.build().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Build error: {e}"))
})?;
formatter.highlights(output)
}
FormatterOption::HtmlLinked { .. } => {
let builder = self.configure_html_linked_builder(
&self.formatter,
Some(language),
Some(source),
stored_attrs.as_ref(),
)?;
let formatter = builder.build().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Build error: {e}"))
})?;
formatter.highlights(output)
}
FormatterOption::Terminal { .. } => {
let builder = self.configure_terminal_builder(
&self.formatter,
Some(language),
Some(source),
stored_attrs.as_ref(),
)?;
let formatter = builder.build().map_err(|e| {
io::Error::new(io::ErrorKind::InvalidInput, format!("Build error: {e}"))
})?;
formatter.highlights(output)
}
}
}
}
#[cfg(test)]
mod tests {
#[cfg(test)]
use pretty_assertions::assert_str_eq;
use super::*;
use autumnus::formatter::html_inline::{
HighlightLines as HtmlInlineHighlightLines, HighlightLinesStyle,
};
use autumnus::formatter::html_linked::HighlightLines as HtmlLinkedHighlightLines;
use autumnus::formatter::HtmlElement;
use autumnus::{themes, FormatterOption};
use comrak::{format_html_with_plugins, parse_document, Arena, ComrakPlugins, Options};
fn run_test(markdown: &str, formatter: FormatterOption<'static>, options: Options) -> String {
let arena = Arena::new();
let root = parse_document(&arena, markdown, &options);
let adapter = AutumnusAdapter::new(formatter);
let plugins = ComrakPlugins {
render: comrak::RenderPlugins {
codefence_syntax_highlighter: Some(&adapter),
..Default::default()
},
};
let mut html = vec![];
format_html_with_plugins(root, &options, &mut html, &plugins)
.expect("Failed to format HTML with plugins");
String::from_utf8(html).expect("Invalid UTF-8 output")
}
fn default_options() -> Options<'static> {
Options::default()
}
#[test]
fn test_default_formatter_option() {
let markdown = r#"
```rust
fn main() {
let message = "Hello, world!";
}
```
"#;
let theme = themes::get("nord").expect("Theme not found");
let formatter = FormatterOption::default();
let output = run_test(markdown, FormatterOption::default(), Options::default());
let expected = r#"<pre class="athl"><code class="language-rust" translate="no" tabindex="0"><div class="line" data-line="1"><span >fn</span> <span >main</span><span >(</span><span >)</span> <span >{</span>
</div><div class="line" data-line="2"> <span >let</span> <span >message</span> <span >=</span> <span >"Hello, world!"</span><span >;</span>
</div><div class="line" data-line="3"><span >}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_html_inline_no_attrs() {
let markdown = r#"
```rust
fn main() {
let message = "Hello, world!";
}
```
"#;
let formatter = FormatterOption::HtmlInline {
theme: None,
pre_class: None,
italic: false,
include_highlights: false,
highlight_lines: None,
header: None,
};
let output = run_test(markdown, formatter, Options::default());
let expected = r#"<pre class="athl"><code class="language-rust" translate="no" tabindex="0"><div class="line" data-line="1"><span >fn</span> <span >main</span><span >(</span><span >)</span> <span >{</span>
</div><div class="line" data-line="2"> <span >let</span> <span >message</span> <span >=</span> <span >"Hello, world!"</span><span >;</span>
</div><div class="line" data-line="3"><span >}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_html_inline_all_attrs() {
let markdown = r#"
```rust
fn main() {
let a = 1;
let b = 2;
let sum = a + b;
}
```
"#;
let theme = themes::get("nord").expect("Theme not found");
let highlight_lines = HtmlInlineHighlightLines {
lines: vec![1..=1, 3..=4],
style: Some(HighlightLinesStyle::Theme),
class: None,
};
let header = HtmlElement {
open_tag: "<div class=\"code-header\">Rust Code</div>".to_string(),
close_tag: "</div>".to_string(),
};
let formatter = FormatterOption::HtmlInline {
theme: Some(theme),
pre_class: Some("custom-pre-class"),
italic: true,
include_highlights: true,
highlight_lines: Some(highlight_lines),
header: Some(header),
};
let output = run_test(markdown, formatter, Options::default());
let expected = r#"<pre class="athl custom-pre-class" style="color: #d8dee9; background-color: #2e3440;"><code class="language-rust" translate="no" tabindex="0"><div class="line" style="background-color: #3b4252;" data-line="1"><span data-highlight="keyword.function" style="color: #88c0d0; font-style: italic;">fn</span> <span data-highlight="function" style="color: #88c0d0; font-style: italic;">main</span><span data-highlight="punctuation.bracket" style="color: #88c0d0;">(</span><span data-highlight="punctuation.bracket" style="color: #88c0d0;">)</span> <span data-highlight="punctuation.bracket" style="color: #88c0d0;">{</span>
</div><div class="line" data-line="2"> <span data-highlight="keyword" style="color: #81a1c1; font-style: italic;">let</span> <span data-highlight="variable" style="color: #d8dee9; font-weight: bold;">a</span> <span data-highlight="operator" style="color: #81a1c1;">=</span> <span data-highlight="number" style="color: #b48ead;">1</span><span data-highlight="punctuation.delimiter" style="color: #88c0d0;">;</span>
</div><div class="line" style="background-color: #3b4252;" data-line="3"> <span data-highlight="keyword" style="color: #81a1c1; font-style: italic;">let</span> <span data-highlight="variable" style="color: #d8dee9; font-weight: bold;">b</span> <span data-highlight="operator" style="color: #81a1c1;">=</span> <span data-highlight="number" style="color: #b48ead;">2</span><span data-highlight="punctuation.delimiter" style="color: #88c0d0;">;</span>
</div><div class="line" style="background-color: #3b4252;" data-line="4"> <span data-highlight="keyword" style="color: #81a1c1; font-style: italic;">let</span> <span data-highlight="variable" style="color: #d8dee9; font-weight: bold;">sum</span> <span data-highlight="operator" style="color: #81a1c1;">=</span> <span data-highlight="variable" style="color: #d8dee9; font-weight: bold;">a</span> <span data-highlight="operator" style="color: #81a1c1;">+</span> <span data-highlight="variable" style="color: #d8dee9; font-weight: bold;">b</span><span data-highlight="punctuation.delimiter" style="color: #88c0d0;">;</span>
</div><div class="line" data-line="5"><span data-highlight="punctuation.bracket" style="color: #88c0d0;">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_html_inline_decorators() {
let markdown = r#"
```rust pre_class="my-custom-pre extra-class" theme=github_light include_highlights highlight_lines="1,3-5" highlight_lines_style="background-color: #ffffcc; border-left: 3px solid #ff0000" highlight_lines_class="custom-highlight-class"
fn main() {
let x = 1;
let y = 2;
let z = 3;
let message = "Hello, world!";
}
```
"#;
let nord_theme = themes::get("nord").expect("Nord theme not found");
let formatter = FormatterOption::HtmlInline {
theme: Some(nord_theme),
pre_class: Some("default-pre-class"),
italic: true,
include_highlights: false,
highlight_lines: Some(HtmlInlineHighlightLines {
lines: vec![2..=2],
style: Some(HighlightLinesStyle::Theme),
class: Some("default-highlight".to_string()),
}),
header: Some(HtmlElement {
open_tag: "<div class=\"header\">Header</div>".to_string(),
close_tag: "</div>".to_string(),
}),
};
let mut options = Options::default();
options.render.github_pre_lang = true;
options.render.full_info_string = true;
let output = run_test(markdown, formatter, options);
let expected = r#"<pre class="athl my-custom-pre extra-class" style="color: #1f2328; background-color: #ffffff;"><code class="language-rust" translate="no" tabindex="0"><div class="line custom-highlight-class" style="background-color: #ffffcc; border-left: 3px solid #ff0000" data-line="1"><span data-highlight="keyword.function" style="color: #cf222e;">fn</span> <span data-highlight="function" style="color: #6639ba;">main</span><span data-highlight="punctuation.bracket" style="color: #1f2328;">(</span><span data-highlight="punctuation.bracket" style="color: #1f2328;">)</span> <span data-highlight="punctuation.bracket" style="color: #1f2328;">{</span>
</div><div class="line" data-line="2"> <span data-highlight="keyword" style="color: #cf222e;">let</span> <span data-highlight="variable" style="color: #1f2328;">x</span> <span data-highlight="operator" style="color: #0550ae;">=</span> <span data-highlight="number" style="color: #0550ae;">1</span><span data-highlight="punctuation.delimiter" style="color: #1f2328;">;</span>
</div><div class="line custom-highlight-class" style="background-color: #ffffcc; border-left: 3px solid #ff0000" data-line="3"> <span data-highlight="keyword" style="color: #cf222e;">let</span> <span data-highlight="variable" style="color: #1f2328;">y</span> <span data-highlight="operator" style="color: #0550ae;">=</span> <span data-highlight="number" style="color: #0550ae;">2</span><span data-highlight="punctuation.delimiter" style="color: #1f2328;">;</span>
</div><div class="line custom-highlight-class" style="background-color: #ffffcc; border-left: 3px solid #ff0000" data-line="4"> <span data-highlight="keyword" style="color: #cf222e;">let</span> <span data-highlight="variable" style="color: #1f2328;">z</span> <span data-highlight="operator" style="color: #0550ae;">=</span> <span data-highlight="number" style="color: #0550ae;">3</span><span data-highlight="punctuation.delimiter" style="color: #1f2328;">;</span>
</div><div class="line custom-highlight-class" style="background-color: #ffffcc; border-left: 3px solid #ff0000" data-line="5"> <span data-highlight="keyword" style="color: #cf222e;">let</span> <span data-highlight="variable" style="color: #1f2328;">message</span> <span data-highlight="operator" style="color: #0550ae;">=</span> <span data-highlight="string" style="color: #0a3069;">"Hello, world!"</span><span data-highlight="punctuation.delimiter" style="color: #1f2328;">;</span>
</div><div class="line" data-line="6"><span data-highlight="punctuation.bracket" style="color: #1f2328;">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_html_inline_decorators_without_github_pre_lang() {
let markdown = r#"
```rust pre_class="custom-class" highlight_lines="1,3" theme=github_light
fn main() {
let x = 1;
let message = "Hello, world!";
}
```
"#;
let nord_theme = themes::get("nord").expect("Nord theme not found");
let formatter = FormatterOption::HtmlInline {
theme: Some(nord_theme),
pre_class: Some("default-class"),
italic: false,
include_highlights: true,
highlight_lines: None,
header: None,
};
let mut options = Options::default();
options.render.github_pre_lang = false;
options.render.full_info_string = true;
let output = run_test(markdown, formatter, options);
let expected = r#"<pre class="athl default-class" style="color: #d8dee9; background-color: #2e3440;"><code class="language-rust" translate="no" tabindex="0"><div class="line" style="background-color: #e7eaf0;" data-line="1"><span data-highlight="keyword.function" style="color: #cf222e;">fn</span> <span data-highlight="function" style="color: #6639ba;">main</span><span data-highlight="punctuation.bracket" style="color: #1f2328;">(</span><span data-highlight="punctuation.bracket" style="color: #1f2328;">)</span> <span data-highlight="punctuation.bracket" style="color: #1f2328;">{</span>
</div><div class="line" data-line="2"> <span data-highlight="keyword" style="color: #cf222e;">let</span> <span data-highlight="variable" style="color: #1f2328;">x</span> <span data-highlight="operator" style="color: #0550ae;">=</span> <span data-highlight="number" style="color: #0550ae;">1</span><span data-highlight="punctuation.delimiter" style="color: #1f2328;">;</span>
</div><div class="line" style="background-color: #e7eaf0;" data-line="3"> <span data-highlight="keyword" style="color: #cf222e;">let</span> <span data-highlight="variable" style="color: #1f2328;">message</span> <span data-highlight="operator" style="color: #0550ae;">=</span> <span data-highlight="string" style="color: #0a3069;">"Hello, world!"</span><span data-highlight="punctuation.delimiter" style="color: #1f2328;">;</span>
</div><div class="line" data-line="4"><span data-highlight="punctuation.bracket" style="color: #1f2328;">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_html_linked_no_attrs() {
let markdown = r#"
```rust
fn main() {
let message = "Hello, world!";
}
```
"#;
let formatter = FormatterOption::HtmlLinked {
pre_class: None,
highlight_lines: None,
header: None,
};
let output = run_test(markdown, formatter, Options::default());
let expected = r#"<pre class="athl"><code class="language-rust" translate="no" tabindex="0"><div class="line" data-line="1"><span class="keyword-function">fn</span> <span class="function">main</span><span class="punctuation-bracket">(</span><span class="punctuation-bracket">)</span> <span class="punctuation-bracket">{</span>
</div><div class="line" data-line="2"> <span class="keyword">let</span> <span class="variable">message</span> <span class="operator">=</span> <span class="string">"Hello, world!"</span><span class="punctuation-delimiter">;</span>
</div><div class="line" data-line="3"><span class="punctuation-bracket">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_html_linked_all_attrs() {
let markdown = r#"
```rust
fn main() {
let a = 1;
let b = 2;
let sum = a + b;
}
```
"#;
let highlight_lines = HtmlLinkedHighlightLines {
lines: vec![1..=1, 3..=4],
class: "highlighted-line".to_string(),
};
let header = HtmlElement {
open_tag: "<div class=\"linked-header\">Linked Code</div>".to_string(),
close_tag: "</div>".to_string(),
};
let formatter = FormatterOption::HtmlLinked {
pre_class: Some("custom-linked-class"),
highlight_lines: Some(highlight_lines),
header: Some(header),
};
let output = run_test(markdown, formatter, Options::default());
let expected = r#"<pre class="athl custom-linked-class"><code class="language-rust" translate="no" tabindex="0"><div class="line highlighted-line" data-line="1"><span class="keyword-function">fn</span> <span class="function">main</span><span class="punctuation-bracket">(</span><span class="punctuation-bracket">)</span> <span class="punctuation-bracket">{</span>
</div><div class="line" data-line="2"> <span class="keyword">let</span> <span class="variable">a</span> <span class="operator">=</span> <span class="number">1</span><span class="punctuation-delimiter">;</span>
</div><div class="line highlighted-line" data-line="3"> <span class="keyword">let</span> <span class="variable">b</span> <span class="operator">=</span> <span class="number">2</span><span class="punctuation-delimiter">;</span>
</div><div class="line highlighted-line" data-line="4"> <span class="keyword">let</span> <span class="variable">sum</span> <span class="operator">=</span> <span class="variable">a</span> <span class="operator">+</span> <span class="variable">b</span><span class="punctuation-delimiter">;</span>
</div><div class="line" data-line="5"><span class="punctuation-bracket">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_html_linked_decorators() {
let markdown = r#"
```rust pre_class="custom-linked-pre extra-linked" highlight_lines="1-2,4-5" highlight_lines_class="my-custom-highlight-line"
fn main() {
let x = 1;
let y = 2;
let z = 3;
let message = "Hello, world!";
}
```
"#;
let formatter = FormatterOption::HtmlLinked {
pre_class: Some("default-linked-pre"),
highlight_lines: Some(HtmlLinkedHighlightLines {
lines: vec![3..=3],
class: "default-highlight-line".to_string(),
}),
header: Some(HtmlElement {
open_tag: "<div class=\"linked-header\">Linked Header</div>".to_string(),
close_tag: "</div>".to_string(),
}),
};
let mut options = Options::default();
options.render.github_pre_lang = true;
options.render.full_info_string = true;
let output = run_test(markdown, formatter, options);
let expected = r#"<pre class="athl custom-linked-pre extra-linked"><code class="language-rust" translate="no" tabindex="0"><div class="line my-custom-highlight-line" data-line="1"><span class="keyword-function">fn</span> <span class="function">main</span><span class="punctuation-bracket">(</span><span class="punctuation-bracket">)</span> <span class="punctuation-bracket">{</span>
</div><div class="line my-custom-highlight-line" data-line="2"> <span class="keyword">let</span> <span class="variable">x</span> <span class="operator">=</span> <span class="number">1</span><span class="punctuation-delimiter">;</span>
</div><div class="line" data-line="3"> <span class="keyword">let</span> <span class="variable">y</span> <span class="operator">=</span> <span class="number">2</span><span class="punctuation-delimiter">;</span>
</div><div class="line my-custom-highlight-line" data-line="4"> <span class="keyword">let</span> <span class="variable">z</span> <span class="operator">=</span> <span class="number">3</span><span class="punctuation-delimiter">;</span>
</div><div class="line my-custom-highlight-line" data-line="5"> <span class="keyword">let</span> <span class="variable">message</span> <span class="operator">=</span> <span class="string">"Hello, world!"</span><span class="punctuation-delimiter">;</span>
</div><div class="line" data-line="6"><span class="punctuation-bracket">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_decorator_include_highlights_with_value() {
let markdown = r#"
```rust include_highlights=true
fn main() {
let message = "Hello, world!";
}
```
"#;
let theme = themes::get("nord").expect("Theme not found");
let formatter = FormatterOption::HtmlInline {
theme: Some(theme),
pre_class: None,
italic: false,
include_highlights: false,
highlight_lines: None,
header: None,
};
let mut options = Options::default();
options.render.github_pre_lang = true;
options.render.full_info_string = true;
let output = run_test(markdown, formatter, options);
let expected = r#"<pre class="athl" style="color: #d8dee9; background-color: #2e3440;"><code class="language-rust" translate="no" tabindex="0"><div class="line" data-line="1"><span data-highlight="keyword.function" style="color: #88c0d0;">fn</span> <span data-highlight="function" style="color: #88c0d0;">main</span><span data-highlight="punctuation.bracket" style="color: #88c0d0;">(</span><span data-highlight="punctuation.bracket" style="color: #88c0d0;">)</span> <span data-highlight="punctuation.bracket" style="color: #88c0d0;">{</span>
</div><div class="line" data-line="2"> <span data-highlight="keyword" style="color: #81a1c1;">let</span> <span data-highlight="variable" style="color: #d8dee9; font-weight: bold;">message</span> <span data-highlight="operator" style="color: #81a1c1;">=</span> <span data-highlight="string" style="color: #a3be8c;">"Hello, world!"</span><span data-highlight="punctuation.delimiter" style="color: #88c0d0;">;</span>
</div><div class="line" data-line="3"><span data-highlight="punctuation.bracket" style="color: #88c0d0;">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
#[test]
fn test_decorator_include_highlights_standalone() {
let markdown = r#"
```rust include_highlights
fn main() {
let message = "Hello, world!";
}
```
"#;
let theme = themes::get("nord").expect("Theme not found");
let formatter = FormatterOption::HtmlInline {
theme: Some(theme),
pre_class: None,
italic: false,
include_highlights: false,
highlight_lines: None,
header: None,
};
let mut options = Options::default();
options.render.github_pre_lang = true;
options.render.full_info_string = true;
let output = run_test(markdown, formatter, options);
let expected = r#"<pre class="athl" style="color: #d8dee9; background-color: #2e3440;"><code class="language-rust" translate="no" tabindex="0"><div class="line" data-line="1"><span data-highlight="keyword.function" style="color: #88c0d0;">fn</span> <span data-highlight="function" style="color: #88c0d0;">main</span><span data-highlight="punctuation.bracket" style="color: #88c0d0;">(</span><span data-highlight="punctuation.bracket" style="color: #88c0d0;">)</span> <span data-highlight="punctuation.bracket" style="color: #88c0d0;">{</span>
</div><div class="line" data-line="2"> <span data-highlight="keyword" style="color: #81a1c1;">let</span> <span data-highlight="variable" style="color: #d8dee9; font-weight: bold;">message</span> <span data-highlight="operator" style="color: #81a1c1;">=</span> <span data-highlight="string" style="color: #a3be8c;">"Hello, world!"</span><span data-highlight="punctuation.delimiter" style="color: #88c0d0;">;</span>
</div><div class="line" data-line="3"><span data-highlight="punctuation.bracket" style="color: #88c0d0;">}</span>
</div></code></pre>"#;
assert_str_eq!(output.trim(), expected.trim());
}
}