diff --git a/Cargo.lock b/Cargo.lock index 2c87955..8fb19d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.1.0" dependencies = [ "clap", "indoc", + "rand", "rayon", "rnix", "rowan 0.15.3", @@ -129,6 +130,17 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "getrandom" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -230,6 +242,52 @@ dependencies = [ "memchr", ] +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "rayon" version = "1.5.1" @@ -372,6 +430,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 9a41d1d..0752903 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [dependencies] clap = "3" + rand = "0.8" rayon = "1.5" rnix = "0.10" rowan = "0.15" diff --git a/README.md b/README.md index 7f3f8a9..fd36549 100644 --- a/README.md +++ b/README.md @@ -55,13 +55,6 @@ Coverage is currently 80%, and we'll have 💯% soon. -- ✔️ **Developer aware** - - Syntax errors are gracefully handled - by leaving that specific zone unformatted. - - The rest of the file will be formatted normally. - - ✔️ **Reproducible** Formatting many times yields the same results. @@ -70,9 +63,6 @@ Beauty is subjective, right? - Yet there are a few improvements to implement like: - - Multiline strings indentation is missing `'' ... ''`. - Style is negotiable at this moment. ## Getting started diff --git a/src/builder.rs b/src/builder.rs index ae9c3c2..2e34d1f 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -123,7 +123,7 @@ fn build_step( add_token( builder, build_ctx, - rnix::SyntaxKind::TOKEN_COMMA, + rnix::SyntaxKind::TOKEN_WHITESPACE, &format!("{0:<1$}", "", 2 * build_ctx.indentation), ); } @@ -209,7 +209,7 @@ fn format( } rnix::SyntaxKind::NODE_ROOT => crate::rules::root::rule, rnix::SyntaxKind::NODE_SELECT => crate::rules::select::rule, - rnix::SyntaxKind::NODE_STRING => crate::rules::default, + rnix::SyntaxKind::NODE_STRING => crate::rules::string::rule, rnix::SyntaxKind::NODE_STRING_INTERPOL => { crate::rules::string_interpol::rule } diff --git a/src/children.rs b/src/children.rs index eb0fffb..6ac335c 100644 --- a/src/children.rs +++ b/src/children.rs @@ -65,6 +65,17 @@ impl Children { child } + pub fn get_remaining(&mut self) -> Vec { + if self.current_index < self.children.len() { + let remaining = + &self.children[self.current_index..self.children.len()]; + self.current_index = self.children.len(); + remaining.to_vec() + } else { + vec![] + } + } + pub fn has_next(&self) -> bool { self.current_index < self.children.len() } diff --git a/src/rules/string.rs b/src/rules/string.rs index cee8eeb..606f5fb 100644 --- a/src/rules/string.rs +++ b/src/rules/string.rs @@ -26,82 +26,113 @@ pub fn rule( } } } else { - let indentation = get_double_quoted_string_indentation(&node); + let placeholder = get_placeholder(); - while let Some(child) = children.peek_next() { - match child.element.kind() { + let elements: Vec = children + .get_remaining() + .iter() + .map(|child| child.element.clone()) + .collect(); + + let mut interpolations = elements + .iter() + .filter(|e| e.kind() != rnix::SyntaxKind::TOKEN_STRING_CONTENT); + + let mut lines: Vec = elements[0..elements.len() - 1] + .iter() + .map(|element| match element.kind() { rnix::SyntaxKind::TOKEN_STRING_CONTENT => { - let child_token = child.element.into_token().unwrap(); - let lines: Vec<&str> = - child_token.text().split('\n').collect(); + let token = element.clone().into_token().unwrap(); + token.text().to_string() + } + _ => placeholder.to_string(), + }) + .collect::() + .split('\n') + .map(|line| line.trim_end().to_string()) + .collect(); - children.move_next(); - for (index, line) in lines.iter().enumerate() { - if index + 1 == lines.len() && line.trim().len() == 0 { - if let rnix::SyntaxKind::TOKEN_STRING_END = - children.peek_next().unwrap().element.kind() - { - continue; - } - } + // eprintln!("0: {:?}", lines); - steps.push_back(crate::builder::Step::Token( - rnix::SyntaxKind::TOKEN_STRING_CONTENT, - if indentation >= line.len() { - line.to_string() - } else { - line[indentation..line.len()].to_string() - }, + let mut indentation: usize = usize::MAX; + for line in lines.iter() { + let line = line.trim_end(); + + if line.len() > 0 { + indentation = usize::min( + indentation, + line.len() - line.trim_start().len(), + ); + } + } + if indentation == usize::MAX { + indentation = 0; + }; + + // Dedent everything as much as possible + lines = lines + .iter() + .map(|line| { + if indentation < line.len() { + line[indentation..line.len()].to_string() + } else { + line.to_string() + } + }) + .collect(); + + // eprintln!("1: ''{}''", lines.join("\n")); + // eprintln!("indentation={}, placeholder={}", indentation, placeholder); + + for (index, line) in lines.iter().enumerate() { + let portions: Vec = line + .split(&placeholder) + .map(|portion| portion.to_string()) + .collect(); + + if portions.len() == 1 { + steps.push_back(crate::builder::Step::Pad); + steps.push_back(crate::builder::Step::Token( + rnix::SyntaxKind::TOKEN_STRING_CONTENT, + portions[0].to_string(), + )); + } else { + steps.push_back(crate::builder::Step::Pad); + for (index, portion) in portions.iter().enumerate() { + steps.push_back(crate::builder::Step::Token( + rnix::SyntaxKind::TOKEN_STRING_CONTENT, + portion.to_string(), + )); + + if index + 1 != portions.len() { + steps.push_back(crate::builder::Step::FormatWider( + interpolations.next().unwrap().clone(), )); - - if index == 0 && lines.len() > 1 { - steps.push_back(crate::builder::Step::NewLine); - steps.push_back(crate::builder::Step::Pad); - } else if index + 1 < lines.len() - && lines[index + 1].trim().len() == 0 - { - steps.push_back(crate::builder::Step::NewLine); - steps.push_back(crate::builder::Step::Pad); - } } } - rnix::SyntaxKind::TOKEN_STRING_END => { - steps - .push_back(crate::builder::Step::Format(child.element)); - children.move_next(); - } - _ => { - steps.push_back(crate::builder::Step::FormatWider( - child.element, - )); - children.move_next(); - } } + + if index + 1 < lines.len() { + steps.push_back(crate::builder::Step::NewLine); + } + } + + for interpolation in interpolations { + steps.push_back(crate::builder::Step::FormatWider( + interpolation.clone(), + )); } } - // steps = crate::rules::default(build_ctx, node); steps } -fn get_double_quoted_string_indentation(node: &rnix::SyntaxNode) -> usize { - let mut indentation: usize = usize::MAX; +fn get_placeholder() -> String { + use rand::RngCore; - let text: String = node - .children_with_tokens() - .filter(|child| child.kind() == rnix::SyntaxKind::TOKEN_STRING_CONTENT) - .map(|child| child.into_token().unwrap()) - .map(|token| token.text().to_string()) - .collect(); + let mut bytes = [0u8; 32]; - for line in text.split('\n') { - let line = line.trim_end(); + rand::thread_rng().fill_bytes(&mut bytes); - if line.len() > 0 { - indentation = - usize::min(indentation, line.len() - line.trim_start().len()); - } - } - - if indentation == usize::MAX { 0 } else { indentation } + bytes.iter().map(|byte| format!("{:02X}", byte)).collect() } diff --git a/tests/cases/string/in b/tests/cases/string/in index de62ede..2b0f7eb 100644 --- a/tests/cases/string/in +++ b/tests/cases/string/in @@ -1,40 +1,62 @@ [ "" - +### " " - +### "a ${x} b " - +### '''' - - +### ''a'' - +### ''${""}'' +### + ''${""} + '' +### + ''a + '' +### + ''a + + '' +### + '' a + '' +### + + ''a + '' +### '' a - ${""} + ${""} b ${""} c ${""} d e '' - +### '' '' - +### '' declare -a makefiles=(./*.mak) sed -i -f ${makefile-sed} "''${makefiles[@]}" - # assign Makefile variables eagerly & change backticks to `$(shell …)` + ### assign Makefile variables eagerly & change backticks to `$(shell …)` sed -i -e 's/ = `\([^`]\+\)`/ := $(shell \1)/' \ -e 's/`\([^`]\+\)`/$(shell \1)/' \ "''${makefiles[@]}" '' - +### + '' + [${ mkSectionName sectName }] + '' +### +''-couch_ini ${ cfg.package }/etc/default.ini ${ configFile } ${ pkgs.writeText "couchdb-extra.ini" cfg.extraConfig } ${ cfg.configFile }'' ] diff --git a/tests/cases/string/out b/tests/cases/string/out index 7fd26ce..4a71346 100644 --- a/tests/cases/string/out +++ b/tests/cases/string/out @@ -1,30 +1,63 @@ [ "" + ### " " + ### "a ${ x } b " - '''' - ''a'' - ''${ "" }'' + ### + '' '' + ### + '' a'' + ### + '' ${ "" }'' + ### + '' ${ "" } + '' - a - ${ "" } - b - ${ "" } - c ${ "" } d - e - '' + ### + '' a '' + ### + '' a + '' + ### + '' a '' - declare -a makefiles=(./*.mak) - sed -i -f ${ makefile-sed } "''${makefiles[@]}" - # assign Makefile variables eagerly & change backticks to `$(shell …)` - sed -i -e 's/ = `\([^`]\+\)`/ := $(shell \1)/' \ - -e 's/`\([^`]\+\)`/$(shell \1)/' \ - "''${makefiles[@]}" - '' + ### + '' a + '' + ### + '' + a + ${ "" } + b + ${ "" } + c ${ "" } d + e + '' + ### + '' + '' + ### + '' + declare -a makefiles=(./*.mak) + sed -i -f ${ makefile-sed } "''${makefiles[@]}" + ### assign Makefile variables eagerly & change backticks to `$(shell …)` + sed -i -e 's/ = `\([^`]\+\)`/ := $(shell \1)/' \ + -e 's/`\([^`]\+\)`/$(shell \1)/' \ + "''${makefiles[@]}" + '' + ### + '' + [${ mkSectionName sectName }] + '' + ### + '' -couch_ini ${ cfg.package }/etc/default.ini ${ configFile } ${ + pkgs.writeText "couchdb-extra.ini" cfg.extraConfig + } ${ cfg.configFile }'' ]