diff --git a/table/render_test.go b/table/render_test.go index 9dd9562..86df53a 100644 --- a/table/render_test.go +++ b/table/render_test.go @@ -620,7 +620,7 @@ func TestTable_Render_CRLF(t *testing.T) { tw := NewWriter() tw.AppendHeader(testHeader) tw.AppendRows(testRows) - tw.AppendRow(Row{5000, "Night", "King", 10000, "Was once a\r\nMortal \rMan"}) + tw.AppendRow(Row{5000, "Night", "King", 10000, "Was once a Mortal\rMan"}) tw.AppendFooter(testFooter) compareOutput(t, tw.Render(), ` @@ -630,8 +630,7 @@ func TestTable_Render_CRLF(t *testing.T) { | 1 | Arya | Stark | 3000 | | | 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! | | 300 | Tyrion | Lannister | 5000 | | -| 5000 | Night | King | 10000 | Was once a | -| | | | | Man | +| 5000 | Night | King | 10000 | Man once a Mortal | +------+------------+-----------+--------+-----------------------------+ | | | TOTAL | 10000 | | +------+------------+-----------+--------+-----------------------------+`) diff --git a/text/string.go b/text/string.go index a7b337a..dbc3242 100644 --- a/text/string.go +++ b/text/string.go @@ -1,7 +1,6 @@ package text import ( - "regexp" "strings" "unicode/utf8" @@ -108,27 +107,47 @@ func Pad(str string, maxLen int, paddingChar rune) string { return str } -var ( - reCarriageReturn = regexp.MustCompile(`(.*)\r`) -) - -// ProcessCRLF converts "\r\n" to "\n", and erases everything preceding a lone -// "\r" in each line of the string. +// ProcessCRLF converts "\r\n" to "\n", and processes lone "\r" by moving the +// cursor/carriage to the start of the line and overwrites the contents +// accordingly. Ex.: +// +// ProcessCRLF("abc") == "abc" +// ProcessCRLF("abc\r\ndef") == "abc\ndef" +// ProcessCRLF("abc\r\ndef\rghi") == "abc\nghi" +// ProcessCRLF("abc\r\ndef\rghi\njkl") == "abc\nghi\njkl" +// ProcessCRLF("abc\r\ndef\rghi\njkl\r") == "abc\nghi\njkl" +// ProcessCRLF("abc\r\ndef\rghi\rjkl\rmn") == "abc\nmnl" func ProcessCRLF(str string) string { str = strings.ReplaceAll(str, "\r\n", "\n") + if !strings.Contains(str, "\r") { + return str + } - // process \r by erasing everything preceding it in the line - if strings.Contains(str, "\r") { - lines := strings.Split(str, "\n") - for idx := range lines { - for reCarriageReturn.MatchString(lines[idx]) { - lines[idx] = reCarriageReturn.ReplaceAllString(lines[idx], "") + lines := strings.Split(str, "\n") + for lineIdx, line := range lines { + if !strings.Contains(line, "\r") { + continue + } + + lineRunes, newLineRunes := []rune(line), make([]rune, 0) + for idx, realIdx := 0, 0; idx < len(lineRunes); idx++ { + // if a CR, move "cursor" back to beginning of line + if lineRunes[idx] == '\r' { + realIdx = 0 + continue + } + + // if cursor is not at end, overwrite + if realIdx < len(newLineRunes) { + newLineRunes[realIdx] = lineRunes[idx] + } else { // else append + newLineRunes = append(newLineRunes, lineRunes[idx]) } + realIdx++ } - str = strings.Join(lines, "\n") + lines[lineIdx] = string(newLineRunes) } - - return str + return strings.Join(lines, "\n") } // RepeatAndTrim repeats the given string until it is as long as maxRunes. diff --git a/text/string_test.go b/text/string_test.go index 42e1e0c..ac1b1b9 100644 --- a/text/string_test.go +++ b/text/string_test.go @@ -140,12 +140,14 @@ func ExampleProcessCRLF() { fmt.Printf("%#v\n", ProcessCRLF("abc\r\ndef\rghi")) fmt.Printf("%#v\n", ProcessCRLF("abc\r\ndef\rghi\njkl")) fmt.Printf("%#v\n", ProcessCRLF("abc\r\ndef\rghi\njkl\r")) + fmt.Printf("%#v\n", ProcessCRLF("abc\r\ndef\rghi\rjkl\rmn")) // Output: "abc" // "abc\ndef" // "abc\nghi" // "abc\nghi\njkl" - // "abc\nghi\n" + // "abc\nghi\njkl" + // "abc\nmnl" } func TestProcessCRLF(t *testing.T) { @@ -153,7 +155,8 @@ func TestProcessCRLF(t *testing.T) { assert.Equal(t, "abc\ndef", ProcessCRLF("abc\r\ndef")) assert.Equal(t, "abc\nghi", ProcessCRLF("abc\r\ndef\rghi")) assert.Equal(t, "abc\nghi\njkl", ProcessCRLF("abc\r\ndef\rghi\njkl")) - assert.Equal(t, "abc\nghi\n", ProcessCRLF("abc\r\ndef\rghi\njkl\r")) + assert.Equal(t, "abc\nghi\njkl", ProcessCRLF("abc\r\ndef\rghi\njkl\r")) + assert.Equal(t, "abc\nmnl", ProcessCRLF("abc\r\ndef\rghi\rjkl\rmn")) } func ExampleRepeatAndTrim() {