#![warn(
clippy::alloc_instead_of_core,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core
)]
#![cfg_attr(not(feature = "simd"), forbid(unsafe_code))]
#![warn(missing_debug_implementations)]
#![cfg_attr(not(feature = "std"), no_std)]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(not(feature = "std"))]
compile_error!("This crate requires the \"std\" feature.");
use alloc::vec::Vec;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "html")]
pub mod html;
pub mod utils;
pub mod arena_build;
mod entities;
mod firstpass;
mod linklabel;
mod mdx;
mod parse;
mod puncttable;
mod scanners;
mod strings;
mod tree;
use core::fmt::Display;
pub use crate::{
arena_build::{parse, DEFAULT_OPTIONS, MDX_OPTIONS},
parse::{
BrokenLink, BrokenLinkCallback, DefaultParserCallbacks, OffsetIter, Parser,
ParserCallbacks, RefDefs,
},
strings::{CowStr, InlineStr},
utils::*,
};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CodeBlockKind<'a> {
Indented,
#[cfg_attr(feature = "serde", serde(borrow))]
Fenced(CowStr<'a>),
}
impl<'a> CodeBlockKind<'a> {
pub fn is_indented(&self) -> bool {
matches!(*self, CodeBlockKind::Indented)
}
pub fn is_fenced(&self) -> bool {
matches!(*self, CodeBlockKind::Fenced(_))
}
pub fn into_static(self) -> CodeBlockKind<'static> {
match self {
CodeBlockKind::Indented => CodeBlockKind::Indented,
CodeBlockKind::Fenced(s) => CodeBlockKind::Fenced(s.into_static()),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum BlockQuoteKind {
Note,
Tip,
Important,
Warning,
Caution,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ContainerKind {
Default,
Spoiler,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MetadataBlockKind {
YamlStyle,
PlusesStyle,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Tag<'a> {
Paragraph,
Heading {
level: HeadingLevel,
id: Option<CowStr<'a>>,
classes: Vec<CowStr<'a>>,
attrs: Vec<(CowStr<'a>, Option<CowStr<'a>>)>,
},
BlockQuote(Option<BlockQuoteKind>),
CodeBlock(CodeBlockKind<'a>),
ContainerBlock(ContainerKind, CowStr<'a>),
HtmlBlock,
List(Option<u64>, bool),
Item,
#[cfg_attr(feature = "serde", serde(borrow))]
FootnoteDefinition(CowStr<'a>),
DefinitionList,
DefinitionListTitle,
DefinitionListDefinition,
Table(Vec<Alignment>),
TableHead,
TableRow,
TableCell,
Emphasis,
Strong,
Strikethrough,
Superscript,
Subscript,
Link {
link_type: LinkType,
dest_url: CowStr<'a>,
title: CowStr<'a>,
id: CowStr<'a>,
},
Image {
link_type: LinkType,
dest_url: CowStr<'a>,
title: CowStr<'a>,
id: CowStr<'a>,
},
MetadataBlock(MetadataBlockKind),
#[cfg_attr(feature = "serde", serde(borrow))]
MdxJsxFlowElement(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
MdxJsxTextElement(CowStr<'a>),
}
impl<'a> Tag<'a> {
pub fn to_end(&self) -> TagEnd {
match self {
Tag::Paragraph => TagEnd::Paragraph,
Tag::Heading { level, .. } => TagEnd::Heading(*level),
Tag::BlockQuote(kind) => TagEnd::BlockQuote(*kind),
Tag::CodeBlock(_) => TagEnd::CodeBlock,
Tag::ContainerBlock(kind, _) => TagEnd::ContainerBlock(*kind),
Tag::HtmlBlock => TagEnd::HtmlBlock,
Tag::List(number, _) => TagEnd::List(number.is_some()),
Tag::Item => TagEnd::Item,
Tag::FootnoteDefinition(_) => TagEnd::FootnoteDefinition,
Tag::Table(_) => TagEnd::Table,
Tag::TableHead => TagEnd::TableHead,
Tag::TableRow => TagEnd::TableRow,
Tag::TableCell => TagEnd::TableCell,
Tag::Subscript => TagEnd::Subscript,
Tag::Superscript => TagEnd::Superscript,
Tag::Emphasis => TagEnd::Emphasis,
Tag::Strong => TagEnd::Strong,
Tag::Strikethrough => TagEnd::Strikethrough,
Tag::Link { .. } => TagEnd::Link,
Tag::Image { .. } => TagEnd::Image,
Tag::MetadataBlock(kind) => TagEnd::MetadataBlock(*kind),
Tag::DefinitionList => TagEnd::DefinitionList,
Tag::DefinitionListTitle => TagEnd::DefinitionListTitle,
Tag::DefinitionListDefinition => TagEnd::DefinitionListDefinition,
Tag::MdxJsxFlowElement(_) => TagEnd::MdxJsxFlowElement,
Tag::MdxJsxTextElement(_) => TagEnd::MdxJsxTextElement,
}
}
pub fn into_static(self) -> Tag<'static> {
match self {
Tag::Paragraph => Tag::Paragraph,
Tag::Heading {
level,
id,
classes,
attrs,
} => Tag::Heading {
level,
id: id.map(|s| s.into_static()),
classes: classes.into_iter().map(|s| s.into_static()).collect(),
attrs: attrs
.into_iter()
.map(|(k, v)| (k.into_static(), v.map(|s| s.into_static())))
.collect(),
},
Tag::BlockQuote(k) => Tag::BlockQuote(k),
Tag::CodeBlock(kb) => Tag::CodeBlock(kb.into_static()),
Tag::ContainerBlock(k, s) => Tag::ContainerBlock(k, s.into_static()),
Tag::HtmlBlock => Tag::HtmlBlock,
Tag::List(v, t) => Tag::List(v, t),
Tag::Item => Tag::Item,
Tag::FootnoteDefinition(a) => Tag::FootnoteDefinition(a.into_static()),
Tag::Table(v) => Tag::Table(v),
Tag::TableHead => Tag::TableHead,
Tag::TableRow => Tag::TableRow,
Tag::TableCell => Tag::TableCell,
Tag::Emphasis => Tag::Emphasis,
Tag::Strong => Tag::Strong,
Tag::Strikethrough => Tag::Strikethrough,
Tag::Superscript => Tag::Superscript,
Tag::Subscript => Tag::Subscript,
Tag::Link {
link_type,
dest_url,
title,
id,
} => Tag::Link {
link_type,
dest_url: dest_url.into_static(),
title: title.into_static(),
id: id.into_static(),
},
Tag::Image {
link_type,
dest_url,
title,
id,
} => Tag::Image {
link_type,
dest_url: dest_url.into_static(),
title: title.into_static(),
id: id.into_static(),
},
Tag::MetadataBlock(v) => Tag::MetadataBlock(v),
Tag::DefinitionList => Tag::DefinitionList,
Tag::DefinitionListTitle => Tag::DefinitionListTitle,
Tag::DefinitionListDefinition => Tag::DefinitionListDefinition,
Tag::MdxJsxFlowElement(s) => Tag::MdxJsxFlowElement(s.into_static()),
Tag::MdxJsxTextElement(s) => Tag::MdxJsxTextElement(s.into_static()),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TagEnd {
Paragraph,
Heading(HeadingLevel),
BlockQuote(Option<BlockQuoteKind>),
CodeBlock,
ContainerBlock(ContainerKind),
HtmlBlock,
List(bool),
Item,
FootnoteDefinition,
DefinitionList,
DefinitionListTitle,
DefinitionListDefinition,
Table,
TableHead,
TableRow,
TableCell,
Emphasis,
Strong,
Strikethrough,
Superscript,
Subscript,
Link,
Image,
MetadataBlock(MetadataBlockKind),
MdxJsxFlowElement,
MdxJsxTextElement,
}
#[cfg(target_pointer_width = "64")]
const _STATIC_ASSERT_TAG_END_SIZE: [(); 2] = [(); core::mem::size_of::<TagEnd>()];
impl<'a> From<Tag<'a>> for TagEnd {
fn from(value: Tag) -> Self {
value.to_end()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum HeadingLevel {
H1 = 1,
H2,
H3,
H4,
H5,
H6,
}
impl Display for HeadingLevel {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::H1 => write!(f, "h1"),
Self::H2 => write!(f, "h2"),
Self::H3 => write!(f, "h3"),
Self::H4 => write!(f, "h4"),
Self::H5 => write!(f, "h5"),
Self::H6 => write!(f, "h6"),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct InvalidHeadingLevel(usize);
impl TryFrom<usize> for HeadingLevel {
type Error = InvalidHeadingLevel;
fn try_from(value: usize) -> Result<Self, Self::Error> {
match value {
1 => Ok(Self::H1),
2 => Ok(Self::H2),
3 => Ok(Self::H3),
4 => Ok(Self::H4),
5 => Ok(Self::H5),
6 => Ok(Self::H6),
_ => Err(InvalidHeadingLevel(value)),
}
}
}
#[derive(Clone, Debug, PartialEq, Copy)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LinkType {
Inline,
Reference,
ReferenceUnknown,
Collapsed,
CollapsedUnknown,
Shortcut,
ShortcutUnknown,
Autolink,
Email,
WikiLink {
has_pothole: bool,
},
}
impl LinkType {
fn to_unknown(self) -> Self {
match self {
LinkType::Reference => LinkType::ReferenceUnknown,
LinkType::Collapsed => LinkType::CollapsedUnknown,
LinkType::Shortcut => LinkType::ShortcutUnknown,
_ => unreachable!(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Event<'a> {
#[cfg_attr(feature = "serde", serde(borrow))]
Start(Tag<'a>),
End(TagEnd),
#[cfg_attr(feature = "serde", serde(borrow))]
Text(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
Code(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
InlineMath(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
DisplayMath(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
Html(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
InlineHtml(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
FootnoteReference(CowStr<'a>),
SoftBreak,
HardBreak,
Rule,
TaskListMarker(bool),
#[cfg_attr(feature = "serde", serde(borrow))]
MdxFlowExpression(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
MdxTextExpression(CowStr<'a>),
#[cfg_attr(feature = "serde", serde(borrow))]
MdxEsm(CowStr<'a>),
}
impl<'a> Event<'a> {
pub fn into_static(self) -> Event<'static> {
match self {
Event::Start(t) => Event::Start(t.into_static()),
Event::End(e) => Event::End(e),
Event::Text(s) => Event::Text(s.into_static()),
Event::Code(s) => Event::Code(s.into_static()),
Event::InlineMath(s) => Event::InlineMath(s.into_static()),
Event::DisplayMath(s) => Event::DisplayMath(s.into_static()),
Event::Html(s) => Event::Html(s.into_static()),
Event::InlineHtml(s) => Event::InlineHtml(s.into_static()),
Event::FootnoteReference(s) => Event::FootnoteReference(s.into_static()),
Event::SoftBreak => Event::SoftBreak,
Event::HardBreak => Event::HardBreak,
Event::Rule => Event::Rule,
Event::TaskListMarker(b) => Event::TaskListMarker(b),
Event::MdxFlowExpression(s) => Event::MdxFlowExpression(s.into_static()),
Event::MdxTextExpression(s) => Event::MdxTextExpression(s.into_static()),
Event::MdxEsm(s) => Event::MdxEsm(s.into_static()),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum Alignment {
None,
Left,
Center,
Right,
}
bitflags::bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Options: u32 {
const ENABLE_TABLES = 1 << 1;
const ENABLE_FOOTNOTES = 1 << 2;
const ENABLE_STRIKETHROUGH = 1 << 3;
const ENABLE_TASKLISTS = 1 << 4;
const ENABLE_SMART_PUNCTUATION = 1 << 5;
const ENABLE_HEADING_ATTRIBUTES = 1 << 6;
const ENABLE_YAML_STYLE_METADATA_BLOCKS = 1 << 7;
const ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS = 1 << 8;
const ENABLE_OLD_FOOTNOTES = (1 << 9) | (1 << 2);
const ENABLE_MATH = 1 << 10;
const ENABLE_GFM = 1 << 11;
const ENABLE_DEFINITION_LIST = 1 << 12;
const ENABLE_SUPERSCRIPT = 1 << 13;
const ENABLE_SUBSCRIPT = 1 << 14;
const ENABLE_WIKILINKS = 1 << 15;
const ENABLE_CONTAINER_EXTENSIONS = 1 << 16;
const ENABLE_MDX = 1 << 17;
}
}
impl Options {
pub(crate) fn has_gfm_footnotes(&self) -> bool {
self.contains(Options::ENABLE_FOOTNOTES) && !self.contains(Options::ENABLE_OLD_FOOTNOTES)
}
}