use std::borrow::Cow;
use scraper::ElementRef;
use crate::context::Context;
use crate::converter::{Action, Rule};
use crate::dom;
#[derive(Debug, Clone, Copy)]
pub(super) struct InlineCode;
impl Rule for InlineCode {
fn tags(&self) -> &'static [&'static str] {
&["code", "kbd", "samp", "tt"]
}
fn apply(&self, content: &str, element: &ElementRef<'_>, _ctx: &mut Context<'_>) -> Action {
if dom::has_ancestor(element, "pre") {
return Action::Skip;
}
if content.is_empty() {
return Action::Skip;
}
let code = collapse_newlines(content);
let max_backtick_run = dom::max_consecutive_char(&code, '`');
let fence_len = max_backtick_run + 1;
let needs_pad = code.starts_with('`') || code.ends_with('`');
let mut text = String::with_capacity(code.len() + fence_len * 2 + 2);
text.extend(std::iter::repeat_n('`', fence_len));
if needs_pad {
text.push(' ');
}
text.push_str(&code);
if needs_pad {
text.push(' ');
}
text.extend(std::iter::repeat_n('`', fence_len));
Action::Replace(dom::add_space_if_necessary(element, text))
}
}
fn collapse_newlines(text: &str) -> Cow<'_, str> {
if !text.contains("\n\n") {
return Cow::Borrowed(text);
}
let mut result = String::with_capacity(text.len());
let mut prev_newline = false;
for c in text.chars() {
if c == '\n' {
if !prev_newline {
result.push('\n');
}
prev_newline = true;
} else {
result.push(c);
prev_newline = false;
}
}
Cow::Owned(result)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collapse_no_newlines() {
assert_eq!(collapse_newlines("hello world"), "hello world");
}
#[test]
fn collapse_single_newline_preserved() {
assert_eq!(collapse_newlines("a\nb"), "a\nb");
}
#[test]
fn collapse_multiple_newlines() {
assert_eq!(collapse_newlines("a\n\n\nb"), "a\nb");
}
#[test]
fn collapse_empty() {
assert_eq!(collapse_newlines(""), "");
}
#[test]
fn collapse_trailing_newlines() {
assert_eq!(collapse_newlines("a\n\n"), "a\n");
}
}