Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

text: process Carriage-Return codes correctly #292

Merged
merged 3 commits into from Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 2 additions & 3 deletions table/render_test.go
Expand Up @@ -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(), `
Expand All @@ -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 | |
+------+------------+-----------+--------+-----------------------------+`)
Expand Down
51 changes: 35 additions & 16 deletions text/string.go
@@ -1,7 +1,6 @@
package text

import (
"regexp"
"strings"
"unicode/utf8"

Expand Down Expand Up @@ -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.
Expand Down
7 changes: 5 additions & 2 deletions text/string_test.go
Expand Up @@ -140,20 +140,23 @@ 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) {
assert.Equal(t, "abc", ProcessCRLF("abc"))
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() {
Expand Down