Skip to content

Commit

Permalink
progress: auto-trim output to fit terminal width (#290)
Browse files Browse the repository at this point in the history
  • Loading branch information
jedib0t authored Jan 7, 2024
1 parent f26db87 commit 50f17e4
Show file tree
Hide file tree
Showing 9 changed files with 194 additions and 67 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.17

# Download all the tools used in the steps that follow
- name: Set up Tools
run: |
go get -u github.com/fzipp/gocyclo/cmd/gocyclo
go get -u github.com/mattn/goveralls
go install github.com/fzipp/gocyclo/cmd/gocyclo@v0.6.0
go install github.com/mattn/goveralls@v0.0.12
# Run all the unit-tests
- name: Test
Expand Down
4 changes: 2 additions & 2 deletions cmd/demo-progress/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,11 @@ func main() {
// instantiate a Progress Writer and set up the options
pw := progress.NewWriter()
pw.SetAutoStop(*flagAutoStop)
pw.SetTrackerLength(25)
pw.SetMessageWidth(24)
pw.SetMessageLength(24)
pw.SetNumTrackersExpected(*flagNumTrackers)
pw.SetSortBy(progress.SortByPercentDsc)
pw.SetStyle(progress.StyleDefault)
pw.SetTrackerLength(25)
pw.SetTrackerPosition(progress.PositionRight)
pw.SetUpdateFrequency(time.Millisecond * 100)
pw.Style().Colors = progress.StyleColorsExample
Expand Down
20 changes: 15 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
module github.com/jedib0t/go-pretty/v6

go 1.16
go 1.17

require (
github.com/mattn/go-runewidth v0.0.13
github.com/pkg/profile v1.6.0
github.com/stretchr/testify v1.7.4
golang.org/x/sys v0.1.0
github.com/mattn/go-runewidth v0.0.15
github.com/pkg/profile v1.7.0
github.com/stretchr/testify v1.8.4
golang.org/x/sys v0.16.0
golang.org/x/term v0.16.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/felixge/fgprof v0.9.3 // indirect
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
29 changes: 21 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
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/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 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM=
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
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.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
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=
Expand Down
81 changes: 69 additions & 12 deletions progress/progress.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package progress

import (
"context"
"fmt"
"io"
"os"
"sync"
"time"
"unicode/utf8"

"github.com/jedib0t/go-pretty/v6/text"
"golang.org/x/term"
)

var (
Expand All @@ -23,24 +24,28 @@ var (
// Progress helps track progress for one or more tasks.
type Progress struct {
autoStop bool
done chan bool
lengthMessage int
lengthProgress int
lengthProgressOverall int
lengthTracker int
logsToRender []string
logsToRenderMutex sync.RWMutex
messageWidth int
numTrackersExpected int64
outputWriter io.Writer
overallTracker *Tracker
overallTrackerMutex sync.RWMutex
pinnedMessages []string
pinnedMessageMutex sync.RWMutex
pinnedMessageNumLines int
renderContext context.Context
renderContextCancel context.CancelFunc
renderInProgress bool
renderInProgressMutex sync.RWMutex
sortBy SortBy
style *Style
terminalWidth int
terminalWidthMutex sync.RWMutex
terminalWidthOverride int
trackerPosition Position
trackersActive []*Tracker
trackersActiveMutex sync.RWMutex
Expand Down Expand Up @@ -168,11 +173,19 @@ func (p *Progress) SetAutoStop(autoStop bool) {
p.autoStop = autoStop
}

// SetMessageWidth sets the (printed) length of the tracker message. Any message
// longer the specified width will be snipped abruptly. Any message shorter than
// SetMessageLength sets the (printed) length of the tracker message. Any
// message longer the specified length will be snipped. Any message shorter than
// the specified width will be padded with spaces.
func (p *Progress) SetMessageLength(length int) {
p.lengthMessage = length
}

// SetMessageWidth sets the (printed) length of the tracker message. Any message
// longer the specified width will be snipped. Any message shorter than the
// specified width will be padded with spaces.
// Deprecated: in favor of SetMessageLength(length)
func (p *Progress) SetMessageWidth(width int) {
p.messageWidth = width
p.lengthMessage = width
}

// SetNumTrackersExpected sets the expected number of trackers to be tracked.
Expand Down Expand Up @@ -209,6 +222,12 @@ func (p *Progress) SetStyle(style Style) {
p.style = &style
}

// SetTerminalWidth sets up a sticky terminal width and prevents the Progress
// Writer from polling for the real width during render.
func (p *Progress) SetTerminalWidth(width int) {
p.terminalWidthOverride = width
}

// SetTrackerLength sets the text-length of all the Trackers.
func (p *Progress) SetTrackerLength(length int) {
p.lengthTracker = length
Expand Down Expand Up @@ -266,7 +285,7 @@ func (p *Progress) ShowValue(show bool) {
// Stop stops the Render() logic that is in progress.
func (p *Progress) Stop() {
if p.IsRenderInProgress() {
p.done <- true
p.renderContextCancel()
}
}

Expand All @@ -279,6 +298,16 @@ func (p *Progress) Style() *Style {
return p.style
}

func (p *Progress) getTerminalWidth() int {
p.terminalWidthMutex.RLock()
defer p.terminalWidthMutex.RUnlock()

if p.terminalWidthOverride > 0 {
return p.terminalWidthOverride
}
return p.terminalWidth
}

func (p *Progress) initForRender() {
// pick a default style
p.Style()
Expand All @@ -287,7 +316,7 @@ func (p *Progress) initForRender() {
}

// reset the signals
p.done = make(chan bool, 1)
p.renderContext, p.renderContextCancel = context.WithCancel(context.Background())

// pick default lengths if no valid ones set
if p.lengthTracker <= 0 {
Expand All @@ -297,13 +326,15 @@ func (p *Progress) initForRender() {
// calculate length of the actual progress bar by discounting the left/right
// border/box chars
p.lengthProgress = p.lengthTracker -
utf8.RuneCountInString(p.style.Chars.BoxLeft) -
utf8.RuneCountInString(p.style.Chars.BoxRight)
p.lengthProgressOverall = p.messageWidth +
text.RuneWidthWithoutEscSequences(p.style.Chars.BoxLeft) -
text.RuneWidthWithoutEscSequences(p.style.Chars.BoxRight)
p.lengthProgressOverall = p.lengthMessage +
text.RuneWidthWithoutEscSequences(p.style.Options.Separator) +
p.lengthProgress + 1
if p.style.Visibility.Percentage {
p.lengthProgressOverall += text.RuneWidthWithoutEscSequences(fmt.Sprintf(p.style.Options.PercentFormat, 0.0))
p.lengthProgressOverall += text.RuneWidthWithoutEscSequences(
fmt.Sprintf(p.style.Options.PercentFormat, 0.0),
)
}

// if not output write has been set, output to STDOUT
Expand All @@ -315,6 +346,32 @@ func (p *Progress) initForRender() {
if p.updateFrequency <= 0 {
p.updateFrequency = DefaultUpdateFrequency
}

// get the current terminal size for preventing roll-overs, and do this in a
// background loop until end of render
go p.watchTerminalSize() // needs p.updateFrequency
}

func (p *Progress) updateTerminalSize() {
p.terminalWidthMutex.Lock()
defer p.terminalWidthMutex.Unlock()

p.terminalWidth, _, _ = term.GetSize(int(os.Stdout.Fd()))
}

func (p *Progress) watchTerminalSize() {
// once
p.updateTerminalSize()
// until end of time
ticker := time.NewTicker(time.Second / 10)
for {
select {
case <-ticker.C:
p.updateTerminalSize()
case <-p.renderContext.Done():
return
}
}
}

// renderHint has hints for the Render*() logic
Expand Down
17 changes: 13 additions & 4 deletions progress/progress_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package progress

import (
"context"
"math"
"os"
"testing"
Expand Down Expand Up @@ -143,6 +144,16 @@ func TestProgress_SetStyle(t *testing.T) {
assert.Equal(t, StyleCircle.Name, p.Style().Name)
}

func TestProgress_SetMessageLength(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.lengthMessage)

p.SetMessageLength(80)
assert.Equal(t, 80, p.lengthMessage)
p.SetMessageWidth(81)
assert.Equal(t, 81, p.lengthMessage)
}

func TestProgress_SetTrackerLength(t *testing.T) {
p := Progress{}
assert.Equal(t, 0, p.lengthTracker)
Expand Down Expand Up @@ -222,13 +233,11 @@ func TestProgress_ShowValue(t *testing.T) {
}

func TestProgress_Stop(t *testing.T) {
doneChannel := make(chan bool, 1)

p := Progress{}
p.done = doneChannel
p.renderContext, p.renderContextCancel = context.WithCancel(context.Background())
p.renderInProgress = true
p.Stop()
assert.True(t, <-doneChannel)
assert.NotNil(t, <-p.renderContext.Done())
}

func TestProgress_Style(t *testing.T) {
Expand Down
Loading

0 comments on commit 50f17e4

Please sign in to comment.