use crate::Span;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParagraphType {
Text,
Header1,
Header2,
Header3,
CodeBlock,
OrderedList,
UnorderedList,
Checklist,
Quote,
}
impl fmt::Display for ParagraphType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
ParagraphType::Text => "Text",
ParagraphType::Header1 => "Header Lvl 1",
ParagraphType::Header2 => "Header Lvl 2",
ParagraphType::Header3 => "Header Lvl 3",
ParagraphType::CodeBlock => "Code Block",
ParagraphType::OrderedList => "Ordered List",
ParagraphType::UnorderedList => "Unordered List",
ParagraphType::Checklist => "Checklist",
ParagraphType::Quote => "Quote",
};
write!(f, "{}", s)
}
}
impl ParagraphType {
pub fn is_leaf(&self) -> bool {
matches!(
self,
ParagraphType::Text
| ParagraphType::Header1
| ParagraphType::Header2
| ParagraphType::Header3
| ParagraphType::CodeBlock
)
}
pub fn html_tag(&self) -> &'static str {
match self {
ParagraphType::Text => "p",
ParagraphType::Header1 => "h1",
ParagraphType::Header2 => "h2",
ParagraphType::Header3 => "h3",
ParagraphType::CodeBlock => "pre",
ParagraphType::OrderedList => "ol",
ParagraphType::UnorderedList => "ul",
ParagraphType::Checklist => "ul",
ParagraphType::Quote => "blockquote",
}
}
pub fn from_html_tag(tag: &str) -> Option<Self> {
match tag {
"p" => Some(ParagraphType::Text),
"h1" => Some(ParagraphType::Header1),
"h2" => Some(ParagraphType::Header2),
"h3" => Some(ParagraphType::Header3),
"pre" => Some(ParagraphType::CodeBlock),
"ol" => Some(ParagraphType::OrderedList),
"ul" => Some(ParagraphType::UnorderedList),
"blockquote" => Some(ParagraphType::Quote),
_ => None,
}
}
pub fn matches_closing_tag(self, closing: ParagraphType) -> bool {
if self == closing {
return true;
}
matches!(
(self, closing),
(ParagraphType::Checklist, ParagraphType::UnorderedList)
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Paragraph {
Text { content: Vec<Span> },
Header1 { content: Vec<Span> },
Header2 { content: Vec<Span> },
Header3 { content: Vec<Span> },
CodeBlock { content: Vec<Span> },
OrderedList { entries: Vec<Vec<Paragraph>> },
UnorderedList { entries: Vec<Vec<Paragraph>> },
Checklist { items: Vec<ChecklistItem> },
Quote { children: Vec<Paragraph> },
}
impl Paragraph {
pub fn new(paragraph_type: ParagraphType) -> Self {
match paragraph_type {
ParagraphType::Text => Self::new_text(),
ParagraphType::Header1 => Self::new_header1(),
ParagraphType::Header2 => Self::new_header2(),
ParagraphType::Header3 => Self::new_header3(),
ParagraphType::CodeBlock => Self::new_code_block(),
ParagraphType::OrderedList => Self::new_ordered_list(),
ParagraphType::UnorderedList => Self::new_unordered_list(),
ParagraphType::Checklist => Self::new_checklist(),
ParagraphType::Quote => Self::new_quote(),
}
}
pub fn new_text() -> Self {
Self::Text {
content: Vec::new(),
}
}
pub fn new_header1() -> Self {
Self::Header1 {
content: Vec::new(),
}
}
pub fn new_header2() -> Self {
Self::Header2 {
content: Vec::new(),
}
}
pub fn new_header3() -> Self {
Self::Header3 {
content: Vec::new(),
}
}
pub fn new_code_block() -> Self {
Self::CodeBlock {
content: Vec::new(),
}
}
pub fn new_ordered_list() -> Self {
Self::OrderedList {
entries: Vec::new(),
}
}
pub fn new_unordered_list() -> Self {
Self::UnorderedList {
entries: Vec::new(),
}
}
pub fn new_checklist() -> Self {
Self::Checklist { items: Vec::new() }
}
pub fn new_quote() -> Self {
Self::Quote {
children: Vec::new(),
}
}
pub fn paragraph_type(&self) -> ParagraphType {
match self {
Paragraph::Text { .. } => ParagraphType::Text,
Paragraph::Header1 { .. } => ParagraphType::Header1,
Paragraph::Header2 { .. } => ParagraphType::Header2,
Paragraph::Header3 { .. } => ParagraphType::Header3,
Paragraph::CodeBlock { .. } => ParagraphType::CodeBlock,
Paragraph::OrderedList { .. } => ParagraphType::OrderedList,
Paragraph::UnorderedList { .. } => ParagraphType::UnorderedList,
Paragraph::Checklist { .. } => ParagraphType::Checklist,
Paragraph::Quote { .. } => ParagraphType::Quote,
}
}
pub fn is_leaf(&self) -> bool {
self.paragraph_type().is_leaf()
}
pub fn content(&self) -> &[Span] {
match self {
Paragraph::Text { content }
| Paragraph::Header1 { content }
| Paragraph::Header2 { content }
| Paragraph::Header3 { content }
| Paragraph::CodeBlock { content } => content,
_ => &[],
}
}
pub fn content_mut(&mut self) -> &mut Vec<Span> {
match self {
Paragraph::Text { content }
| Paragraph::Header1 { content }
| Paragraph::Header2 { content }
| Paragraph::Header3 { content }
| Paragraph::CodeBlock { content } => content,
_ => panic!("only leaf paragraphs contain inline content"),
}
}
pub fn with_content(self, content: Vec<Span>) -> Self {
match self {
Paragraph::Text { .. } => Paragraph::Text { content },
Paragraph::Header1 { .. } => Paragraph::Header1 { content },
Paragraph::Header2 { .. } => Paragraph::Header2 { content },
Paragraph::Header3 { .. } => Paragraph::Header3 { content },
Paragraph::CodeBlock { .. } => Paragraph::CodeBlock { content },
_ => panic!("only leaf paragraphs can hold inline content"),
}
}
pub fn children(&self) -> &[Paragraph] {
match self {
Paragraph::Quote { children } => children,
_ => &[],
}
}
pub fn children_mut(&mut self) -> &mut Vec<Paragraph> {
match self {
Paragraph::Quote { children } => children,
_ => panic!("only block quotes hold child paragraphs"),
}
}
pub fn with_children(self, children: Vec<Paragraph>) -> Self {
match self {
Paragraph::Quote { .. } => Paragraph::Quote { children },
_ => panic!("only block quotes can hold child paragraphs"),
}
}
pub fn add_child(&mut self, child: Paragraph) {
self.children_mut().push(child);
}
pub fn entries(&self) -> &[Vec<Paragraph>] {
match self {
Paragraph::OrderedList { entries } | Paragraph::UnorderedList { entries } => entries,
_ => &[],
}
}
pub fn entries_mut(&mut self) -> &mut Vec<Vec<Paragraph>> {
match self {
Paragraph::OrderedList { entries } | Paragraph::UnorderedList { entries } => entries,
_ => panic!("only list paragraphs can hold entries"),
}
}
pub fn with_entries(self, entries: Vec<Vec<Paragraph>>) -> Self {
match self {
Paragraph::OrderedList { .. } => Paragraph::OrderedList { entries },
Paragraph::UnorderedList { .. } => Paragraph::UnorderedList { entries },
_ => panic!("only list paragraphs can hold entries"),
}
}
pub fn add_list_item(&mut self, item: Vec<Paragraph>) {
self.entries_mut().push(item);
}
pub fn checklist_items(&self) -> &[ChecklistItem] {
match self {
Paragraph::Checklist { items } => items,
_ => &[],
}
}
pub fn checklist_items_mut(&mut self) -> &mut Vec<ChecklistItem> {
match self {
Paragraph::Checklist { items } => items,
_ => panic!("only checklist paragraphs can hold checklist items"),
}
}
pub fn with_checklist_items(self, items: Vec<ChecklistItem>) -> Self {
match self {
Paragraph::Checklist { .. } => Paragraph::Checklist { items },
_ => panic!("only checklist paragraphs can hold checklist items"),
}
}
pub fn add_checklist_item(&mut self, item: ChecklistItem) {
self.checklist_items_mut().push(item);
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChecklistItem {
pub checked: bool,
pub content: Vec<Span>,
pub children: Vec<ChecklistItem>,
}
impl ChecklistItem {
pub fn new(checked: bool) -> Self {
Self {
checked,
content: Vec::new(),
children: Vec::new(),
}
}
pub fn with_content(mut self, content: Vec<Span>) -> Self {
self.content = content;
self
}
pub fn with_children(mut self, children: Vec<ChecklistItem>) -> Self {
self.children = children;
self
}
pub fn add_child(&mut self, child: ChecklistItem) {
self.children.push(child);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_paragraph_type_display() {
assert_eq!(format!("{}", ParagraphType::Text), "Text");
assert_eq!(format!("{}", ParagraphType::Header1), "Header Lvl 1");
}
#[test]
fn test_html_tag_conversion() {
assert_eq!(ParagraphType::Text.html_tag(), "p");
assert_eq!(ParagraphType::from_html_tag("p"), Some(ParagraphType::Text));
assert_eq!(ParagraphType::CodeBlock.html_tag(), "pre");
assert_eq!(
ParagraphType::from_html_tag("pre"),
Some(ParagraphType::CodeBlock)
);
assert_eq!(ParagraphType::from_html_tag("div"), None);
}
#[test]
fn test_is_leaf() {
assert!(ParagraphType::Text.is_leaf());
assert!(ParagraphType::Header1.is_leaf());
assert!(ParagraphType::CodeBlock.is_leaf());
assert!(!ParagraphType::OrderedList.is_leaf());
assert!(!ParagraphType::Quote.is_leaf());
}
#[test]
fn test_paragraph_creation() {
let p = Paragraph::new_text().with_content(vec![Span::new_text("Hello")]);
assert_eq!(p.paragraph_type(), ParagraphType::Text);
assert_eq!(p.content().len(), 1);
assert_eq!(p.content()[0].text, "Hello");
}
}