use crate::{CodeFence, LineEnding};
use alloc::{borrow::Cow, string::String};
use core::{fmt, ops::Range};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Text<'a> {
Source(Range<usize>),
Literal(&'static str),
Value(Cow<'a, str>),
}
#[cfg(feature = "serde")]
impl serde::Serialize for Text<'_> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Self::Source(range) => serializer.serialize_newtype_variant("Text", 0, "Source", range),
Self::Literal(value) => serializer.serialize_newtype_variant("Text", 1, "Value", value),
Self::Value(value) => serializer.serialize_newtype_variant("Text", 1, "Value", value),
}
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Text<'_> {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(serde::Deserialize)]
enum Stored {
Source(Range<usize>),
Value(String),
Literal(String),
}
Ok(match Stored::deserialize(deserializer)? {
Stored::Source(range) => Self::Source(range),
Stored::Value(value) | Stored::Literal(value) => Self::Value(Cow::Owned(value)),
})
}
}
impl Text<'_> {
pub fn resolve<'a>(&'a self, source: &'a str) -> Result<&'a str, StreamError> {
match self {
Self::Source(range) => source.get(range.clone()).ok_or(StreamError::InvalidRange),
Self::Literal(value) => Ok(value),
Self::Value(value) => Ok(value),
}
}
pub(crate) fn borrowed(&self) -> Text<'_> {
match self {
Self::Source(range) => Text::Source(range.clone()),
Self::Literal(value) => Text::Literal(value),
Self::Value(value) => Text::Value(Cow::Borrowed(value)),
}
}
pub(crate) fn to_mut(&mut self, source: &str) -> Result<&mut String, StreamError> {
if !matches!(self, Self::Value(_)) {
*self = Self::Value(Cow::Owned(self.resolve(source)?.into()));
}
match self {
Self::Value(value) => Ok(value.to_mut()),
_ => unreachable!("source and literal text were materialized above"),
}
}
pub(crate) fn materialized(self, source: &str) -> Text<'static> {
match self {
Self::Source(range) => Text::from(String::from(
source.get(range).expect("document ranges are validated"),
)),
Self::Literal(value) => Text::Literal(value),
Self::Value(value) => Text::Value(Cow::Owned(value.into_owned())),
}
}
pub(crate) fn owned(self) -> Text<'static> {
match self {
Self::Source(range) => Text::Source(range),
Self::Literal(value) => Text::Literal(value),
Self::Value(value) => Text::Value(Cow::Owned(value.into_owned())),
}
}
}
impl From<String> for Text<'static> {
fn from(value: String) -> Self {
Self::Value(Cow::Owned(value))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TagKind {
Document,
Paragraph,
Heading,
BlockQuote,
List,
Item,
Emphasis,
Strong,
Delete,
Link,
Image,
Table,
TableHead,
TableBody,
TableRow,
TableCell,
FootnoteDefinition,
}
impl TagKind {
pub(crate) fn allows_descendant(self, child: Self) -> bool {
!matches!(self, Self::Heading | Self::Image)
|| !matches!(child, Self::Heading | Self::FootnoteDefinition)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Tag<'a> {
Document(Option<LineEnding>),
Paragraph { tight: bool },
Heading(u8),
BlockQuote,
List { start: Option<u32>, tight: bool },
Item { block_end: bool },
Emphasis,
Strong,
Delete,
Link {
destination: Text<'a>,
title: Option<Text<'a>>,
},
Image {
destination: Text<'a>,
title: Option<Text<'a>>,
},
Table,
TableHead,
TableBody,
TableRow,
TableCell {
alignment: crate::AlignKind,
header: bool,
},
FootnoteDefinition(Text<'a>),
}
impl<'s> Tag<'s> {
pub fn kind(&self) -> TagKind {
match self {
Self::Document(_) => TagKind::Document,
Self::Paragraph { .. } => TagKind::Paragraph,
Self::Heading(_) => TagKind::Heading,
Self::BlockQuote => TagKind::BlockQuote,
Self::List { .. } => TagKind::List,
Self::Item { .. } => TagKind::Item,
Self::Emphasis => TagKind::Emphasis,
Self::Strong => TagKind::Strong,
Self::Delete => TagKind::Delete,
Self::Link { .. } => TagKind::Link,
Self::Image { .. } => TagKind::Image,
Self::Table => TagKind::Table,
Self::TableHead => TagKind::TableHead,
Self::TableBody => TagKind::TableBody,
Self::TableRow => TagKind::TableRow,
Self::TableCell { .. } => TagKind::TableCell,
Self::FootnoteDefinition(_) => TagKind::FootnoteDefinition,
}
}
pub(crate) fn map_text<'a>(self, mut map: impl FnMut(Text<'s>) -> Text<'a>) -> Tag<'a> {
match self {
Self::Document(value) => Tag::Document(value),
Self::Paragraph { tight } => Tag::Paragraph { tight },
Self::Heading(value) => Tag::Heading(value),
Self::BlockQuote => Tag::BlockQuote,
Self::List { start, tight } => Tag::List { start, tight },
Self::Item { block_end } => Tag::Item { block_end },
Self::Emphasis => Tag::Emphasis,
Self::Strong => Tag::Strong,
Self::Delete => Tag::Delete,
Self::Link { destination, title } => Tag::Link {
destination: map(destination),
title: title.map(&mut map),
},
Self::Image { destination, title } => Tag::Image {
destination: map(destination),
title: title.map(&mut map),
},
Self::Table => Tag::Table,
Self::TableHead => Tag::TableHead,
Self::TableBody => Tag::TableBody,
Self::TableRow => Tag::TableRow,
Self::TableCell { alignment, header } => Tag::TableCell { alignment, header },
Self::FootnoteDefinition(value) => Tag::FootnoteDefinition(map(value)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EventKind<'a> {
Start(Tag<'a>),
End(TagKind),
Text(Text<'a>),
SoftBreak(Text<'a>),
Separator { value: Text<'a>, conditional: bool },
HardBreak,
Rule,
Code {
value: Text<'a>,
language: Option<Text<'a>>,
},
CodeBlock {
value: Text<'a>,
language: Option<Text<'a>>,
fence: Option<CodeFence>,
math: bool,
unclosed: bool,
},
Math(Text<'a>),
Html { value: Text<'a>, block: bool },
TaskMarker(bool),
Definition {
label: Text<'a>,
destination: Text<'a>,
title: Option<Text<'a>>,
},
Opaque { value: Text<'a>, block: bool },
FootnoteReference(Text<'a>),
}
impl<'s> EventKind<'s> {
pub(crate) fn validate(&self, source: Option<&str>) -> Result<(), StreamError> {
let text = |value: &Text<'_>| {
if let Some(source) = source {
value.resolve(source)?;
}
Ok(())
};
let optional = |value: &Option<Text<'_>>| {
if let Some(value) = value {
text(value)?;
}
Ok(())
};
match self {
Self::Start(Tag::Heading(level)) if !(1..=6).contains(level) => {
Err(StreamError::Unbalanced)
}
Self::Start(Tag::Link { destination, title } | Tag::Image { destination, title }) => {
text(destination)?;
optional(title)
}
Self::Start(Tag::FootnoteDefinition(value))
| Self::Text(value)
| Self::SoftBreak(value)
| Self::Math(value)
| Self::Separator { value, .. }
| Self::Html { value, .. }
| Self::Opaque { value, .. }
| Self::FootnoteReference(value) => text(value),
Self::Code { value, language }
| Self::CodeBlock {
value, language, ..
} => {
text(value)?;
optional(language)
}
Self::Definition {
label,
destination,
title,
} => {
text(label)?;
text(destination)?;
optional(title)
}
_ => Ok(()),
}
}
pub(crate) fn map_text<'a>(self, mut map: impl FnMut(Text<'s>) -> Text<'a>) -> EventKind<'a> {
match self {
Self::Start(tag) => EventKind::Start(tag.map_text(map)),
Self::End(kind) => EventKind::End(kind),
Self::Text(text) => EventKind::Text(map(text)),
Self::SoftBreak(text) => EventKind::SoftBreak(map(text)),
Self::Separator { value, conditional } => EventKind::Separator {
value: map(value),
conditional,
},
Self::HardBreak => EventKind::HardBreak,
Self::Rule => EventKind::Rule,
Self::Code { value, language } => EventKind::Code {
value: map(value),
language: language.map(map),
},
Self::CodeBlock {
value,
language,
fence,
math,
unclosed,
} => EventKind::CodeBlock {
value: map(value),
language: language.map(map),
fence,
math,
unclosed,
},
Self::Math(value) => EventKind::Math(map(value)),
Self::Html { value, block } => EventKind::Html {
value: map(value),
block,
},
Self::TaskMarker(value) => EventKind::TaskMarker(value),
Self::Definition {
label,
destination,
title,
} => EventKind::Definition {
label: map(label),
destination: map(destination),
title: title.map(map),
},
Self::Opaque { value, block } => EventKind::Opaque {
value: map(value),
block,
},
Self::FootnoteReference(value) => EventKind::FootnoteReference(map(value)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Event<'a> {
pub kind: EventKind<'a>,
pub span: Range<usize>,
}
impl Event<'_> {
pub(crate) fn validate(&self, source: Option<&str>) -> Result<(), StreamError> {
if self.span.start > self.span.end
|| (matches!(self.kind, EventKind::End(_)) && !self.span.is_empty())
|| source.is_some_and(|source| source.get(self.span.clone()).is_none())
{
return Err(StreamError::InvalidRange);
}
self.kind.validate(source)
}
}
#[derive(Debug)]
pub enum StreamError {
Parse(crate::message::Message),
InvalidRange,
Unbalanced,
Write(fmt::Error),
}
impl fmt::Display for StreamError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Parse(error) => write!(f, "{error}"),
Self::InvalidRange => f.write_str("invalid source range"),
Self::Unbalanced => f.write_str("unbalanced semantic event stream"),
Self::Write(error) => write!(f, "{error}"),
}
}
}
impl From<fmt::Error> for StreamError {
fn from(error: fmt::Error) -> Self {
Self::Write(error)
}
}
impl From<crate::message::Message> for StreamError {
fn from(error: crate::message::Message) -> Self {
Self::Parse(error)
}
}