diff --git a/readme.md b/readme.md index a24a659..6751370 100644 --- a/readme.md +++ b/readme.md @@ -5,7 +5,7 @@ ## Status -It is ready to poke around. There is no significant API changes expected. +It is not ready to poke around. There is significant API changes expected. ## Why? diff --git a/src/lib.rs b/src/lib.rs index 449d942..96abb23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,14 +220,14 @@ mod tests { #[test] fn test_deserialize() { let input = "# header"; - let expected = Yamd::new_with_nodes(None, vec![Heading::new(true, "header", 1).into()]); + let expected = Yamd::new(None, vec![Heading::new("header", 1).into()]); let actual = deserialize(input).unwrap(); assert_eq!(expected, actual); } #[test] fn test_serialize() { - let input = Yamd::new_with_nodes(None, vec![Heading::new(true, "header", 1).into()]); + let input = Yamd::new(None, vec![Heading::new("header", 1).into()]); let expected = "# header"; let actual = serialize(&input); assert_eq!(expected, actual); @@ -236,11 +236,11 @@ mod tests { #[test] fn deserialize_text_containing_utf8() { let input = "## 🤔\n\n[link 😉](url)"; - let expected = Yamd::new_with_nodes( + let expected = Yamd::new( None, vec![ - Heading::new(false, "🤔", 2).into(), - Paragraph::new_with_nodes(true, vec![Anchor::new("link 😉", "url").into()]).into(), + Heading::new("🤔", 2).into(), + Paragraph::new(vec![Anchor::new("link 😉", "url").into()]).into(), ], ); let actual = deserialize(input).unwrap(); diff --git a/src/nodes/accordion.rs b/src/nodes/accordion.rs index 72dba38..b0fe599 100644 --- a/src/nodes/accordion.rs +++ b/src/nodes/accordion.rs @@ -41,21 +41,18 @@ impl From for AccordionNodes { #[derive(Debug, PartialEq, Serialize, Clone)] pub struct Accordion { - #[serde(skip_serializing)] - consumed_all_input: bool, pub nodes: Vec, } impl Accordion { - pub fn new(consumed_all_input: bool) -> Self { - Self::new_with_nodes(consumed_all_input, vec![]) + pub fn new(nodes: Vec) -> Self { + Accordion { nodes } } +} - pub fn new_with_nodes(consumed_all_input: bool, nodes: Vec) -> Self { - Accordion { - consumed_all_input, - nodes, - } +impl Default for Accordion { + fn default() -> Self { + Accordion::new(vec![]) } } @@ -63,21 +60,27 @@ impl Display for Accordion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "///\n{nodes}\n\\\\\\{end}", + "///\n{nodes}\n\\\\\\", nodes = self .nodes .iter() .map(|n| n.to_string()) .collect::>() - .join(""), - end = if self.consumed_all_input { "" } else { "\n\n" } + .join("\n"), ) } } impl Node for Accordion { fn len(&self) -> usize { - self.nodes.iter().map(|n| n.len()).sum::() + self.get_outer_token_length() + let delimiter_len = if self.nodes.is_empty() { + 0 + } else { + self.nodes.len() - 1 + }; + self.nodes.iter().map(|n| n.len()).sum::() + + self.get_outer_token_length() + + delimiter_len } } @@ -95,7 +98,7 @@ impl Branch for Accordion { } fn get_outer_token_length(&self) -> usize { - 8 + if self.consumed_all_input { 0 } else { 2 } + 8 } } @@ -107,8 +110,7 @@ impl Deserializer for Accordion { let mut matcher = Matcher::new(input); if let Some(accordion) = matcher.get_match("///\n", "\n\\\\\\", false) { - let consumed_all_input = matcher.get_match("\n\n", "", false).is_none(); - return Self::parse_branch(accordion.body, Self::new(consumed_all_input)); + return Self::parse_branch(accordion.body, "\n", Self::default()); } None } @@ -126,7 +128,7 @@ mod test { #[test] fn test_deserialize_empty() { let input = "///\n\n\\\\\\"; - assert_eq!(Accordion::deserialize(input), Some(Accordion::new(true))); + assert_eq!(Accordion::deserialize(input), Some(Accordion::default())); } #[test] @@ -143,22 +145,13 @@ mod test { \\\"#; assert_eq!( Accordion::deserialize(input), - Some(Accordion::new_with_nodes( - true, - vec![ - AccordionTab::new(false, Some("header")).into(), - AccordionTab::new(true, Some("one more")).into() - ] - )) + Some(Accordion::new(vec![ + AccordionTab::new(Some("header"), vec![]).into(), + AccordionTab::new(Some("one more"), vec![]).into() + ])) ); } - #[test] - fn consumed_all_input() { - let input = "///\n\n\\\\\\\n\n"; - assert_eq!(Accordion::deserialize(input), Some(Accordion::new(false))); - } - #[test] fn test_len() { let input = r#"/// @@ -180,28 +173,22 @@ mod test { Accordion::deserialize( "///\n//\n/ header\n///\n//\n/ hi from nested\ncontent\n\\\\\n\\\\\\\n\\\\\n\\\\\\" ), - Some(Accordion::new_with_nodes( - true, - vec![AccordionTab::new_with_nodes( - true, - Some("header"), - vec![Accordion::new_with_nodes( - true, - vec![AccordionTab::new_with_nodes( - true, - Some("hi from nested"), - vec![Paragraph::new_with_nodes( - true, - vec![Text::new("content").into()] - ) - .into()] - ) - .into()] - ) - .into()] + Some(Accordion::new(vec![AccordionTab::new( + Some("header"), + vec![Accordion::new(vec![AccordionTab::new( + Some("hi from nested"), + vec![Paragraph::new(vec![Text::new("content").into()]).into()] ) + .into()]) .into()] - )) + ) + .into()])) ); } + + #[test] + fn empty_accordion() { + let accordion = Accordion::default(); + assert_eq!(accordion.len(), 8); + } } diff --git a/src/nodes/accordion_tab.rs b/src/nodes/accordion_tab.rs index 2d1056a..34d1bd0 100644 --- a/src/nodes/accordion_tab.rs +++ b/src/nodes/accordion_tab.rs @@ -118,22 +118,12 @@ impl From for AccordionTabNodes { pub struct AccordionTab { pub header: Option, pub nodes: Vec, - #[serde(skip_serializing)] - consumed_all_input: bool, } impl AccordionTab { - pub fn new>(consumed_all_input: bool, header: Option) -> Self { - Self::new_with_nodes(consumed_all_input, header, vec![]) - } - pub fn new_with_nodes>( - consumed_all_input: bool, - header: Option, - nodes: Vec, - ) -> Self { + pub fn new>(header: Option, nodes: Vec) -> Self { Self { nodes, - consumed_all_input, header: header.map(|s| s.into()), } } @@ -143,7 +133,7 @@ impl Display for AccordionTab { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "//\n{header}{nodes}\n\\\\{end}", + "//\n{header}{nodes}\n\\\\", header = self .header .as_ref() @@ -153,15 +143,21 @@ impl Display for AccordionTab { .iter() .map(|node| node.to_string()) .collect::>() - .join(""), - end = if self.consumed_all_input { "" } else { "\n" } + .join("\n\n"), ) } } impl Node for AccordionTab { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + let delimeter_len = if self.nodes.is_empty() { + 0 + } else { + (self.nodes.len() - 1) * 2 + }; + self.nodes.iter().map(|node| node.len()).sum::() + + delimeter_len + + self.get_outer_token_length() } } @@ -189,7 +185,6 @@ impl Branch for AccordionTab { fn get_outer_token_length(&self) -> usize { 6 + self.header.as_ref().map_or(0, |header| header.len() + 3) - + if self.consumed_all_input { 0 } else { 1 } } } @@ -202,11 +197,7 @@ impl Deserializer for AccordionTab { .get_match("/ ", "\n", false) .map(|header| header.body); - let consumed_all_input = matcher.get_match("\n", "", false).is_none(); - return Self::parse_branch( - inner_matcher.get_rest(), - Self::new(consumed_all_input, header), - ); + return Self::parse_branch(inner_matcher.get_rest(), "\n\n", Self::new(header, vec![])); } None } @@ -230,10 +221,9 @@ mod cfg { fn test_accordion_tab_deserialize() { assert_eq!( AccordionTab::deserialize("//\n/ Header\n# Heading\n\\\\\n\n"), - Some(AccordionTab::new_with_nodes( - false, + Some(AccordionTab::new( Some("Header"), - vec![Heading::new(true, "Heading", 1).into()] + vec![Heading::new("Heading", 1).into()] )) ); } @@ -242,13 +232,9 @@ mod cfg { fn test_accordion_tab_deserialize_with_no_header() { assert_eq!( AccordionTab::deserialize("//\nI am regular text\n\\\\\n\n"), - Some(AccordionTab::new_with_nodes::<&str>( - false, + Some(AccordionTab::new::<&str>( None, - vec![ - Paragraph::new_with_nodes(true, vec![Text::new("I am regular text").into()]) - .into() - ] + vec![Paragraph::new(vec![Text::new("I am regular text").into()]).into()] )) ); } @@ -256,11 +242,10 @@ mod cfg { #[test] fn test_accordion_tab_deserialize_with_no_header_and_no_newline() { assert_eq!( - AccordionTab::deserialize("//\n![alt](url)\n\n\\\\"), - Some(AccordionTab::new_with_nodes::<&str>( - true, + AccordionTab::deserialize("//\n![alt](url)\n\\\\"), + Some(AccordionTab::new::<&str>( None, - vec![Image::new(true, "alt", "url").into()] + vec![Image::new("alt", "url").into()] )) ); } @@ -268,37 +253,17 @@ mod cfg { #[test] fn test_accordion_tab_len() { assert_eq!( - AccordionTab::new_with_nodes( - false, - Some("Header"), - vec![Heading::new(true, "Heading", 1).into()] - ) - .len(), - 25 - ); - assert_eq!( - AccordionTab::new_with_nodes( - true, - Some("Header"), - vec![Heading::new(true, "Heading", 1).into()] - ) - .len(), + AccordionTab::new(Some("Header"), vec![Heading::new("Heading", 1).into()]).len(), 24 ); - assert_eq!(AccordionTab::new(true, Some("Header")).len(), 15); - assert_eq!(AccordionTab::new(false, Some("Header")).len(), 16); + assert_eq!(AccordionTab::new(Some("Header"), vec![]).len(), 15); } #[test] fn test_accordion_tab_serialize() { assert_eq!( - AccordionTab::new_with_nodes( - false, - Some("Header"), - vec![Heading::new(true, "Heading", 1).into()] - ) - .to_string(), - "//\n/ Header\n# Heading\n\\\\\n" + AccordionTab::new(Some("Header"), vec![Heading::new("Heading", 1).into()]).to_string(), + "//\n/ Header\n# Heading\n\\\\" ); } @@ -322,8 +287,8 @@ t**b** ![a](u) !!! -![a](u) ![a2](u2) +![a3](u3) !!! ----- @@ -335,49 +300,37 @@ t**b** {{cloudinary_gallery|cloud_name&tag}} \\"#; - let tab = AccordionTab::new_with_nodes( - true, + let tab = AccordionTab::new( Some("Header"), vec![ - Heading::new(false, "hello", 1).into(), - Code::new(false, "rust", "let a=1;").into(), - Paragraph::new_with_nodes( - false, - vec![ - Text::new("t").into(), - Bold::new_with_nodes(vec![Text::new("b").into()]).into(), - ], - ) + Heading::new("hello", 1).into(), + Code::new("rust", "let a=1;").into(), + Paragraph::new(vec![ + Text::new("t").into(), + Bold::new(vec![Text::new("b").into()]).into(), + ]) .into(), - Image::new(false, 'a', 'u').into(), - ImageGallery::new_with_nodes( - false, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into(), - ], - ) + Image::new('a', 'u').into(), + ImageGallery::new(vec![ + Image::new("a2", "u2").into(), + Image::new("a3", "u3").into(), + ]) .into(), - Divider::new(false).into(), - List::new_with_nodes( - false, + Divider::new().into(), + List::new( Unordered, 0, vec![ListItem::new_with_nested_list( Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("one").into()]), - Some(List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("one").into()]), + Some(List::new( Unordered, 1, vec![ListItem::new( Unordered, 1, - ListItemContent::new_with_nodes( - true, - vec![Text::new("two").into()], - ), + ListItemContent::new(vec![Text::new("two").into()]), ) .into()], )), @@ -385,11 +338,17 @@ t**b** .into()], ) .into(), - Embed::new("youtube", "123", false).into(), - Embed::new("cloudinary_gallery", "cloud_name&tag", true).into(), + Embed::new("youtube", "123").into(), + Embed::new("cloudinary_gallery", "cloud_name&tag").into(), ], ); assert_eq!(tab.to_string(), input); assert_eq!(AccordionTab::deserialize(input), Some(tab)); } + + #[test] + fn empty_tab() { + let tab = AccordionTab::new::<&str>(None, vec![]); + assert_eq!(tab.len(), 6); + } } diff --git a/src/nodes/bold.rs b/src/nodes/bold.rs index 446e09c..5c91364 100644 --- a/src/nodes/bold.rs +++ b/src/nodes/bold.rs @@ -66,11 +66,7 @@ pub struct Bold { } impl Bold { - pub fn new() -> Self { - Self::new_with_nodes(vec![]) - } - - pub fn new_with_nodes(nodes: Vec) -> Self { + pub fn new(nodes: Vec) -> Self { Self { nodes } } } @@ -87,6 +83,7 @@ impl Branch for Bold { fn get_fallback_node() -> Option> { Some(Box::new(|str| Text::new(str).into())) } + fn get_outer_token_length(&self) -> usize { 4 } @@ -108,7 +105,7 @@ impl Display for Bold { impl Node for Bold { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + self.nodes.iter().map(|node| node.len()).sum::() + 4 } } @@ -116,7 +113,7 @@ impl Deserializer for Bold { fn deserialize_with_context(input: &str, _: Option) -> Option { let mut matcher = Matcher::new(input); if let Some(bold) = matcher.get_match("**", "**", false) { - return Self::parse_branch(bold.body, Self::new()); + return Self::parse_branch(bold.body, "", Self::default()); } None } @@ -138,7 +135,7 @@ mod tests { #[test] fn only_text() { - let mut b = Bold::new(); + let mut b = Bold::default(); b.push(Text::new("B as bold")); let str = b.to_string(); assert_eq!(str, "**B as bold**".to_string()); @@ -146,7 +143,7 @@ mod tests { #[test] fn from_vec() { - let b: String = Bold::new_with_nodes(vec![ + let b: String = Bold::new(vec![ Text::new("B as bold ").into(), Italic::new("Italic").into(), Strikethrough::new("Strikethrough").into(), @@ -159,12 +156,12 @@ mod tests { fn from_string() { assert_eq!( Bold::deserialize("**b**"), - Some(Bold::new_with_nodes(vec![Text::new("b").into()])) + Some(Bold::new(vec![Text::new("b").into()])) ); assert_eq!( Bold::deserialize("**b ~~st~~ _i t_**"), - Some(Bold::new_with_nodes(vec![ + Some(Bold::new(vec![ Text::new("b ").into(), Strikethrough::new("st").into(), Text::new(" ").into(), @@ -175,15 +172,21 @@ mod tests { #[test] fn len() { - assert_eq!(Bold::new_with_nodes(vec![Text::new("T").into()]).len(), 5); + assert_eq!(Bold::new(vec![Text::new("T").into()]).len(), 5); assert_eq!( - Bold::new_with_nodes(vec![Text::new("T").into(), Strikethrough::new("S").into()]).len(), + Bold::new(vec![Text::new("T").into(), Strikethrough::new("S").into()]).len(), 10 ); } #[test] fn default() { - assert_eq!(Bold::default(), Bold::new()); + assert_eq!(Bold::default(), Bold::default()); + } + + #[test] + fn empty_bold() { + let b = Bold::new(vec![]); + assert_eq!(b.len(), 4); } } diff --git a/src/nodes/code.rs b/src/nodes/code.rs index ef9125a..f82c24c 100644 --- a/src/nodes/code.rs +++ b/src/nodes/code.rs @@ -8,31 +8,26 @@ use crate::toolkit::{context::Context, deserializer::Deserializer, matcher::Matc pub struct Code { pub lang: String, pub code: String, - #[serde(skip_serializing)] - consumed_all_input: bool, } impl Code { - pub fn new>(consumed_all_input: bool, lang: S, code: S) -> Self { + pub fn new>(lang: S, code: S) -> Self { Self { lang: lang.into(), code: code.into(), - consumed_all_input, } } } impl Display for Code { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let end = if self.consumed_all_input { "" } else { "\n\n" }; - write!(f, "```{}\n{}\n```{}", self.lang, self.code, end) + write!(f, "```{}\n{}\n```", self.lang, self.code) } } impl Node for Code { fn len(&self) -> usize { - let end = if self.consumed_all_input { 0 } else { 2 }; - self.lang.len() + self.code.len() + 8 + end + self.lang.len() + self.code.len() + 8 } } @@ -41,8 +36,7 @@ impl Deserializer for Code { let mut matcher = Matcher::new(input); if let Some(lang) = matcher.get_match("```", "\n", false) { if let Some(code) = matcher.get_match("", "\n```", false) { - let consumed_all_input = matcher.get_match("\n\n", "", false).is_none(); - return Some(Self::new(consumed_all_input, lang.body, code.body)); + return Some(Self::new(lang.body, code.body)); } } None @@ -60,30 +54,25 @@ mod tests { #[test] fn serialize() { assert_eq!( - Code::new(true, "rust", "let foo:usize=1;").to_string(), + Code::new("rust", "let foo:usize=1;").to_string(), String::from("```rust\nlet foo:usize=1;\n```") ); - assert_eq!( - Code::new(false, "rust", "let foo:usize=1;").to_string(), - String::from("```rust\nlet foo:usize=1;\n```\n\n") - ); } #[test] fn len() { - assert_eq!(Code::new(true, 'r', 'b').len(), 10); - assert_eq!(Code::new(false, 'r', 'b').len(), 12); + assert_eq!(Code::new('r', 'b').len(), 10); } #[test] fn deserializer() { assert_eq!( Code::deserialize("```rust\nlet a=1;\n```"), - Some(Code::new(true, "rust", "let a=1;")) + Some(Code::new("rust", "let a=1;")) ); assert_eq!( Code::deserialize("```rust\nlet a=1;\n```\n\n"), - Some(Code::new(false, "rust", "let a=1;")) + Some(Code::new("rust", "let a=1;")) ); assert_eq!(Code::deserialize("```rust\nlet a=1;\n"), None); } diff --git a/src/nodes/divider.rs b/src/nodes/divider.rs index 074d8fe..8155de0 100644 --- a/src/nodes/divider.rs +++ b/src/nodes/divider.rs @@ -4,42 +4,32 @@ use serde::Serialize; use crate::toolkit::{context::Context, deserializer::Deserializer, matcher::Matcher, node::Node}; -#[derive(Debug, PartialEq, Serialize, Clone)] -pub struct Divider { - #[serde(skip_serializing)] - consumed_all_input: bool, -} +#[derive(Debug, PartialEq, Serialize, Clone, Default)] +pub struct Divider {} impl Divider { - pub fn new(consumed_all_input: bool) -> Self { - Self { consumed_all_input } + pub fn new() -> Self { + Self {} } } impl Display for Divider { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let end = if self.consumed_all_input { "" } else { "\n\n" }; - write!(f, "-----{end}") + write!(f, "-----") } } impl Node for Divider { fn len(&self) -> usize { - if self.consumed_all_input { - 5 - } else { - 7 - } + 5 } } impl Deserializer for Divider { fn deserialize_with_context(input: &str, _: Option) -> Option { let mut matcher = Matcher::new(input); - if let Some(divider) = matcher.get_match("-----", "\n\n", true) { - return Some(Divider { - consumed_all_input: divider.end_token.is_empty(), - }); + if matcher.get_match("-----", "\n\n", true).is_some() { + return Some(Divider {}); } None } @@ -55,30 +45,17 @@ mod tests { #[test] fn deserialize() { - assert_eq!( - Divider::deserialize("-----"), - Some(Divider { - consumed_all_input: true - }) - ); - assert_eq!( - Divider::deserialize("-----\n\n"), - Some(Divider { - consumed_all_input: false - }) - ); + assert_eq!(Divider::deserialize("-----"), Some(Divider {})); assert_eq!(Divider::deserialize("----\n\n"), None); } #[test] fn len() { - assert_eq!(Divider::new(true).len(), 5); - assert_eq!(Divider::new(false).len(), 7); + assert_eq!(Divider::new().len(), 5); } #[test] fn serialize() { - assert_eq!(Divider::new(true).to_string(), String::from("-----")); - assert_eq!(Divider::new(false).to_string(), String::from("-----\n\n")); + assert_eq!(Divider::new().to_string(), String::from("-----")); } } diff --git a/src/nodes/embed.rs b/src/nodes/embed.rs index b864808..e7e2840 100644 --- a/src/nodes/embed.rs +++ b/src/nodes/embed.rs @@ -8,31 +8,26 @@ use crate::toolkit::{deserializer::Deserializer, matcher::Matcher, node::Node}; pub struct Embed { pub args: String, pub kind: String, - #[serde(skip_serializing)] - pub consumed_all_input: bool, } impl Embed { - pub fn new>(kind: S, args: S, consumed_all_input: bool) -> Self { + pub fn new>(kind: S, args: S) -> Self { Self { kind: kind.into(), args: args.into(), - consumed_all_input, } } } impl Display for Embed { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let end = if self.consumed_all_input { "" } else { "\n\n" }; - write!(f, "{{{{{}|{}}}}}{}", self.kind, self.args, end) + write!(f, "{{{{{}|{}}}}}", self.kind, self.args) } } impl Node for Embed { fn len(&self) -> usize { - let end = if self.consumed_all_input { 0 } else { 2 }; - 5 + self.kind.len() + self.args.len() + end + 5 + self.kind.len() + self.args.len() } } @@ -45,12 +40,7 @@ impl Deserializer for Embed { if let Some(embed) = matcher.get_match("{{", "}}", false) { let mut embed = embed.body.split('|'); if let (Some(kind), Some(args)) = (embed.next(), embed.next()) { - let consumed_all_input = matcher.get_match("\n\n", "", false).is_none(); - return Some(Self::new( - kind.to_string(), - args.to_string(), - consumed_all_input, - )); + return Some(Self::new(kind.to_string(), args.to_string())); } } None @@ -67,39 +57,18 @@ mod tests { #[test] fn serializer() { assert_eq!( - Embed::new( - "youtube", - "https://www.youtube.com/embed/wsfdjlkjsdf", - false, - ) - .to_string(), - "{{youtube|https://www.youtube.com/embed/wsfdjlkjsdf}}\n\n" - ); - assert_eq!( - Embed::new("youtube", "https://www.youtube.com/embed/wsfdjlkjsdf", true).to_string(), + Embed::new("youtube", "https://www.youtube.com/embed/wsfdjlkjsdf",).to_string(), "{{youtube|https://www.youtube.com/embed/wsfdjlkjsdf}}" ); } #[test] fn len() { - assert_eq!(Embed::new("y", "h", false,).len(), 9); - assert_eq!(Embed::new("y", "h", true).len(), 7); + assert_eq!(Embed::new("y", "h",).len(), 7); } #[test] fn deserialize() { - assert_eq!( - Embed::deserialize_with_context( - "{{youtube|https://www.youtube.com/embed/wsfdjlkjsdf}}\n\n", - None - ), - Some(Embed::new( - "youtube", - "https://www.youtube.com/embed/wsfdjlkjsdf", - false, - )) - ); assert_eq!( Embed::deserialize_with_context( "{{youtube|https://www.youtube.com/embed/wsfdjlkjsdf}}", @@ -108,7 +77,6 @@ mod tests { Some(Embed::new( "youtube", "https://www.youtube.com/embed/wsfdjlkjsdf", - true, )) ); } diff --git a/src/nodes/heading.rs b/src/nodes/heading.rs index dd39194..8ca262d 100644 --- a/src/nodes/heading.rs +++ b/src/nodes/heading.rs @@ -8,12 +8,10 @@ use crate::toolkit::{context::Context, deserializer::Deserializer, matcher::Matc pub struct Heading { pub level: u8, pub text: String, - #[serde(skip_serializing)] - consumed_all_input: bool, } impl Heading { - pub fn new>(consumed_all_input: bool, text: S, level: u8) -> Self { + pub fn new>(text: S, level: u8) -> Self { let normalized_level = match level { 0 => 1, 7.. => 6, @@ -22,7 +20,6 @@ impl Heading { Heading { text: text.into(), level: normalized_level, - consumed_all_input, } } } @@ -35,7 +32,6 @@ impl Deserializer for Heading { let mut matcher = Matcher::new(input); if let Some(heading) = matcher.get_match(start_token, "\n\n", true) { return Some(Self::new( - heading.end_token.is_empty(), heading.body, (start_tokens.len() - i).try_into().unwrap_or(1), )); @@ -49,15 +45,13 @@ impl Deserializer for Heading { impl Display for Heading { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let level = String::from('#').repeat(self.level as usize); - let end = if self.consumed_all_input { "" } else { "\n\n" }; - write!(f, "{} {}{}", level, self.text, end) + write!(f, "{} {}", level, self.text) } } impl Node for Heading { fn len(&self) -> usize { - let end = if self.consumed_all_input { 0 } else { 2 }; - self.text.len() + self.level as usize + 1 + end + self.text.len() + self.level as usize + 1 } } @@ -69,27 +63,26 @@ mod tests { #[test] fn level_one() { - assert_eq!(Heading::new(true, "Header", 1).to_string(), "# Header"); - assert_eq!(Heading::new(false, "Header", 1).to_string(), "# Header\n\n"); + assert_eq!(Heading::new("Header", 1).to_string(), "# Header"); } #[test] fn level_gt_six() { - let h = Heading::new(true, "Header", 7).to_string(); + let h = Heading::new("Header", 7).to_string(); assert_eq!(h, "###### Header"); - let h = Heading::new(true, "Header", 34).to_string(); + let h = Heading::new("Header", 34).to_string(); assert_eq!(h, "###### Header"); } #[test] fn level_eq_zero() { - let h = Heading::new(true, "Header", 0).to_string(); + let h = Heading::new("Header", 0).to_string(); assert_eq!(h, "# Header"); } #[test] fn level_eq_four() { - let h = Heading::new(true, "Header", 4).to_string(); + let h = Heading::new("Header", 4).to_string(); assert_eq!(h, "#### Header"); } @@ -97,15 +90,15 @@ mod tests { fn from_string() { assert_eq!( Heading::deserialize("## Header"), - Some(Heading::new(true, "Header", 2)) + Some(Heading::new("Header", 2)) ); assert_eq!( Heading::deserialize("### Head"), - Some(Heading::new(true, "Head", 3)) + Some(Heading::new("Head", 3)) ); assert_eq!( Heading::deserialize("### Head\n\nsome other thing"), - Some(Heading::new(false, "Head", 3)) + Some(Heading::new("Head", 3)) ); assert_eq!(Heading::deserialize("not a header"), None); assert_eq!(Heading::deserialize("######"), None); @@ -114,8 +107,7 @@ mod tests { #[test] fn len() { - assert_eq!(Heading::new(true, "h", 1).len(), 3); - assert_eq!(Heading::new(true, "h", 2).len(), 4); - assert_eq!(Heading::new(false, "h", 2).len(), 6); + assert_eq!(Heading::new("h", 1).len(), 3); + assert_eq!(Heading::new("h", 2).len(), 4); } } diff --git a/src/nodes/highlight.rs b/src/nodes/highlight.rs index 38a546e..2cb206f 100644 --- a/src/nodes/highlight.rs +++ b/src/nodes/highlight.rs @@ -44,21 +44,10 @@ pub struct Highlight { pub header: Option, pub icon: Option, pub nodes: Vec, - #[serde(skip_serializing)] - consumed_all_input: bool, } impl Highlight { pub fn new, I: Into>( - consumed_all_input: bool, - header: Option, - icon: Option, - ) -> Self { - Self::new_with_nodes(consumed_all_input, header, icon, vec![]) - } - - pub fn new_with_nodes, I: Into>( - consumed_all_input: bool, header: Option, icon: Option, nodes: Vec, @@ -67,7 +56,6 @@ impl Highlight { header: header.map(|header| header.into()), icon: icon.map(|icon| icon.into()), nodes, - consumed_all_input, } } } @@ -82,25 +70,30 @@ impl Display for Highlight { Some(icon) => format!("> {icon}\n"), None => String::new(), }; - let end = if self.consumed_all_input { "" } else { "\n\n" }; write!( f, - ">>>\n{header}{icon}{}\n>>>{end}", + ">>>\n{header}{icon}{}\n>>>", self.nodes .iter() .map(|node| node.to_string()) .collect::>() - .join(""), + .join("\n\n"), header = header, icon = icon, - end = end ) } } impl Node for Highlight { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + let delimiter_length = if self.nodes.is_empty() { + 0 + } else { + (self.nodes.len() - 1) * 2 + }; + self.nodes.iter().map(|node| node.len()).sum::() + + delimiter_length + + self.get_outer_token_length() } } @@ -118,17 +111,8 @@ impl Branch for Highlight { } fn get_outer_token_length(&self) -> usize { - let header = match &self.header { - Some(header) => header.len() + 4, - None => 0, - }; - let icon = match &self.icon { - Some(icon) => icon.len() + 3, - None => 0, - }; - let end = if self.consumed_all_input { 0 } else { 2 }; - - 8 + icon + header + end + 8 + self.header.as_ref().map_or(0, |header| header.len() + 4) + + self.icon.as_ref().map_or(0, |icon| icon.len() + 3) } } @@ -142,12 +126,8 @@ impl Deserializer for Highlight { .map(|header| header.body); let icon = matcher.get_match("> ", "\n", false).map(|icon| icon.body); - let consumed_all_input = outer_matcher.get_match("\n", "", false).is_none(); - return Self::parse_branch( - matcher.get_rest(), - Self::new(consumed_all_input, header, icon), - ); + return Self::parse_branch(matcher.get_rest(), "\n\n", Self::new(header, icon, vec![])); } None @@ -165,85 +145,55 @@ mod tests { #[test] fn len() { assert_eq!( - Highlight::new_with_nodes( - true, + Highlight::new( Some("h"), Some("i"), vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t").into()]).into() + Paragraph::new(vec![Text::new("t").into()]).into(), + Paragraph::new(vec![Text::new("t").into()]).into() ] ) .len(), 21 ); assert_eq!( - Highlight::new_with_nodes( - false, - Some("h"), - Some("i"), - vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t").into()]).into() - ] - ) - .len(), - 23 - ); - assert_eq!( - Highlight::new_with_nodes::( - false, + Highlight::new::( None, None, vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t").into()]).into() + Paragraph::new(vec![Text::new("t").into()]).into(), + Paragraph::new(vec![Text::new("t").into()]).into() ] ) .len(), - 14 + 12 ); } #[test] fn serialize() { assert_eq!( - Highlight::new_with_nodes( - true, + Highlight::new( Some("h"), Some("i"), vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t").into()]).into() + Paragraph::new(vec![Text::new("t").into()]).into(), + Paragraph::new(vec![Text::new("t").into()]).into() ] ) .to_string(), String::from(">>>\n>> h\n> i\nt\n\nt\n>>>") ); assert_eq!( - Highlight::new_with_nodes( - false, - Some("h"), - Some("i"), - vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t").into()]).into() - ] - ) - .to_string(), - String::from(">>>\n>> h\n> i\nt\n\nt\n>>>\n\n") - ); - assert_eq!( - Highlight::new_with_nodes::( - false, + Highlight::new::( None, None, vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t").into()]).into() + Paragraph::new(vec![Text::new("t").into()]).into(), + Paragraph::new(vec![Text::new("t").into()]).into() ] ) .to_string(), - String::from(">>>\nt\n\nt\n>>>\n\n") + String::from(">>>\nt\n\nt\n>>>") ); } @@ -251,28 +201,32 @@ mod tests { fn deserialize() { assert_eq!( Highlight::deserialize(">>>\n>> h\n> i\nt\n\nt\n>>>"), - Some(Highlight::new_with_nodes( - true, + Some(Highlight::new( Some("h"), Some("i"), vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t").into()]).into() + Paragraph::new(vec![Text::new("t").into()]).into(), + Paragraph::new(vec![Text::new("t").into()]).into() ] )) ); assert_eq!( Highlight::deserialize(">>>\n>> h\n> i\nt\n\nt2\n>>>\n\n"), - Some(Highlight::new_with_nodes( - false, + Some(Highlight::new( Some("h"), Some("i"), vec![ - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("t2").into()]).into() + Paragraph::new(vec![Text::new("t").into()]).into(), + Paragraph::new(vec![Text::new("t2").into()]).into() ] )) ) } + + #[test] + fn empty_highlight() { + let highlight = Highlight::new::(None, None, vec![]); + assert_eq!(highlight.len(), 8); + } } diff --git a/src/nodes/image.rs b/src/nodes/image.rs index 0bcdd00..1a31fdd 100644 --- a/src/nodes/image.rs +++ b/src/nodes/image.rs @@ -8,36 +8,26 @@ use crate::toolkit::{context::Context, deserializer::Deserializer, matcher::Matc pub struct Image { pub alt: String, pub src: String, - #[serde(skip_serializing)] - consumed_all_input: bool, } impl Image { - pub fn new>(consumed_all_input: bool, alt: S, src: S) -> Self { + pub fn new>(alt: S, src: S) -> Self { Self { alt: alt.into(), src: src.into(), - consumed_all_input, } } } impl Display for Image { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let end = if self.consumed_all_input { - "\n" - } else { - "\n\n" - }; - - write!(f, "![{}]({}){}", self.alt, self.src, end) + write!(f, "![{}]({})", self.alt, self.src) } } impl Node for Image { fn len(&self) -> usize { - let end = if self.consumed_all_input { 1 } else { 2 }; - self.alt.len() + self.src.len() + 5 + end + self.alt.len() + self.src.len() + 5 } } @@ -45,9 +35,8 @@ impl Deserializer for Image { fn deserialize_with_context(input: &str, _: Option) -> Option { let mut matcher = Matcher::new(input); if let Some(alt) = matcher.get_match("![", "]", false) { - if let Some(url) = matcher.get_match("(", ")\n", false) { - let consumed_all_input = matcher.get_match("\n", "", false).is_none(); - return Some(Self::new(consumed_all_input, alt.body, url.body)); + if let Some(url) = matcher.get_match("(", ")", false) { + return Some(Self::new(alt.body, url.body)); } } None @@ -62,33 +51,20 @@ mod tests { #[test] fn serializer() { - assert_eq!( - Image::new(true, 'a', 'u').to_string(), - String::from("![a](u)\n") - ); - assert_eq!( - Image::new(false, 'a', 'u').to_string(), - String::from("![a](u)\n\n") - ) + assert_eq!(Image::new('a', 'u').to_string(), String::from("![a](u)")); } #[test] fn len() { - assert_eq!(Image::new(true, 'a', 'u').len(), 8); - assert_eq!(Image::new(false, 'a', 'u').len(), 9); + assert_eq!(Image::new('a', 'u').len(), 7); } #[test] fn deserializer() { assert_eq!( - Image::deserialize("![alt](url)\n"), - Some(Image::new(true, "alt", "url")) + Image::deserialize("![alt](url)"), + Some(Image::new("alt", "url")) ); - assert_eq!( - Image::deserialize("![alt](url)\n\n"), - Some(Image::new(false, "alt", "url")) - ); - assert_eq!(Image::deserialize("![alt](url"), None); assert_eq!(Image::deserialize("[alt](url)"), None); } diff --git a/src/nodes/image_gallery.rs b/src/nodes/image_gallery.rs index 4e6c24a..b6d27a3 100644 --- a/src/nodes/image_gallery.rs +++ b/src/nodes/image_gallery.rs @@ -44,52 +44,52 @@ impl From for ImageGalleryNodes { #[derive(Debug, PartialEq, Serialize, Clone)] pub struct ImageGallery { pub nodes: Vec, - #[serde(skip_serializing)] - consumed_all_input: bool, } impl ImageGallery { - pub fn new(consumed_all_input: bool) -> Self { - Self::new_with_nodes(consumed_all_input, vec![]) + pub fn new(nodes: Vec) -> Self { + Self { nodes } } +} - pub fn new_with_nodes(consumed_all_input: bool, nodes: Vec) -> Self { - Self { - nodes, - consumed_all_input, - } +impl Default for ImageGallery { + fn default() -> Self { + Self::new(vec![]) } } impl Display for ImageGallery { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let end = if self.consumed_all_input { "" } else { "\n\n" }; write!( f, - "!!!\n{}!!!{end}", + "!!!\n{}\n!!!", self.nodes .iter() .map(|node| node.to_string()) .collect::>() - .join("") + .join("\n") ) } } impl Node for ImageGallery { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + let delimiter_len = if self.nodes.is_empty() { + 0 + } else { + self.nodes.len() - 1 + }; + self.nodes.iter().map(|node| node.len()).sum::() + + delimiter_len + + self.get_outer_token_length() } } impl Deserializer for ImageGallery { fn deserialize_with_context(input: &str, _: Option) -> Option { let mut matcher = Matcher::new(input); - if let Some(image_gallery) = matcher.get_match("!!!\n", "!!!", false) { - return Self::parse_branch( - image_gallery.body, - Self::new(matcher.get_match("\n\n", "", false).is_none()), - ); + if let Some(image_gallery) = matcher.get_match("!!!\n", "\n!!!", false) { + return Self::parse_branch(image_gallery.body, "\n", Self::default()); } None } @@ -109,11 +109,7 @@ impl Branch for ImageGallery { } fn get_outer_token_length(&self) -> usize { - if self.consumed_all_input { - 7 - } else { - 9 - } + 8 } } @@ -129,76 +125,56 @@ mod tests { #[test] fn serialize() { assert_eq!( - ImageGallery::new_with_nodes( - true, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - ) + ImageGallery::new(vec![ + Image::new("a", "u").into(), + Image::new("a2", "u2").into() + ],) .to_string(), "!!!\n![a](u)\n![a2](u2)\n!!!" ); assert_eq!( - ImageGallery::new_with_nodes( - false, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - ) + ImageGallery::new(vec![ + Image::new("a", "u").into(), + Image::new("a2", "u2").into() + ],) .to_string(), - "!!!\n![a](u)\n![a2](u2)\n!!!\n\n" + "!!!\n![a](u)\n![a2](u2)\n!!!" ); } #[test] fn len() { assert_eq!( - ImageGallery::new_with_nodes( - true, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - ) + ImageGallery::new(vec![ + Image::new("a", "u").into(), + Image::new("a2", "u2").into() + ],) .len(), 25 ); - assert_eq!( - ImageGallery::new_with_nodes( - false, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - ) - .len(), - 27 - ); } #[test] fn deserialize() { assert_eq!( ImageGallery::deserialize("!!!\n![a](u)\n![a2](u2)\n!!!"), - Some(ImageGallery::new_with_nodes( - true, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - )) + Some(ImageGallery::new(vec![ + Image::new("a", "u").into(), + Image::new("a2", "u2").into() + ],)) ); assert_eq!( ImageGallery::deserialize("!!!\n![a](u)\n![a2](u2)\n!!!\n\n"), - Some(ImageGallery::new_with_nodes( - false, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - )) + Some(ImageGallery::new(vec![ + Image::new("a", "u").into(), + Image::new("a2", "u2").into() + ],)) ); } + + #[test] + fn empty_gallery() { + let gal = ImageGallery::new(vec![]); + assert_eq!(gal.len(), 8); + } } diff --git a/src/nodes/list.rs b/src/nodes/list.rs index 794e726..87e206c 100644 --- a/src/nodes/list.rs +++ b/src/nodes/list.rs @@ -50,26 +50,14 @@ pub struct List { pub list_type: ListTypes, pub level: usize, pub nodes: Vec, - #[serde(skip_serializing)] - consumed_all_input: bool, } impl List { - pub fn new(consumed_all_input: bool, list_type: ListTypes, level: usize) -> Self { - Self::new_with_nodes(consumed_all_input, list_type, level, vec![]) - } - - pub fn new_with_nodes( - consumed_all_input: bool, - list_type: ListTypes, - level: usize, - nodes: Vec, - ) -> Self { + pub fn new(list_type: ListTypes, level: usize, nodes: Vec) -> Self { Self { list_type, level, nodes, - consumed_all_input, } } @@ -98,22 +86,26 @@ impl List { impl Display for List { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let end = if self.consumed_all_input { "" } else { "\n\n" }; write!( f, - "{}{end}", + "{}", self.nodes .iter() .map(|node| node.to_string()) .collect::>() - .join("") + .join("\n") ) } } impl Node for List { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + let add = if self.nodes.is_empty() { + 0 + } else { + self.nodes.len() - 1 + }; + self.nodes.iter().map(|node| node.len()).sum::() + add } fn context(&self) -> Option { Some(Self::create_context(self.level, &self.list_type)) @@ -129,19 +121,18 @@ impl Deserializer for List { { return Self::parse_branch( &input[..unordered_list.start_token.len() + unordered_list.body.len()], - Self::new( - unordered_list.end_token.is_empty(), - ListTypes::Unordered, - level, - ), + "\n", + Self::new(ListTypes::Unordered, level, vec![]), ); } else if let Some(ordered_list) = matcher.get_match(format!("{}+ ", " ".repeat(level)).as_str(), "\n\n", true) { - return Self::parse_branch( + let res = Self::parse_branch( &input[..ordered_list.start_token.len() + ordered_list.body.len()], - Self::new(ordered_list.end_token.is_empty(), ListTypes::Ordered, level), + "\n", + Self::new(ListTypes::Ordered, level, vec![]), ); + return res; } None } @@ -161,11 +152,7 @@ impl Branch for List { } fn get_outer_token_length(&self) -> usize { - if self.consumed_all_input { - 0 - } else { - 2 - } + 0 } } @@ -184,24 +171,17 @@ mod tests { List { list_type: ListTypes::Unordered, level: 0, - consumed_all_input: true, nodes: vec![ ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes( - false, - vec![Text::new("unordered list item").into()], - ) + ListItemContent::new(vec![Text::new("unordered list item").into()],) ) .into(), ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes( - true, - vec![Text::new("unordered list item").into()], - ) + ListItemContent::new(vec![Text::new("unordered list item").into()],) ) .into(), ], @@ -213,56 +193,42 @@ mod tests { List { list_type: ListTypes::Unordered, level: 0, - consumed_all_input: false, nodes: vec![ ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes( - false, - vec![Text::new("unordered list item").into()], - ) + ListItemContent::new(vec![Text::new("unordered list item").into()],) ) .into(), ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes( - true, - vec![Text::new("unordered list item").into()], - ) + ListItemContent::new(vec![Text::new("unordered list item").into()],) ) .into(), ], } .to_string(), - "- unordered list item\n- unordered list item\n\n" + "- unordered list item\n- unordered list item" ); } #[test] fn serialize_ordered() { - let list = List::new_with_nodes( - true, + let list = List::new( ListTypes::Ordered, 0, vec![ ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes( - false, - vec![Text::new("ordered list item").into()], - ), + ListItemContent::new(vec![Text::new("ordered list item").into()]), ) .into(), ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes( - true, - vec![Text::new("ordered list item").into()], - ), + ListItemContent::new(vec![Text::new("ordered list item").into()]), ) .into(), ], @@ -286,21 +252,20 @@ mod tests { fn deserialize_unordered() { assert_eq!( List::deserialize("- level 0\n- level 0"), - Some(List::new_with_nodes( - true, + Some(List::new( ListTypes::Unordered, 0, vec![ ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ], @@ -308,21 +273,20 @@ mod tests { ); assert_eq!( List::deserialize("- level 0\n- level 0\n\n"), - Some(List::new_with_nodes( - false, + Some(List::new( ListTypes::Unordered, 0, vec![ ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ], @@ -334,21 +298,20 @@ mod tests { fn deserialize_ordered() { assert_eq!( List::deserialize("+ level 0\n+ level 0"), - Some(List::new_with_nodes( - true, + Some(List::new( ListTypes::Ordered, 0, vec![ ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ], @@ -356,21 +319,20 @@ mod tests { ); assert_eq!( List::deserialize("+ level 0\n+ level 0\n\n"), - Some(List::new_with_nodes( - false, + Some(List::new( ListTypes::Ordered, 0, vec![ ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("level 0").into()]) + ListItemContent::new(vec![Text::new("level 0").into()]) ) .into(), ], @@ -380,22 +342,20 @@ mod tests { #[test] fn deserialize_mixed() { - let list = List::new_with_nodes( - true, + let list = List::new( ListTypes::Ordered, 0, vec![ListItem::new_with_nested_list( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("level 0").into()]), - Some(List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("level 0").into()]), + Some(List::new( ListTypes::Unordered, 1, vec![ListItem::new( ListTypes::Unordered, 1, - ListItemContent::new_with_nodes(true, vec![Text::new("level 0").into()]), + ListItemContent::new(vec![Text::new("level 0").into()]), ) .into()], )), @@ -408,22 +368,20 @@ mod tests { #[test] fn deserialized_nested() { - let list = List::new_with_nodes( - false, + let list = List::new( ListTypes::Unordered, 0, vec![ListItem::new_with_nested_list( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("one").into()]).into(), - Some(List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("one").into()]).into(), + Some(List::new( ListTypes::Unordered, 1, vec![ListItem::new( ListTypes::Unordered, 1, - ListItemContent::new_with_nodes(true, vec![Text::new("two").into()]), + ListItemContent::new(vec![Text::new("two").into()]), ) .into()], )), @@ -440,48 +398,30 @@ mod tests { #[test] fn len() { - let list = List::new_with_nodes( - true, + let list = List::new( ListTypes::Ordered, 0, vec![ ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("l").into()]), + ListItemContent::new(vec![Text::new("l").into()]), ) .into(), ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("l").into()]), + ListItemContent::new(vec![Text::new("l").into()]), ) .into(), ], ); assert_eq!(list.len(), 7); - assert_eq!( - List::new_with_nodes( - false, - ListTypes::Ordered, - 0, - vec![ - ListItem::new( - ListTypes::Ordered, - 0, - ListItemContent::new_with_nodes(false, vec![Text::new("l").into()]) - ) - .into(), - ListItem::new( - ListTypes::Ordered, - 0, - ListItemContent::new_with_nodes(true, vec![Text::new("l").into()]) - ) - .into(), - ], - ) - .len(), - 9 - ); + } + + #[test] + fn empty_list() { + let list = List::new(ListTypes::Ordered, 0, vec![]); + assert_eq!(list.len(), 0); } } diff --git a/src/nodes/list_item.rs b/src/nodes/list_item.rs index 78ec360..bf63a0b 100644 --- a/src/nodes/list_item.rs +++ b/src/nodes/list_item.rs @@ -68,14 +68,17 @@ impl Display for ListItem { self.text, self.nested_list .as_ref() - .map_or("".to_string(), |list| list.to_string()) + .map_or("".to_string(), |list| format!("\n{}", list)) ) } } impl Node for ListItem { fn len(&self) -> usize { - self.nested_list.as_ref().map_or(0, |list| list.len()) + self.text.len() + self.level + 2 + self.nested_list.as_ref().map_or(0, |list| list.len() + 1) + + self.text.len() + + self.level + + 2 } } @@ -92,19 +95,13 @@ impl Deserializer for ListItem { format!("\n{}{} ", " ".repeat(level), list_type).as_str(), true, ) { - let content_body = if list_item.end_token.is_empty() { - list_item.body - } else { - &input[list_item.start_token.len() - ..list_item.start_token.len() + list_item.body.len() + 1] - }; - if let Some(text) = ListItemContent::deserialize(content_body) { + if let Some(text) = ListItemContent::deserialize(list_item.body) { let mut nested_list = None; - if text.len() < list_item.body.len() { + if text.len() + 1 < list_item.body.len() { let mut ctx = Context::new(); ctx.add("level", level); if let Some(list) = - List::deserialize_with_context(&list_item.body[text.len()..], Some(ctx)) + List::deserialize_with_context(&list_item.body[text.len() + 1..], Some(ctx)) { nested_list = Some(list); } else { @@ -142,7 +139,7 @@ mod tests { ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("test").into()]) + ListItemContent::new(vec![Text::new("test").into()]) ) .to_string(), "- test".to_string() @@ -152,7 +149,7 @@ mod tests { ListItem::new( ListTypes::Ordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("test").into()]) + ListItemContent::new(vec![Text::new("test").into()]) ) .to_string(), "+ test".to_string() @@ -162,15 +159,14 @@ mod tests { ListItem::new_with_nested_list( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("test").into()]), - Some(List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("test").into()]), + Some(List::new( ListTypes::Unordered, 1, vec![ListItem::new( ListTypes::Unordered, 1, - ListItemContent::new_with_nodes(true, vec![Text::new("test").into()]) + ListItemContent::new(vec![Text::new("test").into()]) ) .into()] )) @@ -186,7 +182,7 @@ mod tests { ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("test").into()]) + ListItemContent::new(vec![Text::new("test").into()]) ) .len(), 6 @@ -196,15 +192,14 @@ mod tests { ListItem::new_with_nested_list( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("test").into()]), - Some(List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("test").into()]), + Some(List::new( ListTypes::Unordered, 1, vec![ListItem::new( ListTypes::Unordered, 1, - ListItemContent::new_with_nodes(true, vec![Text::new("test").into()]) + ListItemContent::new(vec![Text::new("test").into()]) ) .into()] )) @@ -221,7 +216,7 @@ mod tests { ListItem::new( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(true, vec![Text::new("test").into()]) + ListItemContent::new(vec![Text::new("test").into()]) ) ); } @@ -233,15 +228,14 @@ mod tests { Some(ListItem::new_with_nested_list( ListTypes::Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("111111").into()]), - Some(List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("111111").into()]), + Some(List::new( ListTypes::Unordered, 1, vec![ListItem::new( ListTypes::Unordered, 1, - ListItemContent::new_with_nodes(true, vec![Text::new("22222").into()]) + ListItemContent::new(vec![Text::new("22222").into()]) ) .into()] )) diff --git a/src/nodes/list_item_content.rs b/src/nodes/list_item_content.rs index 11d98a6..eff50ea 100644 --- a/src/nodes/list_item_content.rs +++ b/src/nodes/list_item_content.rs @@ -89,8 +89,6 @@ impl Node for ListItemContentNodes { #[derive(Debug, PartialEq, Serialize, Clone)] pub struct ListItemContent { - #[serde(skip_serializing)] - consumed_all_input: bool, pub nodes: Vec, } @@ -98,32 +96,31 @@ impl Display for ListItemContent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{}{}", + "{}", self.nodes .iter() .map(|node| node.to_string()) .collect::>() .join(""), - if self.consumed_all_input { "" } else { "\n" } ) } } impl Node for ListItemContent { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + self.nodes.iter().map(|node| node.len()).sum::() } } impl ListItemContent { - pub fn new(consumed_all_input: bool) -> Self { - Self::new_with_nodes(consumed_all_input, vec![]) + pub fn new(nodes: Vec) -> Self { + ListItemContent { nodes } } - pub fn new_with_nodes(consumed_all_input: bool, nodes: Vec) -> Self { - ListItemContent { - consumed_all_input, - nodes, - } +} + +impl Default for ListItemContent { + fn default() -> Self { + Self::new(vec![]) } } @@ -147,11 +144,7 @@ impl Branch for ListItemContent { } fn get_outer_token_length(&self) -> usize { - if self.consumed_all_input { - 0 - } else { - 1 - } + 0 } } @@ -159,10 +152,7 @@ impl Deserializer for ListItemContent { fn deserialize_with_context(input: &str, _: Option) -> Option { let mut m = Matcher::new(input); if let Some(list_item_content) = m.get_match("", "\n", true) { - return Self::parse_branch( - list_item_content.body, - Self::new(list_item_content.end_token.is_empty()), - ); + return Self::parse_branch(list_item_content.body, "", Self::default()); } None } @@ -183,42 +173,33 @@ mod test { #[test] fn test_consume_all() { let input = "This is a list item content"; - let expected = ListItemContent::new_with_nodes(true, vec![Text::new(input).into()]); + let expected = ListItemContent::new(vec![Text::new(input).into()]); assert_eq!(ListItemContent::deserialize(input), Some(expected)); } #[test] fn test_consume_all_with_newline() { let input = "This is a list item content\nAnd this is not"; - let expected = ListItemContent::new_with_nodes( - false, - vec![Text::new("This is a list item content").into()], - ); + let expected = ListItemContent::new(vec![Text::new("This is a list item content").into()]); assert_eq!(ListItemContent::deserialize(input), Some(expected)); } #[test] fn len() { - assert_eq!(ListItemContent::new(true).len(), 0); - assert_eq!(ListItemContent::new(false).len(), 1); + assert_eq!(ListItemContent::default().len(), 0); assert_eq!( - ListItemContent::new_with_nodes(true, vec![Text::new("Hello").into()]).len(), + ListItemContent::new(vec![Text::new("Hello").into()]).len(), 5 ); } #[test] fn serialize() { - assert_eq!(ListItemContent::new(true).to_string(), ""); - assert_eq!(ListItemContent::new(false).to_string(), "\n"); + assert_eq!(ListItemContent::default().to_string(), ""); assert_eq!( - ListItemContent::new_with_nodes(true, vec![Text::new("Hello").into()]).to_string(), + ListItemContent::new(vec![Text::new("Hello").into()]).to_string(), "Hello" ); - assert_eq!( - ListItemContent::new_with_nodes(false, vec![Text::new("Hello").into()]).to_string(), - "Hello\n" - ); } #[test] @@ -227,43 +208,43 @@ mod test { ListItemContent::deserialize( "simple text **bold text** `let foo='bar';` [a](u) _I_ ~~S~~" ), - Some(ListItemContent::new_with_nodes( - true, - vec![ - Text::new("simple text ").into(), - Bold::new_with_nodes(vec![Text::new("bold text").into()]).into(), - Text::new(" ").into(), - InlineCode::new("let foo='bar';").into(), - Text::new(" ").into(), - Anchor::new("a", "u").into(), - Text::new(" ").into(), - Italic::new("I").into(), - Text::new(" ").into(), - Strikethrough::new("S").into(), - ] - )) + Some(ListItemContent::new(vec![ + Text::new("simple text ").into(), + Bold::new(vec![Text::new("bold text").into()]).into(), + Text::new(" ").into(), + InlineCode::new("let foo='bar';").into(), + Text::new(" ").into(), + Anchor::new("a", "u").into(), + Text::new(" ").into(), + Italic::new("I").into(), + Text::new(" ").into(), + Strikethrough::new("S").into(), + ])) ); } #[test] fn serialize_with_all_nodes() { assert_eq!( "simple text **bold text** `let foo='bar';` [a](u) _I_ ~~S~~", - ListItemContent::new_with_nodes( - true, - vec![ - Text::new("simple text ").into(), - Bold::new_with_nodes(vec![Text::new("bold text").into()]).into(), - Text::new(" ").into(), - InlineCode::new("let foo='bar';").into(), - Text::new(" ").into(), - Anchor::new("a", "u").into(), - Text::new(" ").into(), - Italic::new("I").into(), - Text::new(" ").into(), - Strikethrough::new("S").into(), - ] - ) + ListItemContent::new(vec![ + Text::new("simple text ").into(), + Bold::new(vec![Text::new("bold text").into()]).into(), + Text::new(" ").into(), + InlineCode::new("let foo='bar';").into(), + Text::new(" ").into(), + Anchor::new("a", "u").into(), + Text::new(" ").into(), + Italic::new("I").into(), + Text::new(" ").into(), + Strikethrough::new("S").into(), + ]) .to_string() ); } + + #[test] + fn empty_list_item_content() { + let list_item_content = ListItemContent::default(); + assert_eq!(list_item_content.len(), 0); + } } diff --git a/src/nodes/paragraph.rs b/src/nodes/paragraph.rs index 679349a..3224ea3 100644 --- a/src/nodes/paragraph.rs +++ b/src/nodes/paragraph.rs @@ -88,20 +88,12 @@ impl Node for ParagraphNodes { #[derive(Debug, PartialEq, Serialize, Clone)] pub struct Paragraph { - #[serde(skip_serializing)] - consumed_all_input: bool, pub nodes: Vec, } impl Paragraph { - pub fn new(consumed_all_input: bool) -> Self { - Self::new_with_nodes(consumed_all_input, vec![]) - } - pub fn new_with_nodes(consumed_all_input: bool, nodes: Vec) -> Self { - Self { - consumed_all_input, - nodes, - } + pub fn new(nodes: Vec) -> Self { + Self { nodes } } } @@ -123,12 +115,14 @@ impl Branch for Paragraph { fn get_fallback_node() -> Option> { Some(Text::fallback_node()) } + fn get_outer_token_length(&self) -> usize { - if self.consumed_all_input { - 0 - } else { - 2 - } + 0 + } +} +impl Default for Paragraph { + fn default() -> Self { + Self::new(vec![]) } } @@ -136,7 +130,7 @@ impl Deserializer for Paragraph { fn deserialize_with_context(input: &str, _: Option) -> Option { let mut matcher = Matcher::new(input); if let Some(paragraph) = matcher.get_match("", "\n\n", true) { - return Self::parse_branch(paragraph.body, Self::new(paragraph.end_token.is_empty())); + return Self::parse_branch(paragraph.body, "", Self::default()); } None } @@ -146,20 +140,19 @@ impl Display for Paragraph { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "{}{}", + "{}", self.nodes .iter() .map(|node| node.to_string()) .collect::>() .concat(), - if self.consumed_all_input { "" } else { "\n\n" } ) } } impl Node for Paragraph { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + self.nodes.iter().map(|node| node.len()).sum::() } } @@ -168,11 +161,7 @@ impl FallbackNode for Paragraph { where Self: Into, { - Box::new(|input| { - Paragraph::deserialize(input) - .unwrap_or(Paragraph::new(true)) - .into() - }) + Box::new(|input| Paragraph::deserialize(input).unwrap_or_default().into()) } } @@ -192,9 +181,9 @@ mod tests { #[test] fn push() { - let mut p = Paragraph::new(true); + let mut p = Paragraph::default(); p.push(Text::new("simple text ")); - p.push(Bold::new_with_nodes(vec![Text::new("bold text").into()])); + p.push(Bold::new(vec![Text::new("bold text").into()])); p.push(InlineCode::new("let foo='bar';")); assert_eq!( @@ -206,51 +195,37 @@ mod tests { #[test] fn serialize() { assert_eq!( - Paragraph::new_with_nodes( - true, - vec![ - Text::new("simple text ").into(), - Bold::new_with_nodes(vec![Text::new("bold text").into()]).into(), - InlineCode::new("let foo='bar';").into(), - Anchor::new("a", "u").into(), - Italic::new("I").into(), - Strikethrough::new("S").into() - ], - ) + Paragraph::new(vec![ + Text::new("simple text ").into(), + Bold::new(vec![Text::new("bold text").into()]).into(), + InlineCode::new("let foo='bar';").into(), + Anchor::new("a", "u").into(), + Italic::new("I").into(), + Strikethrough::new("S").into() + ],) .to_string(), "simple text **bold text**`let foo='bar';`[a](u)_I_~~S~~".to_string() ); - assert_eq!( - Paragraph::new_with_nodes(false, vec![Text::new("t").into()]).to_string(), - "t\n\n".to_string() - ); } #[test] fn deserialize() { assert_eq!( Paragraph::deserialize("simple text **bold text**`let foo='bar';`[t](u)"), - Some(Paragraph::new_with_nodes( - true, - vec![ - Text::new("simple text ").into(), - Bold::new_with_nodes(vec![Text::new("bold text").into()]).into(), - InlineCode::new("let foo='bar';").into(), - Anchor::new("t", "u").into() - ] - )) + Some(Paragraph::new(vec![ + Text::new("simple text ").into(), + Bold::new(vec![Text::new("bold text").into()]).into(), + InlineCode::new("let foo='bar';").into(), + Anchor::new("t", "u").into() + ])) ); assert_eq!( Paragraph::deserialize("1 2\n\n3"), - Some(Paragraph::new_with_nodes( - false, - vec![Text::new("1 2").into()] - )) + Some(Paragraph::new(vec![Text::new("1 2").into()])) ); } #[test] fn len() { - assert_eq!(Paragraph::new(true).len(), 0); - assert_eq!(Paragraph::new(false).len(), 2); + assert_eq!(Paragraph::default().len(), 0); } } diff --git a/src/nodes/yamd.rs b/src/nodes/yamd.rs index 965ba99..e1d24a3 100644 --- a/src/nodes/yamd.rs +++ b/src/nodes/yamd.rs @@ -131,11 +131,7 @@ pub struct Yamd { } impl Yamd { - pub fn new(metadata: Option) -> Self { - Self::new_with_nodes(metadata, vec![]) - } - - pub fn new_with_nodes(metadata: Option, nodes: Vec) -> Self { + pub fn new(metadata: Option, nodes: Vec) -> Self { Self { metadata: metadata.unwrap_or_default(), nodes, @@ -167,7 +163,7 @@ impl Branch for Yamd { } fn get_outer_token_length(&self) -> usize { - self.metadata.len() + 0 } } @@ -175,7 +171,7 @@ impl Deserializer for Yamd { fn deserialize_with_context(input: &str, _: Option) -> Option { let metadata = Metadata::deserialize(input); let metadata_len = metadata.as_ref().map(|m| m.len()).unwrap_or(0); - Self::parse_branch(&input[metadata_len..], Self::new(metadata)) + Self::parse_branch(&input[metadata_len..], "\n\n", Self::new(metadata, vec![])) } } @@ -189,14 +185,19 @@ impl Display for Yamd { .iter() .map(|node| node.to_string()) .collect::>() - .join("") + .join("\n\n") ) } } impl Node for Yamd { fn len(&self) -> usize { - self.nodes.iter().map(|node| node.len()).sum::() + self.get_outer_token_length() + let delimeter_len = if self.nodes.is_empty() { + 0 + } else { + (self.nodes.len() - 1) * 2 + }; + self.nodes.iter().map(|node| node.len()).sum::() + delimeter_len } } @@ -225,7 +226,7 @@ mod tests { text::Text, }, toolkit::deserializer::Branch, - toolkit::deserializer::Deserializer, + toolkit::{deserializer::Deserializer, node::Node}, }; use chrono::DateTime; use pretty_assertions::assert_eq; @@ -283,23 +284,20 @@ end"#; #[test] fn push() { - let mut t = Yamd::new(None); - t.push(Heading::new(false, "header", 1)); - t.push(Paragraph::new_with_nodes( - true, - vec![Text::new("text").into()], - )); + let mut t = Yamd::new(None, vec![]); + t.push(Heading::new("header", 1)); + t.push(Paragraph::new(vec![Text::new("text").into()])); assert_eq!(t.to_string(), "# header\n\ntext".to_string()); } #[test] fn from_vec() { - let t: String = Yamd::new_with_nodes( + let t: String = Yamd::new( None, vec![ - Heading::new(false, "header", 1).into(), - Paragraph::new_with_nodes(true, vec![Text::new("text").into()]).into(), + Heading::new("header", 1).into(), + Paragraph::new(vec![Text::new("text").into()]).into(), ], ) .to_string(); @@ -311,7 +309,7 @@ end"#; fn deserialize() { assert_eq!( Yamd::deserialize(TEST_CASE), - Some(Yamd::new_with_nodes( + Some(Yamd::new( Some(Metadata::new( Some("test"), Some( @@ -326,56 +324,43 @@ end"#; Some(vec!["tag1".to_string(), "tag2".to_string()]), )), vec![ - Heading::new(false, "hello", 1).into(), - Code::new(false, "rust", "let a=1;").into(), - Paragraph::new_with_nodes( - false, - vec![ - Text::new("t").into(), - Bold::new_with_nodes(vec![Text::new("b").into()]).into() - ] - ) + Heading::new("hello", 1).into(), + Code::new("rust", "let a=1;").into(), + Paragraph::new(vec![ + Text::new("t").into(), + Bold::new(vec![Text::new("b").into()]).into() + ]) .into(), - Image::new(false, 'a', 'u').into(), - ImageGallery::new_with_nodes( - false, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - ) + Image::new('a', 'u').into(), + ImageGallery::new(vec![ + Image::new("a", "u").into(), + Image::new("a2", "u2").into() + ],) .into(), - Highlight::new_with_nodes( - false, + Highlight::new( Some("H"), Some("I"), vec![ - Paragraph::new_with_nodes(false, vec![Strikethrough::new("s").into()]) - .into(), - Paragraph::new_with_nodes(true, vec![Italic::new("I").into()]).into() + Paragraph::new(vec![Strikethrough::new("s").into()]).into(), + Paragraph::new(vec![Italic::new("I").into()]).into() ] ) .into(), - Divider::new(false).into(), - List::new_with_nodes( - false, + Divider::new().into(), + List::new( Unordered, 0, vec![ListItem::new_with_nested_list( Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("one").into()]), - Some(List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("one").into()]), + Some(List::new( Unordered, 1, vec![ListItem::new( Unordered, 1, - ListItemContent::new_with_nodes( - true, - vec![Text::new("two").into()] - ) + ListItemContent::new(vec![Text::new("two").into()]) ) .into()] )) @@ -383,17 +368,14 @@ end"#; .into()] ) .into(), - Embed::new("youtube", "123", false).into(), - Embed::new("cloudinary_gallery", "cloud_name&tag", false).into(), - Accordion::new_with_nodes( - false, - vec![ - AccordionTab::new(false, Some("accordeon tab"),).into(), - AccordionTab::new(true, Some("one more accordeon tab"),).into() - ] - ) + Embed::new("youtube", "123",).into(), + Embed::new("cloudinary_gallery", "cloud_name&tag",).into(), + Accordion::new(vec![ + AccordionTab::new(Some("accordeon tab"), vec![]).into(), + AccordionTab::new(Some("one more accordeon tab"), vec![]).into() + ]) .into(), - Paragraph::new_with_nodes(true, vec![Text::new("end").into()]).into() + Paragraph::new(vec![Text::new("end").into()]).into() ] )) ); @@ -402,7 +384,7 @@ end"#; #[test] fn serialize() { assert_eq!( - Yamd::new_with_nodes( + Yamd::new( Some(Metadata::new( Some("test"), Some( @@ -417,57 +399,43 @@ end"#; Some(vec!["tag1".to_string(), "tag2".to_string()]), )), vec![ - Heading::new(false, "hello", 1).into(), - Code::new(false, "rust", "let a=1;").into(), - Paragraph::new_with_nodes( - false, - vec![ - Text::new("t").into(), - Bold::new_with_nodes(vec![Text::new("b").into()]).into() - ] - ) + Heading::new("hello", 1).into(), + Code::new("rust", "let a=1;").into(), + Paragraph::new(vec![ + Text::new("t").into(), + Bold::new(vec![Text::new("b").into()]).into() + ]) .into(), - Image::new(false, 'a', 'u').into(), - ImageGallery::new_with_nodes( - false, - vec![ - Image::new(true, "a", "u").into(), - Image::new(true, "a2", "u2").into() - ], - ) + Image::new('a', 'u').into(), + ImageGallery::new(vec![ + Image::new("a", "u").into(), + Image::new("a2", "u2").into() + ],) .into(), - Highlight::new_with_nodes( - false, + Highlight::new( Some("H"), Some("I"), vec![ - Paragraph::new_with_nodes(false, vec![Strikethrough::new("s").into()]) - .into(), - Paragraph::new_with_nodes(true, vec![Italic::new("I").into()]).into() + Paragraph::new(vec![Strikethrough::new("s").into()]).into(), + Paragraph::new(vec![Italic::new("I").into()]).into() ] ) .into(), - Divider::new(false).into(), - List::new_with_nodes( - false, + Divider::new().into(), + List::new( Unordered, 0, vec![ListItem::new_with_nested_list( Unordered, 0, - ListItemContent::new_with_nodes(false, vec![Text::new("one").into()]) - .into(), - List::new_with_nodes( - true, + ListItemContent::new(vec![Text::new("one").into()]).into(), + List::new( Unordered, 1, vec![ListItem::new( Unordered, 1, - ListItemContent::new_with_nodes( - true, - vec![Text::new("two").into()] - ) + ListItemContent::new(vec![Text::new("two").into()]) ) .into()] ) @@ -476,17 +444,14 @@ end"#; .into()] ) .into(), - Embed::new("youtube", "123", false).into(), - Embed::new("cloudinary_gallery", "cloud_name&tag", false).into(), - Accordion::new_with_nodes( - false, - vec![ - AccordionTab::new(false, Some("accordeon tab"),).into(), - AccordionTab::new(true, Some("one more accordeon tab"),).into() - ] - ) + Embed::new("youtube", "123",).into(), + Embed::new("cloudinary_gallery", "cloud_name&tag",).into(), + Accordion::new(vec![ + AccordionTab::new(Some("accordeon tab"), vec![]).into(), + AccordionTab::new(Some("one more accordeon tab"), vec![]).into() + ]) .into(), - Paragraph::new_with_nodes(true, vec![Text::new("end").into()]).into() + Paragraph::new(vec![Text::new("end").into()]).into() ] ) .to_string(), @@ -502,12 +467,12 @@ end"#; #[test] fn multiple_fallbacks_in_a_row() { let input = "1\n\n2\n\n3"; - let expected = Yamd::new_with_nodes( + let expected = Yamd::new( None, vec![ - Paragraph::new_with_nodes(false, vec![Text::new("1").into()]).into(), - Paragraph::new_with_nodes(false, vec![Text::new("2").into()]).into(), - Paragraph::new_with_nodes(true, vec![Text::new("3").into()]).into(), + Paragraph::new(vec![Text::new("1").into()]).into(), + Paragraph::new(vec![Text::new("2").into()]).into(), + Paragraph::new(vec![Text::new("3").into()]).into(), ], ); let actual = Yamd::deserialize(input).unwrap(); @@ -517,16 +482,22 @@ end"#; #[test] fn multiple_fallbacks_in_a_row_before_non_fallback() { let input = "1\n\n2\n\n3\n\n# header"; - let expected = Yamd::new_with_nodes( + let expected = Yamd::new( None, vec![ - Paragraph::new_with_nodes(false, vec![Text::new("1").into()]).into(), - Paragraph::new_with_nodes(false, vec![Text::new("2").into()]).into(), - Paragraph::new_with_nodes(false, vec![Text::new("3").into()]).into(), - Heading::new(true, "header", 1).into(), + Paragraph::new(vec![Text::new("1").into()]).into(), + Paragraph::new(vec![Text::new("2").into()]).into(), + Paragraph::new(vec![Text::new("3").into()]).into(), + Heading::new("header", 1).into(), ], ); let actual = Yamd::deserialize(input).unwrap(); assert_eq!(expected, actual); } + + #[test] + fn empty_yamd() { + let yamd = Yamd::new(None, vec![]); + assert_eq!(yamd.len(), 0); + } } diff --git a/src/toolkit/deserializer.rs b/src/toolkit/deserializer.rs index 66621fe..eca985c 100644 --- a/src/toolkit/deserializer.rs +++ b/src/toolkit/deserializer.rs @@ -1,15 +1,16 @@ +use std::fmt::Display; + use super::{context::Context, node::Node}; pub trait Branch where - BranchNodes: Node, + BranchNodes: Node + Display, { fn push>(&mut self, node: CanBeNode); fn get_maybe_nodes() -> Vec>; fn get_fallback_node() -> Option>; fn get_outer_token_length(&self) -> usize; - - fn parse_branch(input: &str, mut branch: Self) -> Option + fn parse_branch(input: &str, delimeter: &str, mut branch: Self) -> Option where Self: Sized + Deserializer + Node, { @@ -18,32 +19,52 @@ where let maybe_nodes = Self::get_maybe_nodes(); while current_position < input.len() { let slice = &input[current_position..]; - current_position += slice.chars().next().unwrap().len_utf8(); + let char_len = slice.chars().next().unwrap().len_utf8(); + current_position += char_len; for parser in &maybe_nodes { if let Some(node) = parser(slice, branch.context()) { - while fallback_position != current_position - 1 { - fallback_position = - branch.fallback(&input[fallback_position..current_position - 1])?; + while fallback_position != current_position - char_len { + fallback_position = branch.fallback( + &input[fallback_position..current_position - char_len], + delimeter, + ); } branch.push(node); current_position = branch.len() - branch.get_outer_token_length(); fallback_position = current_position; + if !delimeter.is_empty() && input[current_position..].starts_with(delimeter) { + current_position += delimeter.len(); + fallback_position = current_position; + } + + break; } } } while fallback_position < input.len() { - fallback_position = branch.fallback(&input[fallback_position..])?; + if Self::get_fallback_node().is_none() { + return None; + } else { + fallback_position = branch.fallback(&input[fallback_position..], delimeter); + } } - Some(branch) } - fn fallback(&mut self, slice: &str) -> Option + fn fallback(&mut self, slice: &str, delimeter: &str) -> usize where Self: Node, { - self.push(Self::get_fallback_node().map(|f| f(slice))?); - Some(self.len() - self.get_outer_token_length()) + let node = Self::get_fallback_node() + .map(|f| f(slice)) + .expect("Fallback node should always be available"); + let node_len = node.len(); + self.push(node); + let mut fallback_position = self.len() - self.get_outer_token_length(); + if !delimeter.is_empty() && slice[node_len..].starts_with(delimeter) { + fallback_position += delimeter.len(); + } + fallback_position } }