1use crate::Span;
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum ParagraphType {
9 Text,
11 Header1,
13 Header2,
15 Header3,
17 CodeBlock,
19 OrderedList,
21 UnorderedList,
23 Checklist,
25 Quote,
27 Table,
29 HorizontalRule,
31 DefinitionList,
33}
34
35impl fmt::Display for ParagraphType {
36 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37 let s = match self {
38 ParagraphType::Text => "Text",
39 ParagraphType::Header1 => "Header Lvl 1",
40 ParagraphType::Header2 => "Header Lvl 2",
41 ParagraphType::Header3 => "Header Lvl 3",
42 ParagraphType::CodeBlock => "Code Block",
43 ParagraphType::OrderedList => "Ordered List",
44 ParagraphType::UnorderedList => "Unordered List",
45 ParagraphType::Checklist => "Checklist",
46 ParagraphType::Quote => "Quote",
47 ParagraphType::Table => "Table",
48 ParagraphType::HorizontalRule => "Horizontal Rule",
49 ParagraphType::DefinitionList => "Definition List",
50 };
51 write!(f, "{}", s)
52 }
53}
54
55impl ParagraphType {
56 pub fn is_leaf(&self) -> bool {
58 matches!(
59 self,
60 ParagraphType::Text
61 | ParagraphType::Header1
62 | ParagraphType::Header2
63 | ParagraphType::Header3
64 | ParagraphType::CodeBlock
65 | ParagraphType::HorizontalRule
66 )
67 }
68
69 pub fn html_tag(&self) -> &'static str {
71 match self {
72 ParagraphType::Text => "p",
73 ParagraphType::Header1 => "h1",
74 ParagraphType::Header2 => "h2",
75 ParagraphType::Header3 => "h3",
76 ParagraphType::CodeBlock => "pre",
77 ParagraphType::OrderedList => "ol",
78 ParagraphType::UnorderedList => "ul",
79 ParagraphType::Checklist => "ul",
80 ParagraphType::Quote => "blockquote",
81 ParagraphType::Table => "table",
82 ParagraphType::HorizontalRule => "hr",
83 ParagraphType::DefinitionList => "dl",
84 }
85 }
86
87 pub fn from_html_tag(tag: &str) -> Option<Self> {
89 match tag {
90 "p" => Some(ParagraphType::Text),
91 "h1" => Some(ParagraphType::Header1),
92 "h2" => Some(ParagraphType::Header2),
93 "h3" => Some(ParagraphType::Header3),
94 "pre" => Some(ParagraphType::CodeBlock),
95 "ol" => Some(ParagraphType::OrderedList),
96 "ul" => Some(ParagraphType::UnorderedList),
97 "blockquote" => Some(ParagraphType::Quote),
98 "table" => Some(ParagraphType::Table),
99 "hr" => Some(ParagraphType::HorizontalRule),
100 "dl" => Some(ParagraphType::DefinitionList),
101 _ => None,
102 }
103 }
104
105 pub fn matches_closing_tag(self, closing: ParagraphType) -> bool {
108 if self == closing {
109 return true;
110 }
111
112 matches!(
113 (self, closing),
114 (ParagraphType::Checklist, ParagraphType::UnorderedList)
115 )
116 }
117}
118
119#[derive(Debug, Clone, PartialEq)]
120pub enum Paragraph {
143 Text { content: Vec<Span> },
145 Header1 { content: Vec<Span> },
147 Header2 { content: Vec<Span> },
149 Header3 { content: Vec<Span> },
151 CodeBlock { content: Vec<Span> },
153 OrderedList { entries: Vec<Vec<Paragraph>> },
155 UnorderedList { entries: Vec<Vec<Paragraph>> },
157 Checklist { items: Vec<ChecklistItem> },
159 Quote { children: Vec<Paragraph> },
161 Table { rows: Vec<TableRow> },
163 HorizontalRule,
165 DefinitionList { items: Vec<DefinitionItem> },
167}
168
169impl Paragraph {
170 pub fn new(paragraph_type: ParagraphType) -> Self {
172 match paragraph_type {
173 ParagraphType::Text => Self::new_text(),
174 ParagraphType::Header1 => Self::new_header1(),
175 ParagraphType::Header2 => Self::new_header2(),
176 ParagraphType::Header3 => Self::new_header3(),
177 ParagraphType::CodeBlock => Self::new_code_block(),
178 ParagraphType::OrderedList => Self::new_ordered_list(),
179 ParagraphType::UnorderedList => Self::new_unordered_list(),
180 ParagraphType::Checklist => Self::new_checklist(),
181 ParagraphType::Quote => Self::new_quote(),
182 ParagraphType::Table => Self::new_table(),
183 ParagraphType::HorizontalRule => Self::new_horizontal_rule(),
184 ParagraphType::DefinitionList => Self::new_definition_list(),
185 }
186 }
187
188 pub fn new_text() -> Self {
190 Self::Text {
191 content: Vec::new(),
192 }
193 }
194
195 pub fn new_header1() -> Self {
197 Self::Header1 {
198 content: Vec::new(),
199 }
200 }
201
202 pub fn new_header2() -> Self {
204 Self::Header2 {
205 content: Vec::new(),
206 }
207 }
208
209 pub fn new_header3() -> Self {
211 Self::Header3 {
212 content: Vec::new(),
213 }
214 }
215
216 pub fn new_code_block() -> Self {
218 Self::CodeBlock {
219 content: Vec::new(),
220 }
221 }
222
223 pub fn new_ordered_list() -> Self {
225 Self::OrderedList {
226 entries: Vec::new(),
227 }
228 }
229
230 pub fn new_unordered_list() -> Self {
232 Self::UnorderedList {
233 entries: Vec::new(),
234 }
235 }
236
237 pub fn new_checklist() -> Self {
239 Self::Checklist { items: Vec::new() }
240 }
241
242 pub fn new_quote() -> Self {
244 Self::Quote {
245 children: Vec::new(),
246 }
247 }
248
249 pub fn new_table() -> Self {
251 Self::Table { rows: Vec::new() }
252 }
253
254 pub fn new_horizontal_rule() -> Self {
256 Self::HorizontalRule
257 }
258
259 pub fn new_definition_list() -> Self {
261 Self::DefinitionList { items: Vec::new() }
262 }
263
264 pub fn paragraph_type(&self) -> ParagraphType {
266 match self {
267 Paragraph::Text { .. } => ParagraphType::Text,
268 Paragraph::Header1 { .. } => ParagraphType::Header1,
269 Paragraph::Header2 { .. } => ParagraphType::Header2,
270 Paragraph::Header3 { .. } => ParagraphType::Header3,
271 Paragraph::CodeBlock { .. } => ParagraphType::CodeBlock,
272 Paragraph::OrderedList { .. } => ParagraphType::OrderedList,
273 Paragraph::UnorderedList { .. } => ParagraphType::UnorderedList,
274 Paragraph::Checklist { .. } => ParagraphType::Checklist,
275 Paragraph::Quote { .. } => ParagraphType::Quote,
276 Paragraph::Table { .. } => ParagraphType::Table,
277 Paragraph::HorizontalRule => ParagraphType::HorizontalRule,
278 Paragraph::DefinitionList { .. } => ParagraphType::DefinitionList,
279 }
280 }
281
282 pub fn is_leaf(&self) -> bool {
284 self.paragraph_type().is_leaf()
285 }
286
287 pub fn content(&self) -> &[Span] {
289 match self {
290 Paragraph::Text { content }
291 | Paragraph::Header1 { content }
292 | Paragraph::Header2 { content }
293 | Paragraph::Header3 { content }
294 | Paragraph::CodeBlock { content } => content,
295 _ => &[],
296 }
297 }
298
299 pub fn content_mut(&mut self) -> &mut Vec<Span> {
301 match self {
302 Paragraph::Text { content }
303 | Paragraph::Header1 { content }
304 | Paragraph::Header2 { content }
305 | Paragraph::Header3 { content }
306 | Paragraph::CodeBlock { content } => content,
307 _ => panic!("only leaf paragraphs contain inline content"),
308 }
309 }
310
311 pub fn with_content(self, content: Vec<Span>) -> Self {
313 match self {
314 Paragraph::Text { .. } => Paragraph::Text { content },
315 Paragraph::Header1 { .. } => Paragraph::Header1 { content },
316 Paragraph::Header2 { .. } => Paragraph::Header2 { content },
317 Paragraph::Header3 { .. } => Paragraph::Header3 { content },
318 Paragraph::CodeBlock { .. } => Paragraph::CodeBlock { content },
319 _ => panic!("only leaf paragraphs can hold inline content"),
320 }
321 }
322
323 pub fn children(&self) -> &[Paragraph] {
325 match self {
326 Paragraph::Quote { children } => children,
327 _ => &[],
328 }
329 }
330
331 pub fn children_mut(&mut self) -> &mut Vec<Paragraph> {
333 match self {
334 Paragraph::Quote { children } => children,
335 _ => panic!("only block quotes hold child paragraphs"),
336 }
337 }
338
339 pub fn with_children(self, children: Vec<Paragraph>) -> Self {
341 match self {
342 Paragraph::Quote { .. } => Paragraph::Quote { children },
343 _ => panic!("only block quotes can hold child paragraphs"),
344 }
345 }
346
347 pub fn add_child(&mut self, child: Paragraph) {
349 self.children_mut().push(child);
350 }
351
352 pub fn entries(&self) -> &[Vec<Paragraph>] {
354 match self {
355 Paragraph::OrderedList { entries } | Paragraph::UnorderedList { entries } => entries,
356 _ => &[],
357 }
358 }
359
360 pub fn entries_mut(&mut self) -> &mut Vec<Vec<Paragraph>> {
362 match self {
363 Paragraph::OrderedList { entries } | Paragraph::UnorderedList { entries } => entries,
364 _ => panic!("only list paragraphs can hold entries"),
365 }
366 }
367
368 pub fn with_entries(self, entries: Vec<Vec<Paragraph>>) -> Self {
370 match self {
371 Paragraph::OrderedList { .. } => Paragraph::OrderedList { entries },
372 Paragraph::UnorderedList { .. } => Paragraph::UnorderedList { entries },
373 _ => panic!("only list paragraphs can hold entries"),
374 }
375 }
376
377 pub fn add_list_item(&mut self, item: Vec<Paragraph>) {
379 self.entries_mut().push(item);
380 }
381
382 pub fn checklist_items(&self) -> &[ChecklistItem] {
384 match self {
385 Paragraph::Checklist { items } => items,
386 _ => &[],
387 }
388 }
389
390 pub fn checklist_items_mut(&mut self) -> &mut Vec<ChecklistItem> {
392 match self {
393 Paragraph::Checklist { items } => items,
394 _ => panic!("only checklist paragraphs can hold checklist items"),
395 }
396 }
397
398 pub fn with_checklist_items(self, items: Vec<ChecklistItem>) -> Self {
400 match self {
401 Paragraph::Checklist { .. } => Paragraph::Checklist { items },
402 _ => panic!("only checklist paragraphs can hold checklist items"),
403 }
404 }
405
406 pub fn add_checklist_item(&mut self, item: ChecklistItem) {
408 self.checklist_items_mut().push(item);
409 }
410
411 pub fn rows(&self) -> &[TableRow] {
413 match self {
414 Paragraph::Table { rows } => rows,
415 _ => &[],
416 }
417 }
418
419 pub fn rows_mut(&mut self) -> &mut Vec<TableRow> {
421 match self {
422 Paragraph::Table { rows } => rows,
423 _ => panic!("only table paragraphs can hold rows"),
424 }
425 }
426
427 pub fn with_rows(self, rows: Vec<TableRow>) -> Self {
429 match self {
430 Paragraph::Table { .. } => Paragraph::Table { rows },
431 _ => panic!("only table paragraphs can hold rows"),
432 }
433 }
434
435 pub fn add_row(&mut self, row: TableRow) {
437 self.rows_mut().push(row);
438 }
439
440 pub fn definition_items(&self) -> &[DefinitionItem] {
442 match self {
443 Paragraph::DefinitionList { items } => items,
444 _ => &[],
445 }
446 }
447
448 pub fn definition_items_mut(&mut self) -> &mut Vec<DefinitionItem> {
450 match self {
451 Paragraph::DefinitionList { items } => items,
452 _ => panic!("only definition-list paragraphs can hold definition items"),
453 }
454 }
455
456 pub fn with_definition_items(self, items: Vec<DefinitionItem>) -> Self {
458 match self {
459 Paragraph::DefinitionList { .. } => Paragraph::DefinitionList { items },
460 _ => panic!("only definition-list paragraphs can hold definition items"),
461 }
462 }
463
464 pub fn add_definition_item(&mut self, item: DefinitionItem) {
466 self.definition_items_mut().push(item);
467 }
468}
469
470#[derive(Debug, Clone, PartialEq, Default)]
471pub struct TableRow {
476 pub cells: Vec<TableCell>,
477}
478
479impl TableRow {
480 pub fn new() -> Self {
482 Self::default()
483 }
484
485 pub fn with_cells(mut self, cells: Vec<TableCell>) -> Self {
487 self.cells = cells;
488 self
489 }
490
491 pub fn add_cell(&mut self, cell: TableCell) {
493 self.cells.push(cell);
494 }
495}
496
497#[derive(Debug, Clone, PartialEq)]
498pub struct TableCell {
504 pub is_header: bool,
505 pub content: Vec<Span>,
506}
507
508impl TableCell {
509 pub fn new(is_header: bool) -> Self {
511 Self {
512 is_header,
513 content: Vec::new(),
514 }
515 }
516
517 pub fn new_data() -> Self {
519 Self::new(false)
520 }
521
522 pub fn new_header() -> Self {
524 Self::new(true)
525 }
526
527 pub fn with_content(mut self, content: Vec<Span>) -> Self {
529 self.content = content;
530 self
531 }
532}
533
534#[derive(Debug, Clone, PartialEq)]
535pub struct ChecklistItem {
541 pub checked: bool,
542 pub content: Vec<Span>,
543 pub children: Vec<ChecklistItem>,
544}
545
546impl ChecklistItem {
547 pub fn new(checked: bool) -> Self {
549 Self {
550 checked,
551 content: Vec::new(),
552 children: Vec::new(),
553 }
554 }
555
556 pub fn with_content(mut self, content: Vec<Span>) -> Self {
558 self.content = content;
559 self
560 }
561
562 pub fn with_children(mut self, children: Vec<ChecklistItem>) -> Self {
564 self.children = children;
565 self
566 }
567
568 pub fn add_child(&mut self, child: ChecklistItem) {
570 self.children.push(child);
571 }
572}
573
574#[derive(Debug, Clone, PartialEq, Default)]
575pub struct DefinitionItem {
585 pub terms: Vec<Vec<Span>>,
587 pub definition: Vec<Paragraph>,
590}
591
592impl DefinitionItem {
593 pub fn new() -> Self {
595 Self::default()
596 }
597
598 pub fn with_terms(mut self, terms: Vec<Vec<Span>>) -> Self {
600 self.terms = terms;
601 self
602 }
603
604 pub fn with_definition(mut self, definition: Vec<Paragraph>) -> Self {
606 self.definition = definition;
607 self
608 }
609
610 pub fn add_term(&mut self, term: Vec<Span>) {
612 self.terms.push(term);
613 }
614
615 pub fn add_definition_paragraph(&mut self, paragraph: Paragraph) {
617 self.definition.push(paragraph);
618 }
619}
620
621#[cfg(test)]
622mod tests {
623 use super::*;
624
625 #[test]
626 fn test_paragraph_type_display() {
627 assert_eq!(format!("{}", ParagraphType::Text), "Text");
628 assert_eq!(format!("{}", ParagraphType::Header1), "Header Lvl 1");
629 }
630
631 #[test]
632 fn test_html_tag_conversion() {
633 assert_eq!(ParagraphType::Text.html_tag(), "p");
634 assert_eq!(ParagraphType::from_html_tag("p"), Some(ParagraphType::Text));
635 assert_eq!(ParagraphType::CodeBlock.html_tag(), "pre");
636 assert_eq!(
637 ParagraphType::from_html_tag("pre"),
638 Some(ParagraphType::CodeBlock)
639 );
640 assert_eq!(ParagraphType::from_html_tag("div"), None);
641 }
642
643 #[test]
644 fn test_is_leaf() {
645 assert!(ParagraphType::Text.is_leaf());
646 assert!(ParagraphType::Header1.is_leaf());
647 assert!(ParagraphType::CodeBlock.is_leaf());
648 assert!(!ParagraphType::OrderedList.is_leaf());
649 assert!(!ParagraphType::Quote.is_leaf());
650 }
651
652 #[test]
653 fn test_paragraph_creation() {
654 let p = Paragraph::new_text().with_content(vec![Span::new_text("Hello")]);
655
656 assert_eq!(p.paragraph_type(), ParagraphType::Text);
657 assert_eq!(p.content().len(), 1);
658 assert_eq!(p.content()[0].text, "Hello");
659 }
660}