diff --git a/src/parser/attribute/process.rs b/src/parser/attribute/process.rs index 55decdc..913a838 100644 --- a/src/parser/attribute/process.rs +++ b/src/parser/attribute/process.rs @@ -369,16 +369,16 @@ mod tests { #[test] fn it_should_process_attribute_with_multiline_value() { let input = r#"class="{ - 'is-active': isActive, - 'is-disabled': isDisabled, + 'is-active': isActive, + 'is-disabled': isDisabled, }" - :key="item.id""#; + :key="item.id""#; let (rest, attribute) = process_attribute( input, &mut HsmlProcessContext { - indent_level: 1, - indent_string: Some(String::from(" ")), + nested_tag_level: 1, + indent_string: String::from(" "), }, ) .unwrap(); @@ -386,14 +386,14 @@ mod tests { assert_eq!( attribute, r#"class="{ - 'is-active': isActive, - 'is-disabled': isDisabled, + 'is-active': isActive, + 'is-disabled': isDisabled, }""# ); assert_eq!( rest, r#" - :key="item.id""# + :key="item.id""# ); } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index bca4cdb..e13509e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -31,8 +31,14 @@ pub enum HsmlNode { #[derive(Debug, Default)] pub struct HsmlProcessContext { - pub indent_level: usize, - pub indent_string: Option, + // TODO @Shinigami92 2025-03-16: Currently nested_tag_level is not used, but should be later to allow mixed spaces and tabs in indentation + /// The tracked nested tag level + pub nested_tag_level: usize, + + /// The tracked indentation string + /// + /// Can be a combination of spaces and tabs + pub indent_string: String, } pub fn process_newline(input: &str) -> IResult<&str, &str> { diff --git a/src/parser/parse.rs b/src/parser/parse.rs index 5fbef0d..9f3a52f 100644 --- a/src/parser/parse.rs +++ b/src/parser/parse.rs @@ -15,9 +15,19 @@ pub fn parse(input: &str) -> IResult<&str, RootNode> { loop { // eat leading and trailing newlines and whitespace if there are any - if let Ok((rest, _)) = + if let Ok((rest, taken)) = take_till::<_, &str, nom::error::Error<&str>>(|c: char| !c.is_whitespace())(input) { + // take the leading spaces and tabs after the last newline as indentation + context.indent_string = taken + .chars() + .rev() + .take_while(|c| c.is_whitespace() && *c != '\n') + .collect::() + .chars() + .rev() + .collect(); + input = rest; if input.is_empty() { diff --git a/src/parser/tag/node.rs b/src/parser/tag/node.rs index 8e40ede..364396d 100644 --- a/src/parser/tag/node.rs +++ b/src/parser/tag/node.rs @@ -120,34 +120,25 @@ pub fn tag_node<'a>(input: &'a str, context: &mut HsmlProcessContext) -> IResult if !indentation.is_empty() { // check that the indentation is consistent and does not include tabs and spaces at the same time - // if it does, throw an error + // if it does, collect an error for diagnostics if indentation.contains('\t') && indentation.contains(' ') { - // TODO @Shinigami92 2023-05-18: This error could be more specific - return Err(nom::Err::Error(Error::new(input, ErrorKind::Tag))); - } - - // if we never hit an indentation yet, set it - // this only happens once - if context.indent_string.is_none() { - // println!("set indent string = \"{}\"", indentation); - context.indent_string = Some(indentation.to_string()); + // TODO @Shinigami92 2025-03-16: This should collect an error or diagnostics } // persist the indentation level so we can restore it later - let indentation_level = context.indent_level; - - context.indent_level += 1; + let nested_tag_level = context.nested_tag_level; + let indent_string = context.indent_string.clone(); // check that we are at the correct indentation level, otherwise break out of the loop - let indent_string_len = context.indent_string.as_ref().unwrap().len(); - let indent_size = indent_string_len * context.indent_level; - // dbg!(indent_size, indentation.len()); - if indent_size != indentation.len() { + if indentation.len() <= context.indent_string.len() { // dbg!("break out of loop"); break; } + context.nested_tag_level += 1; + context.indent_string = indentation.to_string(); + // we are at the correct indentation level, so we can continue parsing the child tag nodes // there could be a comment (dev or native) node @@ -169,8 +160,9 @@ pub fn tag_node<'a>(input: &'a str, context: &mut HsmlProcessContext) -> IResult } } - // restore the indentation level - context.indent_level = indentation_level; + // restore the nested_tag_level level + context.nested_tag_level = nested_tag_level; + context.indent_string = indent_string; continue; } @@ -207,8 +199,8 @@ mod tests { #[test] fn it_should_return_tag_node_with_piped_text() { let context = &mut HsmlProcessContext { - indent_level: 3, - indent_string: Some(String::from(" ")), + nested_tag_level: 3, + indent_string: String::from(" "), }; let (input, tag) = tag_node( diff --git a/src/parser/text/node.rs b/src/parser/text/node.rs index 24c635b..1a8e4e7 100644 --- a/src/parser/text/node.rs +++ b/src/parser/text/node.rs @@ -15,17 +15,12 @@ pub fn text_block_node<'a>( ) -> IResult<&'a str, TextNode> { let (input, text) = process_text_block(input, context)?; - let indent_string = context - .indent_string - .as_ref() - .unwrap() - .repeat(context.indent_level + 1); - - let newline_indent_replacement: &str = &format!("\n{}", &indent_string); - + // On every line, replace all leading spaces and tabs with an empty string let text = text - .trim_start_matches(&indent_string) - .replace(newline_indent_replacement, "\n"); + .lines() + .map(|line| line.trim_start()) + .collect::>() + .join("\n"); Ok((input, TextNode { text })) } @@ -51,8 +46,8 @@ mod tests { #[test] fn it_should_return_text_block_node() { let context = &mut HsmlProcessContext { - indent_string: Some(String::from(" ")), - indent_level: 3, + nested_tag_level: 3, + indent_string: String::from(" "), }; let (input, text_block) = text_block_node( @@ -78,4 +73,33 @@ and the build size is tiny.""# assert_eq!(input, "\n figcaption.font-medium"); } + + #[test] + fn it_should_stop_before_next_tag_node() { + let context = &mut HsmlProcessContext { + nested_tag_level: 1, + indent_string: String::from(" "), + }; + + let (input, text_block) = text_block_node( + r#". + Sarah Dayan + .text-[#af05c9].dark:text-slate-500. + Staff Engineer, Algolia"#, + context, + ) + .unwrap(); + + assert_eq!( + text_block, + TextNode { + text: String::from(r#"Sarah Dayan"#), + } + ); + + assert_eq!( + input, + "\n .text-[#af05c9].dark:text-slate-500.\n Staff Engineer, Algolia" + ); + } } diff --git a/src/parser/text/process.rs b/src/parser/text/process.rs index 096d042..6f2b99a 100644 --- a/src/parser/text/process.rs +++ b/src/parser/text/process.rs @@ -15,17 +15,9 @@ pub fn process_text_block<'a>( // eat one \r\n or \n let (rest, _) = alt((tag("\r\n"), tag("\n"))).parse(rest)?; - let indent_string: &str = if let Some(indent_string) = &context.indent_string { - indent_string - } else { - " " - }; - - let indent_string: &str = &indent_string.repeat(context.indent_level + 1); - let mut text_block_index = 0; - // loop over each line until we find a line that does not fulfill the indentation + // loop over each line until we find a line that does not starts with the current indent string for (index, c) in rest.chars().enumerate() { if c == '\n' { // if next char is also a \n, then continue @@ -38,7 +30,15 @@ pub fn process_text_block<'a>( let line = &rest[index + 1..]; // otherwise check the indentation and if it does not fulfill the indentation, then break - if !line.starts_with(indent_string) { + // TODO @Shinigami92 2025-03-16: right now this does not support mixed indentations on tag level indentation, but only withing the text block + if !line.starts_with(&context.indent_string) { + break; + } + + let line = &line[context.indent_string.len()..]; + + // break out if the first character is not a space or tab + if !line.starts_with(' ') && !line.starts_with('\t') { break; } } else { @@ -69,15 +69,15 @@ mod tests { #[test] fn it_should_process_text_block() { let mut context = HsmlProcessContext { - indent_string: Some(String::from(" ")), - indent_level: 1, + nested_tag_level: 1, + indent_string: String::from(" "), }; let input = r#". - this is just some text + this is just some text it can be multiline - and also contain blank lines + and also contain blank lines span other text "#; @@ -85,10 +85,10 @@ span other text assert_eq!( text_block, - r#" this is just some text + r#" this is just some text it can be multiline - and also contain blank lines"# + and also contain blank lines"# ); assert_eq!( rest,