use std::hash::{Hash, Hasher};
use pulldown_cmark::CowStr;
#[derive(Debug, Clone)]
pub enum Token<'s> {
Newline,
Text {
text: CowStr<'s>,
style: TokenStyle,
},
CodeBlock {
text: CowStr<'s>,
language: Option<CowStr<'s>>,
},
Link {
text: CowStr<'s>,
href: CowStr<'s>,
title: Option<CowStr<'s>>,
},
ListMarker {
marker: CowStr<'s>,
indent_level: usize,
},
Image {
alt: CowStr<'s>,
url: CowStr<'s>,
title: Option<CowStr<'s>>,
},
Table(TableData<'s>),
HorizontalRule,
BlockquoteStart,
BlockquoteEnd,
TaskListMarker {
checked: bool,
indent_level: usize,
},
FootnoteRef {
label: CowStr<'s>,
},
FootnoteDef {
label: CowStr<'s>,
},
}
#[derive(Debug, Clone, Default, Hash)]
pub struct TokenStyle {
pub bold: bool,
pub italic: bool,
pub strikethrough: bool,
pub inline_code: bool,
pub heading: Option<u8>,
}
impl TokenStyle {
pub fn is_plain(&self) -> bool {
!self.bold && !self.italic && !self.strikethrough && !self.inline_code && self.heading.is_none()
}
}
#[derive(Debug, Clone)]
pub struct TableData<'s> {
pub alignments: Vec<Alignment>,
pub headers: Vec<Vec<Token<'s>>>,
pub rows: Vec<Vec<Vec<Token<'s>>>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Alignment {
None,
Left,
Center,
Right,
}
impl<'s> Hash for TableData<'s> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.alignments.hash(state);
self.headers.hash(state);
self.rows.hash(state);
}
}
impl<'s> Hash for Token<'s> {
fn hash<H: Hasher>(&self, state: &mut H) {
std::mem::discriminant(self).hash(state);
match self {
Token::Newline | Token::HorizontalRule | Token::BlockquoteStart | Token::BlockquoteEnd => {}
Token::Text { text, style } => {
text.as_ref().hash(state);
style.hash(state);
}
Token::CodeBlock { text, language } => {
text.as_ref().hash(state);
language.as_ref().map(|l| l.as_ref()).hash(state);
}
Token::Link { text, href, title } => {
text.as_ref().hash(state);
href.as_ref().hash(state);
title.as_ref().map(|t| t.as_ref()).hash(state);
}
Token::ListMarker { marker, indent_level } => {
marker.as_ref().hash(state);
indent_level.hash(state);
}
Token::Image { alt, url, title } => {
alt.as_ref().hash(state);
url.as_ref().hash(state);
title.as_ref().map(|t| t.as_ref()).hash(state);
}
Token::Table(data) => data.hash(state),
Token::TaskListMarker { checked, indent_level } => {
checked.hash(state);
indent_level.hash(state);
}
Token::FootnoteRef { label } => label.as_ref().hash(state),
Token::FootnoteDef { label } => label.as_ref().hash(state),
}
}
}
pub struct Markdown<'s> {
pub s: &'s str,
pub tokens: Vec<Token<'s>>,
}
impl<'s> Token<'s> {
pub fn text(&self) -> &str {
match self {
Token::Newline => "",
Token::CodeBlock { text, .. } => text,
Token::Text { text, .. } => text,
Token::Link { text, .. } => text,
Token::ListMarker { marker, .. } => marker,
Token::Image { alt, .. } => alt,
Token::HorizontalRule => "",
Token::BlockquoteStart | Token::BlockquoteEnd => "",
Token::TaskListMarker { .. } => "",
Token::FootnoteRef { label } => label,
Token::FootnoteDef { label } => label,
Token::Table(_) => "",
}
}
pub fn href(&self) -> Option<&str> {
match self {
Token::Link { href, .. } => Some(href),
_ => None,
}
}
pub fn is_newline(&self) -> bool {
matches!(self, Token::Newline)
}
pub fn is_list_marker(&self) -> bool {
matches!(self, Token::ListMarker { .. })
}
}
#[cfg(test)]
mod size_tests {
use super::*;
#[test]
fn token_enum_size() {
let size = std::mem::size_of::<Token<'_>>();
eprintln!("Token size: {size} bytes");
assert!(size <= 88, "Token grew unexpectedly: {size} bytes");
}
#[test]
fn layout_result_size() {
let size = std::mem::size_of::<crate::layout::LayoutResult>();
eprintln!("LayoutResult size: {size} bytes");
assert!(size <= 400, "LayoutResult grew unexpectedly: {size} bytes");
}
}