Skip to content

Commit 8c6dcbb

Browse files
authored
fix: normalise whitespace in messages, support preformatted details (#22)
1 parent 9ece10f commit 8c6dcbb

8 files changed

+91
-19
lines changed

CHANGELOG.md

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ The format is based on [keep a changelog](https://keepachangelog.com/en/1.0.0/)
66

77
## [Unreleased]
88

9+
## [v1.0.2] - 2022-11-23
10+
11+
### Fixed
12+
- Normalise whitespace (`\s`` `) in messages to avoid newlines and tabs breaking in-progress message updating.
13+
- 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.
14+
915
## [v1.0.1] - 2022-08-30
1016

1117
### Fixed
@@ -17,6 +23,7 @@ The format is based on [keep a changelog](https://keepachangelog.com/en/1.0.0/)
1723
- Extract and refactor livelog functionality from [UpCloud CLI (`upctl`)](https://github.com/UpCloudLtd/upcloud-cli.git).
1824

1925

20-
[Unreleased]: https://github.com/UpCloudLtd/progress/compare/v1.0.1...HEAD
26+
[Unreleased]: https://github.com/UpCloudLtd/progress/compare/v1.0.2...HEAD
27+
[v1.0.2]: https://github.com/UpCloudLtd/progress/compare/v1.0.1...v1.0.2
2128
[v1.0.1]: https://github.com/UpCloudLtd/progress/compare/v1.0.0...v1.0.1
2229
[v1.0.0]: https://github.com/UpCloudLtd/progress/releases/tag/v1.0.0

go.mod

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
module github.com/UpCloudLtd/progress
22

3-
go 1.18
3+
go 1.20
44

55
require (
6-
github.com/bradleyjkemp/cupaloy/v2 v2.7.0
7-
github.com/jedib0t/go-pretty/v6 v6.3.3
8-
github.com/stretchr/testify v1.8.0
9-
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
6+
github.com/bradleyjkemp/cupaloy/v2 v2.8.0
7+
github.com/jedib0t/go-pretty/v6 v6.4.9
8+
github.com/stretchr/testify v1.8.4
9+
golang.org/x/term v0.14.0
1010
)
1111

1212
require (
1313
github.com/davecgh/go-spew v1.1.1 // indirect
1414
github.com/mattn/go-runewidth v0.0.13 // indirect
1515
github.com/pmezard/go-difflib v1.0.0 // indirect
1616
github.com/rivo/uniseg v0.2.0 // indirect
17-
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
17+
golang.org/x/sys v0.14.0 // indirect
1818
gopkg.in/yaml.v3 v3.0.1 // indirect
1919
)

go.sum

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
github.com/bradleyjkemp/cupaloy/v2 v2.7.0 h1:AT0vOjO68RcLyenLCHOGZzSNiuto7ziqzq6Q1/3xzMQ=
2-
github.com/bradleyjkemp/cupaloy/v2 v2.7.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
1+
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
2+
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
33
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
44
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
55
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6-
github.com/jedib0t/go-pretty/v6 v6.3.3 h1:shEWoyXqldeP54byATY3IczSfMC1b/UziOISaSxcvMQ=
7-
github.com/jedib0t/go-pretty/v6 v6.3.3/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
6+
github.com/jedib0t/go-pretty/v6 v6.4.9 h1:vZ6bjGg2eBSrJn365qlxGcaWu09Id+LHtrfDWlB2Usc=
7+
github.com/jedib0t/go-pretty/v6 v6.4.9/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
88
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
99
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
1010
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
1818
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
1919
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
2020
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
21-
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
22-
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
23-
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
24-
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
25-
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
26-
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
27-
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
21+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
22+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
23+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
24+
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
25+
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
26+
golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8=
27+
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
2828
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
2929
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3030
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

messages/.snapshots/TestMessageRenderer_RenderMessageStore-Default_configuration

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
✓ Test success (100s)  100 s
33
✗ Test error, 10 % (1000s, % char in message)  > 999 s
44
Error: Short dummy error message
5+
✗ Test invalid message with tabs and newlines (5s, \n and \t chars in message)  5 s
6+
✗ Test details with newlines (15s, \n chars in details)  15 s
7+
Output:
8+

9+
+ echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not 
10+
be replaced with other whitespace when wrapping the text.'
11+
Details with newlines are assumed to be preformatted. Thus, newline characters should not be repla
12+
ced with other whitespace when wrapping the text.
13+
+ cat not-found
14+
cat: not-found: No such file or directory
515
! Test warning (10s, long message) - Lorem ipsum dolor sit amet, consectetur adipiscing eli… > 999 s
616
Error: Long dummy error message - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 
717
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 

messages/.snapshots/TestMessageRenderer_RenderMessageStore-Disable_colors

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
✓ Test success (100s) 100 s
33
✗ Test error, 10 % (1000s, % char in message) > 999 s
44
Error: Short dummy error message
5+
✗ Test invalid message with tabs and newlines (5s, \n and \t chars in message) 5 s
6+
✗ Test details with newlines (15s, \n chars in details) 15 s
7+
Output:
8+
9+
+ echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not
10+
be replaced with other whitespace when wrapping the text.'
11+
Details with newlines are assumed to be preformatted. Thus, newline characters should not be repla
12+
ced with other whitespace when wrapping the text.
13+
+ cat not-found
14+
cat: not-found: No such file or directory
515
! Test warning (10s, long message) - Lorem ipsum dolor sit amet, consectetur adipiscing eli… > 999 s
616
Error: Long dummy error message - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
717
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud

messages/.snapshots/TestMessageRenderer_RenderMessageStore-No_indicator_colored_message

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22
Test success (100s)  100 s
33
Test error, 10 % (1000s, % char in message)  > 999 s
44
Error: Short dummy error message
5+
Test invalid message with tabs and newlines (5s, \n and \t chars in message)  5 s
6+
Test details with newlines (15s, \n chars in details)  15 s
7+
Output:
8+

9+
+ echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not 
10+
be replaced with other whitespace when wrapping the text.'
11+
Details with newlines are assumed to be preformatted. Thus, newline characters should not be repla
12+
ced with other whitespace when wrapping the text.
13+
+ cat not-found
14+
cat: not-found: No such file or directory
515
Test warning (10s, long message) - Lorem ipsum dolor sit amet, consectetur adipiscing elit,… > 999 s
616
Error: Long dummy error message - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 
717
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud 

messages/output.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io"
66
"math"
77
"os"
8+
"regexp"
89
"strings"
910

1011
"github.com/UpCloudLtd/progress/terminal"
@@ -18,6 +19,8 @@ const (
1819
RenderStateDone RenderState = -1
1920
)
2021

22+
var whitespace = regexp.MustCompile(`\s`)
23+
2124
type OutputConfig struct {
2225
DefaultTextWidth int
2326
DisableColors bool
@@ -171,7 +174,14 @@ func (cfg OutputConfig) GetMaxHeight() int {
171174

172175
func (cfg OutputConfig) formatDetails(msg *Message) string {
173176
wrapWidth := cfg.GetMaxWidth() - 2
174-
details := text.WrapSoft(cfg.getDetailsColor().Sprint(msg.Details), wrapWidth)
177+
178+
var details string
179+
// If details contains newline characters, assume that details are preformatted (e.g., stack trace, console output, ...)
180+
if strings.Contains(msg.Details, "\n") {
181+
details = text.WrapText(cfg.getDetailsColor().Sprint(msg.Details), wrapWidth)
182+
} else {
183+
details = text.WrapSoft(cfg.getDetailsColor().Sprint(msg.Details), wrapWidth)
184+
}
175185

176186
if cfg.ShowStatusIndicator {
177187
return strings.ReplaceAll("\n"+details, "\n", "\n ")
@@ -206,6 +216,7 @@ func (cfg OutputConfig) GetMessageText(msg *Message, renderState RenderState) st
206216
if maxMessageWidth < 0 {
207217
return ""
208218
}
219+
message = whitespace.ReplaceAllString(message, " ")
209220
if len(message) > maxMessageWidth {
210221
message = fmt.Sprintf("%s…", message[:maxMessageWidth-1])
211222
} else {

messages/output_test.go

+24
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
)
1313

14+
const detailsWithNewlines = `Output:
15+
16+
+ echo 'Details with newlines are assumed to be preformatted. Thus, newline characters should not be replaced with other whitespace when wrapping the text.'
17+
Details with newlines are assumed to be preformatted. Thus, newline characters should not be replaced with other whitespace when wrapping the text.
18+
+ cat not-found
19+
cat: not-found: No such file or directory`
20+
1421
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."
1522

1623
func TestMessageRenderer_RenderMessageStore(t *testing.T) {
@@ -97,6 +104,23 @@ func TestMessageRenderer_RenderMessageStore(t *testing.T) {
97104
})
98105
assert.NoError(t, err)
99106

107+
err = store.Add(messages.Message{
108+
Message: "Test\tinvalid\nmessage\twith\ntabs\tand\nnewlines (5s, \\n and \\t chars in message)",
109+
Status: messages.MessageStatusError,
110+
Started: time.Now().Add(time.Second * -5),
111+
Finished: time.Now(),
112+
})
113+
assert.NoError(t, err)
114+
115+
err = store.Add(messages.Message{
116+
Message: "Test details with newlines (15s, \\n chars in details)",
117+
Status: messages.MessageStatusError,
118+
Details: detailsWithNewlines,
119+
Started: time.Now().Add(time.Second * -15),
120+
Finished: time.Now(),
121+
})
122+
assert.NoError(t, err)
123+
100124
err = store.Add(messages.Message{
101125
Message: "Test warning (10s, long message) - " + loremIpsum,
102126
Status: messages.MessageStatusWarning,

0 commit comments

Comments
 (0)