diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dfa872..f9e8ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [keep a changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +## [v1.0.2] - 2022-11-23 + +### Fixed +- Normalise whitespace (`\s` → ` `) in messages to avoid newlines and tabs breaking in-progress message updating. +- Assume details message to be preformatted, if it contains newline characters (`\n`). Preformatted message details are wrapped so that newline characters are maintained. This makes, for example, stack traces and console output in message details more readable. + ## [v1.0.1] - 2022-08-30 ### Fixed @@ -17,6 +23,7 @@ The format is based on [keep a changelog](https://keepachangelog.com/en/1.0.0/) - Extract and refactor livelog functionality from [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli.git). -[Unreleased]: https://github.com/UpCloudLtd/progress/compare/v1.0.1...HEAD +[Unreleased]: https://github.com/UpCloudLtd/progress/compare/v1.0.2...HEAD +[v1.0.2]: https://github.com/UpCloudLtd/progress/compare/v1.0.1...v1.0.2 [v1.0.1]: https://github.com/UpCloudLtd/progress/compare/v1.0.0...v1.0.1 [v1.0.0]: https://github.com/UpCloudLtd/progress/releases/tag/v1.0.0 diff --git a/go.mod b/go.mod index 0180991..d9b3489 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,12 @@ module github.com/UpCloudLtd/progress -go 1.18 +go 1.20 require ( - github.com/bradleyjkemp/cupaloy/v2 v2.7.0 - github.com/jedib0t/go-pretty/v6 v6.3.3 - github.com/stretchr/testify v1.8.0 - golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 + github.com/bradleyjkemp/cupaloy/v2 v2.8.0 + github.com/jedib0t/go-pretty/v6 v6.4.9 + github.com/stretchr/testify v1.8.4 + golang.org/x/term v0.14.0 ) require ( @@ -14,6 +14,6 @@ require ( github.com/mattn/go-runewidth v0.0.13 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect + golang.org/x/sys v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c0b5693..dc6c294 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ -github.com/bradleyjkemp/cupaloy/v2 v2.7.0 h1:AT0vOjO68RcLyenLCHOGZzSNiuto7ziqzq6Q1/3xzMQ= -github.com/bradleyjkemp/cupaloy/v2 v2.7.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/jedib0t/go-pretty/v6 v6.3.3 h1:shEWoyXqldeP54byATY3IczSfMC1b/UziOISaSxcvMQ= -github.com/jedib0t/go-pretty/v6 v6.3.3/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI= +github.com/jedib0t/go-pretty/v6 v6.4.9 h1:vZ6bjGg2eBSrJn365qlxGcaWu09Id+LHtrfDWlB2Usc= +github.com/jedib0t/go-pretty/v6 v6.4.9/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= @@ -18,13 +18,13 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= -golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Default_configuration b/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Default_configuration index 7cdb7c3..6bae140 100644 --- a/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Default_configuration +++ b/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Default_configuration @@ -2,6 +2,16 @@ ✓ Test success (100s)  100 s ✗ Test error, 10 % (1000s, % char in message)  > 999 s Error: Short dummy error message +✗ Test invalid message with tabs and newlines (5s, \n and \t chars in message)  5 s +✗ Test details with newlines (15s, \n chars in details)  15 s + Output: +  + + echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not  + be replaced with other whitespace when wrapping the text.' + Details with newlines are assumed to be preformatted. Thus, newline characters should not be repla + ced with other whitespace when wrapping the text. + + cat not-found + cat: not-found: No such file or directory ! Test warning (10s, long message) - Lorem ipsum dolor sit amet, consectetur adipiscing eli… > 999 s Error: Long dummy error message - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do  eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud  diff --git a/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Disable_colors b/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Disable_colors index 4ad2792..9c8e44f 100644 --- a/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Disable_colors +++ b/messages/.snapshots/TestMessageRenderer_RenderMessageStore-Disable_colors @@ -2,6 +2,16 @@ ✓ Test success (100s) 100 s ✗ Test error, 10 % (1000s, % char in message) > 999 s Error: Short dummy error message +✗ Test invalid message with tabs and newlines (5s, \n and \t chars in message) 5 s +✗ Test details with newlines (15s, \n chars in details) 15 s + Output: + + + echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not + be replaced with other whitespace when wrapping the text.' + Details with newlines are assumed to be preformatted. Thus, newline characters should not be repla + ced with other whitespace when wrapping the text. + + cat not-found + cat: not-found: No such file or directory ! Test warning (10s, long message) - Lorem ipsum dolor sit amet, consectetur adipiscing eli… > 999 s Error: Long dummy error message - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud diff --git a/messages/.snapshots/TestMessageRenderer_RenderMessageStore-No_indicator_colored_message b/messages/.snapshots/TestMessageRenderer_RenderMessageStore-No_indicator_colored_message index d2aca5f..a04ed9a 100644 --- a/messages/.snapshots/TestMessageRenderer_RenderMessageStore-No_indicator_colored_message +++ b/messages/.snapshots/TestMessageRenderer_RenderMessageStore-No_indicator_colored_message @@ -2,6 +2,16 @@ Test success (100s)  100 s Test error, 10 % (1000s, % char in message)  > 999 s Error: Short dummy error message +Test invalid message with tabs and newlines (5s, \n and \t chars in message)  5 s +Test details with newlines (15s, \n chars in details)  15 s +Output: + ++ echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not  +be replaced with other whitespace when wrapping the text.' +Details with newlines are assumed to be preformatted. Thus, newline characters should not be repla +ced with other whitespace when wrapping the text. ++ cat not-found +cat: not-found: No such file or directory Test warning (10s, long message) - Lorem ipsum dolor sit amet, consectetur adipiscing elit,… > 999 s Error: Long dummy error message - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do  eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud  diff --git a/messages/output.go b/messages/output.go index 3829d74..4a7952c 100644 --- a/messages/output.go +++ b/messages/output.go @@ -5,6 +5,7 @@ import ( "io" "math" "os" + "regexp" "strings" "github.com/UpCloudLtd/progress/terminal" @@ -18,6 +19,8 @@ const ( RenderStateDone RenderState = -1 ) +var whitespace = regexp.MustCompile(`\s`) + type OutputConfig struct { DefaultTextWidth int DisableColors bool @@ -171,7 +174,14 @@ func (cfg OutputConfig) GetMaxHeight() int { func (cfg OutputConfig) formatDetails(msg *Message) string { wrapWidth := cfg.GetMaxWidth() - 2 - details := text.WrapSoft(cfg.getDetailsColor().Sprint(msg.Details), wrapWidth) + + var details string + // If details contains newline characters, assume that details are preformatted (e.g., stack trace, console output, ...) + if strings.Contains(msg.Details, "\n") { + details = text.WrapText(cfg.getDetailsColor().Sprint(msg.Details), wrapWidth) + } else { + details = text.WrapSoft(cfg.getDetailsColor().Sprint(msg.Details), wrapWidth) + } if cfg.ShowStatusIndicator { return strings.ReplaceAll("\n"+details, "\n", "\n ") @@ -206,6 +216,7 @@ func (cfg OutputConfig) GetMessageText(msg *Message, renderState RenderState) st if maxMessageWidth < 0 { return "" } + message = whitespace.ReplaceAllString(message, " ") if len(message) > maxMessageWidth { message = fmt.Sprintf("%s…", message[:maxMessageWidth-1]) } else { diff --git a/messages/output_test.go b/messages/output_test.go index 2f5a828..538b027 100644 --- a/messages/output_test.go +++ b/messages/output_test.go @@ -11,6 +11,13 @@ import ( "github.com/stretchr/testify/assert" ) +const detailsWithNewlines = `Output: + ++ echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not be replaced with other whitespace when wrapping the text.' +Details with newlines are assumed to be preformatted. Thus, newline characters should not be replaced with other whitespace when wrapping the text. ++ cat not-found +cat: not-found: No such file or directory` + const loremIpsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." func TestMessageRenderer_RenderMessageStore(t *testing.T) { @@ -97,6 +104,23 @@ func TestMessageRenderer_RenderMessageStore(t *testing.T) { }) assert.NoError(t, err) + err = store.Add(messages.Message{ + Message: "Test\tinvalid\nmessage\twith\ntabs\tand\nnewlines (5s, \\n and \\t chars in message)", + Status: messages.MessageStatusError, + Started: time.Now().Add(time.Second * -5), + Finished: time.Now(), + }) + assert.NoError(t, err) + + err = store.Add(messages.Message{ + Message: "Test details with newlines (15s, \\n chars in details)", + Status: messages.MessageStatusError, + Details: detailsWithNewlines, + Started: time.Now().Add(time.Second * -15), + Finished: time.Now(), + }) + assert.NoError(t, err) + err = store.Add(messages.Message{ Message: "Test warning (10s, long message) - " + loremIpsum, Status: messages.MessageStatusWarning,