From 8c2af9941c998ed8ec3df3bd98147fdcb6a036df Mon Sep 17 00:00:00 2001 From: Tyarel8 <98483313+Tyarel8@users.noreply.github.com> Date: Tue, 5 Aug 2025 18:21:23 +0200 Subject: [PATCH] feat(std-rfc/str): add `str align` (#16062) --- crates/nu-std/std-rfc/str/mod.nu | 51 +++++++++ crates/nu-std/tests/test_std-rfc_str.nu | 136 ++++++++++++++++++++++++ 2 files changed, 187 insertions(+) diff --git a/crates/nu-std/std-rfc/str/mod.nu b/crates/nu-std/std-rfc/str/mod.nu index 4c9995594f..b8b477504c 100644 --- a/crates/nu-std/std-rfc/str/mod.nu +++ b/crates/nu-std/std-rfc/str/mod.nu @@ -129,3 +129,54 @@ export def unindent [ $text | str replace -r --all $"\(?m\)^($indent_chars)" '' } + +alias "str align" = align + +# Aligns each line in the input string to have the target in the same column through padding +@example "Align variable assignments" { [ "one = 1", "two = 2", "three = 3", "four = 4", "five = 5" ] | str align '=' } --result r#'one = 1 +two = 2 +three = 3 +four = 4 +five = 5'# +@example "Align variable assignments to the center" { [ "one = 1", "two = 2", "three = 3", "four = 4", "five = 5" ] | str align '=' --center } --result r#' one = 1 + two = 2 +three = 3 + four = 4 + five = 5'# +export def align [ + target:string # Substring to align + --char (-c) = " " # Character to use for padding + --center (-C) # Add padding at the beginning of the line instead of before the target + --range (-r): range # The range of lines to align +]: [string -> string, list -> string] { + # noop on empty string + if ($in | is-empty) { return "" } + let $input = $in | to text | lines + + let $indexes = ( + $input + | enumerate + | each {|x| + if $x.index in ($range | default 0..) { + $x.item | str index-of $target + } else { + -1 + } + } + ) + let $max = $indexes | math max + + $input + | zip $indexes + | each {|x| + # Fold adding a `$char` at the index until they are in the same column + # If the substring is not in the line, the index is -1 and it is left as it is + seq 1 (if $x.1 == -1 { 0 } else { $max - $x.1 }) + | reduce -f ($x.0 | split chars) {|_, acc| + let $idx = if $center { 0 } else { $x.1 } + $acc | insert $idx $char + } + | str join + } + | str join (char nl) +} diff --git a/crates/nu-std/tests/test_std-rfc_str.nu b/crates/nu-std/tests/test_std-rfc_str.nu index 3620854fc7..d12f5eb0d3 100644 --- a/crates/nu-std/tests/test_std-rfc_str.nu +++ b/crates/nu-std/tests/test_std-rfc_str.nu @@ -291,3 +291,139 @@ def str-unindent_whitespace_works_with_tabs [] { assert equal $actual $expected } + +@test +def str-align_simple [] { + let actual = [ + "let a = 1" + "let max = 2" + "let very_long_variable_name = 3" + ] | str align '=' + + let expected = [ + "let a = 1" + "let max = 2" + "let very_long_variable_name = 3" + ] | str join "\n" + + assert equal $actual $expected +} + +@test +def str-align_center [] { + let actual = [ + "a = 1" + "max = 2" + "very_long_variable_name = 3" + ] | str align '=' --center + + let expected = [ + " a = 1" + " max = 2" + "very_long_variable_name = 3" + ] | str join "\n" + + assert equal $actual $expected +} + +@test +def str-align_with_range [] { + let actual = r#'match 5 { + 1.. => { print "More than zero" } + 0 => { print "Zero" } + -1 => { print "Negative one" } + -119283 => { print "Very negative" } +}'# | str align '=>' --range 2.. + + let expected = r#'match 5 { + 1.. => { print "More than zero" } + 0 => { print "Zero" } + -1 => { print "Negative one" } + -119283 => { print "Very negative" } +}'# | lines | str join "\n" + + assert equal $actual $expected +} + +@test +def str-align_ignore_lines_with_no_target [] { + let actual = [ + "let a = 1" + "let max = 2" + "# comment" + ] | str align '=' + + let expected = [ + "let a = 1" + "let max = 2" + "# comment" + ] | str join "\n" + + assert equal $actual $expected +} + +@test +def str-align_use_different_char [] { + let actual = [ + "=>" + "=====>" + ] | str align '>' -c '=' + + let expected = [ + "=====>" + "=====>" + ] | str join "\n" + + assert equal $actual $expected +} + +@test +def str-align_multiple_target_in_line [] { + let actual = [ + "print test # Hello # World" + "print hello there # test" + ] | str align '#' + + let expected = [ + "print test # Hello # World" + "print hello there # test" + ] | str join "\n" + + assert equal $actual $expected +} + +@test +def str-align_no_target [] { + + let expected = [ + "print test # Hello # World" + "print hello there # test" + ] | str join "\n" + + let actual = $expected | str align '=' + + assert equal $actual $expected +} + +@test +def str-align_empty_target_noop [] { + + let expected = [ + "print test # Hello # World" + "print hello there # test" + ] | str join "\n" + + let actual = $expected | str align '' + + assert equal $actual $expected +} + +@test +def str-align_empty_input_noop [] { + + let expected = "" + + let actual = [] | str align '=' + + assert equal $actual $expected +}