text: fix broken truncate with unicode and use the ellipsis character in LeftPadMaxLine

Michael Muré created

Change summary

util/text/left_padded.go      | 25 +++++++++------
util/text/left_padded_test.go | 56 +++++++++++++++++++++++++++++++++++++
2 files changed, 71 insertions(+), 10 deletions(-)

Detailed changes

util/text/left_padded.go 🔗

@@ -2,22 +2,27 @@ package text
 
 import (
 	"bytes"
+	"fmt"
 	"strings"
-	"unicode/utf8"
 )
 
 // LeftPadMaxLine pads a string on the left by a specified amount and pads the string on the right to fill the maxLength
-func LeftPadMaxLine(value string, maxValueLength, leftPad int) string {
-	valueLength := utf8.RuneCountInString(value)
-	if maxValueLength-leftPad >= valueLength {
-		return strings.Repeat(" ", leftPad) + value + strings.Repeat(" ", maxValueLength-valueLength-leftPad)
-	} else if maxValueLength-leftPad < valueLength {
-		tmp := strings.Trim(value[0:maxValueLength-leftPad-3], " ") + "..."
-		tmpLength := utf8.RuneCountInString(tmp)
-		return strings.Repeat(" ", leftPad) + tmp + strings.Repeat(" ", maxValueLength-tmpLength-leftPad)
+func LeftPadMaxLine(text string, length, leftPad int) string {
+	runes := []rune(text)
+
+	// truncate and ellipse if needed
+	if len(runes)+leftPad > length {
+		runes = append(runes[:(length-leftPad-1)], '…')
+	}
+
+	if len(runes)+leftPad < length {
+		runes = append(runes, []rune(strings.Repeat(" ", length-len(runes)-leftPad))...)
 	}
 
-	return value
+	return fmt.Sprintf("%s%s",
+		strings.Repeat(" ", leftPad),
+		string(runes),
+	)
 }
 
 // LeftPad left pad each line of the given text

util/text/left_padded_test.go 🔗

@@ -0,0 +1,56 @@
+package text
+
+import "testing"
+
+func TestLeftPadMaxLine(t *testing.T) {
+	cases := []struct {
+		input, output  string
+		maxValueLength int
+		leftPad        int
+	}{
+		{
+			"foo",
+			"foo ",
+			4,
+			0,
+		},
+		{
+			"foofoofoo",
+			"foo…",
+			4,
+			0,
+		},
+		{
+			"foo",
+			"foo       ",
+			10,
+			0,
+		},
+		{
+			"foo",
+			"  f…",
+			4,
+			2,
+		},
+		{
+			"foofoofoo",
+			"  foo…",
+			6,
+			2,
+		},
+		{
+			"foo",
+			"  foo     ",
+			10,
+			2,
+		},
+	}
+
+	for i, tc := range cases {
+		result := LeftPadMaxLine(tc.input, tc.maxValueLength, tc.leftPad)
+		if result != tc.output {
+			t.Fatalf("Case %d Input:\n\n`%s`\n\nExpected Output:\n\n`%s`\n\nActual Output:\n\n`%s`",
+				i, tc.input, tc.output, result)
+		}
+	}
+}