use std::borrow::Cow;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Span {
pub start: u64,
pub end: u64,
pub line: Option<u32>,
pub page: Option<u32>,
}
impl Span {
pub const fn empty() -> Self {
Span {
start: 0,
end: 0,
line: None,
page: None,
}
}
pub const fn bytes(start: u64, end: u64) -> Self {
Span {
start,
end,
line: None,
page: None,
}
}
pub const fn at_line(start: u64, end: u64, line: u32) -> Self {
Span {
start,
end,
line: Some(line),
page: None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Spanned<T> {
pub node: T,
pub span: Span,
}
impl<T> Spanned<T> {
pub const fn new(node: T, span: Span) -> Self {
Spanned { node, span }
}
pub const fn bare(node: T) -> Self {
Spanned {
node,
span: Span::empty(),
}
}
}
pub type Events<'a> = Box<dyn Iterator<Item = crate::Result<Spanned<Event<'a>>>> + 'a>;
#[derive(Clone, Debug, PartialEq)]
pub enum Event<'a> {
Start(Tag<'a>),
End(TagKind),
Text(Cow<'a, str>),
Code(Cow<'a, str>),
Image {
source: ImageSource<'a>,
alt: Cow<'a, str>,
dims: Option<(u32, u32)>,
},
Math {
inline: bool,
tex: Cow<'a, str>,
},
FootnoteRef(Cow<'a, str>),
Break(BreakKind),
Rule,
PageBreak,
TaskMarker(bool),
Diagnostic(Diagnostic),
}
#[derive(Clone, Debug, PartialEq)]
pub enum Tag<'a> {
Document(Box<Metadata<'a>>),
Section {
level: u8,
},
Heading {
level: u8,
id: Option<Cow<'a, str>>,
},
Paragraph,
Preformatted,
List {
ordered: bool,
start: u64,
tight: bool,
},
ListItem {
marker: Marker,
},
Table {
align: Vec<Align>,
},
TableHead,
TableRow,
TableCell {
colspan: u16,
rowspan: u16,
},
CodeBlock {
lang: Option<Cow<'a, str>>,
filename: Option<Cow<'a, str>>,
},
BlockQuote {
attribution: Option<Cow<'a, str>>,
},
Admonition {
kind: AdmonitionKind,
},
Figure {
caption: Option<Cow<'a, str>>,
},
Footnote {
id: Cow<'a, str>,
},
DefinitionList,
DefinitionTerm,
DefinitionDetail,
Emphasis,
Strong,
Strikethrough,
Underline,
Highlight,
SmallCaps,
Sub,
Super,
Link {
href: Cow<'a, str>,
title: Option<Cow<'a, str>>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TagKind {
Document,
Section,
Heading,
Paragraph,
Preformatted,
List,
ListItem,
Table,
TableHead,
TableRow,
TableCell,
CodeBlock,
BlockQuote,
Admonition,
Figure,
Footnote,
DefinitionList,
DefinitionTerm,
DefinitionDetail,
Emphasis,
Strong,
Strikethrough,
Underline,
Highlight,
SmallCaps,
Sub,
Super,
Link,
}
impl Tag<'_> {
pub fn kind(&self) -> TagKind {
match self {
Tag::Document(_) => TagKind::Document,
Tag::Section { .. } => TagKind::Section,
Tag::Heading { .. } => TagKind::Heading,
Tag::Paragraph => TagKind::Paragraph,
Tag::Preformatted => TagKind::Preformatted,
Tag::List { .. } => TagKind::List,
Tag::ListItem { .. } => TagKind::ListItem,
Tag::Table { .. } => TagKind::Table,
Tag::TableHead => TagKind::TableHead,
Tag::TableRow => TagKind::TableRow,
Tag::TableCell { .. } => TagKind::TableCell,
Tag::CodeBlock { .. } => TagKind::CodeBlock,
Tag::BlockQuote { .. } => TagKind::BlockQuote,
Tag::Admonition { .. } => TagKind::Admonition,
Tag::Figure { .. } => TagKind::Figure,
Tag::Footnote { .. } => TagKind::Footnote,
Tag::DefinitionList => TagKind::DefinitionList,
Tag::DefinitionTerm => TagKind::DefinitionTerm,
Tag::DefinitionDetail => TagKind::DefinitionDetail,
Tag::Emphasis => TagKind::Emphasis,
Tag::Strong => TagKind::Strong,
Tag::Strikethrough => TagKind::Strikethrough,
Tag::Underline => TagKind::Underline,
Tag::Highlight => TagKind::Highlight,
Tag::SmallCaps => TagKind::SmallCaps,
Tag::Sub => TagKind::Sub,
Tag::Super => TagKind::Super,
Tag::Link { .. } => TagKind::Link,
}
}
}
impl TagKind {
pub fn is_block(self) -> bool {
!matches!(
self,
TagKind::Emphasis
| TagKind::Strong
| TagKind::Strikethrough
| TagKind::Underline
| TagKind::Highlight
| TagKind::SmallCaps
| TagKind::Sub
| TagKind::Super
| TagKind::Link
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BreakKind {
Soft,
Hard,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Align {
#[default]
None,
Left,
Center,
Right,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AdmonitionKind {
Note,
Tip,
Important,
Warning,
Caution,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Marker {
Bullet {
depth: u8,
},
Ordered {
number: u64,
},
}
#[derive(Clone)]
pub enum ImageSource<'a> {
Path(Cow<'a, str>),
Bytes(Arc<[u8]>),
Entry {
container: Cow<'a, str>,
name: Cow<'a, str>,
},
}
impl std::fmt::Debug for ImageSource<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ImageSource::Path(p) => write!(f, "Path({p:?})"),
ImageSource::Bytes(b) => write!(f, "Bytes({} bytes)", b.len()),
ImageSource::Entry { container, name } => {
write!(f, "Entry({container:?}, {name:?})")
}
}
}
}
impl PartialEq for ImageSource<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ImageSource::Path(a), ImageSource::Path(b)) => a == b,
(ImageSource::Bytes(a), ImageSource::Bytes(b)) => a == b,
(
ImageSource::Entry {
container: c1,
name: n1,
},
ImageSource::Entry {
container: c2,
name: n2,
},
) => c1 == c2 && n1 == n2,
_ => false,
}
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Metadata<'a> {
pub title: Option<Cow<'a, str>>,
pub authors: Vec<Cow<'a, str>>,
pub date: Option<Cow<'a, str>>,
pub language: Option<Cow<'a, str>>,
pub page_count: Option<u32>,
pub word_count: Option<u64>,
pub source_format: Option<crate::FormatId>,
pub encoding: Option<Cow<'a, str>>,
pub custom: Vec<(Cow<'a, str>, Cow<'a, str>)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Severity {
Info,
Warning,
Error,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Diagnostic {
pub severity: Severity,
pub message: String,
}
impl Diagnostic {
pub fn warning(message: impl Into<String>) -> Self {
Diagnostic {
severity: Severity::Warning,
message: message.into(),
}
}
pub fn error(message: impl Into<String>) -> Self {
Diagnostic {
severity: Severity::Error,
message: message.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn kind_round_trips_for_every_tag() {
let tags = [
Tag::Paragraph,
Tag::Preformatted,
Tag::Emphasis,
Tag::Strong,
Tag::Heading { level: 1, id: None },
Tag::List {
ordered: false,
start: 1,
tight: true,
},
Tag::Link {
href: "x".into(),
title: None,
},
];
for t in tags {
let k = t.kind();
assert_eq!(k, t.kind(), "kind() must be stable");
}
}
#[test]
fn inline_is_not_a_block() {
assert!(!TagKind::Emphasis.is_block());
assert!(!TagKind::Link.is_block());
assert!(TagKind::Paragraph.is_block());
assert!(TagKind::Table.is_block());
}
}