1use std::fmt::Display;
2
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use super::Anchor;
7
8#[derive(Debug, PartialEq, Clone, Eq)]
9#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
10#[cfg_attr(feature = "serde", serde(tag = "type", content = "value"))]
11pub enum HeadingNodes {
12 Text(String),
13 Anchor(Anchor),
14}
15
16impl From<String> for HeadingNodes {
17 fn from(text: String) -> Self {
18 Self::Text(text)
19 }
20}
21
22impl From<Anchor> for HeadingNodes {
23 fn from(anchor: Anchor) -> Self {
24 Self::Anchor(anchor)
25 }
26}
27
28impl Display for HeadingNodes {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 match self {
31 HeadingNodes::Text(text) => {
32 write!(f, "{}", text.replace("\n\n", "\\\n\n").replace("#", "\\#"))
33 }
34 HeadingNodes::Anchor(anchor) => write!(f, "{}", anchor),
35 }
36 }
37}
38
39#[derive(Debug, PartialEq, Clone, Eq)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65pub struct Heading {
66 pub level: u8,
67 pub body: Vec<HeadingNodes>,
68}
69
70impl Heading {
71 pub fn new(level: u8, nodes: Vec<HeadingNodes>) -> Self {
72 Self { level, body: nodes }
73 }
74}
75
76impl Display for Heading {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 write!(f, "{} ", "#".repeat(self.level as usize))?;
79 for node in &self.body {
80 write!(f, "{}", node)?;
81 }
82 Ok(())
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use crate::nodes::{Anchor, Heading, HeadingNodes};
89
90 #[test]
91 fn heading() {
92 let heading = Heading::new(
93 3,
94 vec![
95 HeadingNodes::from("Header can contain an ".to_string()),
96 HeadingNodes::from(Anchor::new("anchor".to_string(), "#".to_string())),
97 HeadingNodes::from(" or regular text.".to_string()),
98 ],
99 );
100 assert_eq!(
101 heading.to_string(),
102 "### Header can contain an [anchor](#) or regular text."
103 );
104 }
105
106 #[test]
107 fn heading_with_hash() {
108 let heading = Heading::new(3, vec![HeadingNodes::from("# ##".to_string())]);
109 assert_eq!(heading.to_string(), "### \\# \\#\\#");
110 }
111}