• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jedib0t / go-pretty / 19111272497

05 Nov 2025 05:50PM UTC coverage: 99.773% (-0.1%) from 99.874%
19111272497

Pull #375

github

web-flow
Merge branch 'main' into stdout
Pull Request #375: Only adjust terminal size when the output writer is stdout

5 of 5 new or added lines in 1 file covered. (100.0%)

4 existing lines in 2 files now uncovered.

3961 of 3970 relevant lines covered (99.77%)

1.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.73
/progress/tracker.go
1
package progress
2

3
import (
4
        "math"
5
        "sync"
6
        "time"
7
)
8

9
// Tracker helps track the progress of a single task. The way to use it is to
10
// instantiate a Tracker with a valid Message, a valid (expected) Total, and
11
// Units values. This should then be fed to the Progress Writer with the
12
// Writer.AppendTracker() method. When the task that is being done has progress,
13
// increment the value using the Tracker.Increment(value) method.
14
type Tracker struct {
15
        // AutoStopDisabled prevents the tracker from marking itself as done when
16
        // the value goes beyond the total (if set). Note that this means that a
17
        // manual call to MarkAsDone or MarkAsErrored is expected.
18
        AutoStopDisabled bool
19
        // Message should contain a short description of the "task"; please note
20
        // that this should NOT be updated in the middle of progress - you should
21
        // instead use UpdateMessage() to do this safely without hitting any race
22
        // conditions
23
        Message string
24
        // DeferStart prevents the tracker from starting immediately when appended.
25
        // It will be rendered but remain dormant until Start, Increment,
26
        // IncrementWithError or SetValue is called.
27
        DeferStart bool
28
        // ExpectedDuration tells how long this task is expected to take; and will
29
        // be used in calculation of the ETA value
30
        ExpectedDuration time.Duration
31
        // RemoveOnCompletion tells the Progress Bar to remove this tracker when
32
        // it is done, instead of rendering a "completed" line
33
        RemoveOnCompletion bool
34
        // Total should be set to the (expected) Total/Final value to be reached
35
        Total int64
36
        // Units defines the type of the "value" being tracked
37
        Units Units
38

39
        done      bool
40
        err       bool
41
        mutex     sync.RWMutex
42
        timeStart time.Time
43
        timeStop  time.Time
44
        value     int64
45
        minETA    time.Duration
46
}
47

48
// ETA returns the expected time of "arrival" or completion of this tracker. It
49
// is an estimate and is not guaranteed.
50
func (t *Tracker) ETA() time.Duration {
1✔
51
        t.mutex.RLock()
1✔
52
        defer t.mutex.RUnlock()
1✔
53

1✔
54
        if t.timeStart.IsZero() {
2✔
55
                return time.Duration(0)
1✔
56
        }
1✔
57

58
        timeTaken := time.Since(t.timeStart)
1✔
59
        if t.ExpectedDuration > time.Duration(0) && t.ExpectedDuration > timeTaken {
2✔
60
                return t.ExpectedDuration - timeTaken
1✔
61
        }
1✔
62

63
        pDone := int64(t.percentDoneWithoutLock())
1✔
64
        if pDone == 0 {
2✔
65
                return time.Duration(0)
1✔
66
        }
1✔
67
        eta := time.Duration((int64(timeTaken) / pDone) * (100 - pDone))
1✔
68
        if eta < t.minETA {
1✔
UNCOV
69
                eta = t.minETA
×
UNCOV
70
        }
×
71
        return eta
1✔
72
}
73

74
// Increment updates the current value of the task being tracked.
75
func (t *Tracker) Increment(value int64) {
1✔
76
        t.mutex.Lock()
1✔
77
        t.incrementWithoutLock(value)
1✔
78
        t.mutex.Unlock()
1✔
79
}
1✔
80

81
// IncrementWithError updates the current value of the task being tracked and
82
// marks that an error occurred.
83
func (t *Tracker) IncrementWithError(value int64) {
1✔
84
        t.mutex.Lock()
1✔
85
        t.incrementWithoutLock(value)
1✔
86
        t.err = true
1✔
87
        t.mutex.Unlock()
1✔
88
}
1✔
89

90
// IsStarted true if the tracker has started, false when using DeferStart
91
// prior to Start, Increment, IncrementWithError or SetValue being called.
92
func (t *Tracker) IsStarted() bool {
1✔
93
        t.mutex.RLock()
1✔
94
        defer t.mutex.RUnlock()
1✔
95

1✔
96
        return !t.timeStart.IsZero()
1✔
97
}
1✔
98

99
// IsDone returns true if the tracker is done (value has reached the expected
100
// Total set during initialization).
101
func (t *Tracker) IsDone() bool {
1✔
102
        t.mutex.RLock()
1✔
103
        defer t.mutex.RUnlock()
1✔
104

1✔
105
        return t.done
1✔
106
}
1✔
107

108
// IsErrored true if an error was set with IncrementWithError or MarkAsErrored.
109
func (t *Tracker) IsErrored() bool {
1✔
110
        t.mutex.RLock()
1✔
111
        defer t.mutex.RUnlock()
1✔
112

1✔
113
        return t.err
1✔
114
}
1✔
115

116
// IsIndeterminate returns true if the tracker is indeterminate; i.e., the total
117
// is unknown and it is impossible to auto-calculate if tracking is done.
118
func (t *Tracker) IsIndeterminate() bool {
1✔
119
        t.mutex.RLock()
1✔
120
        defer t.mutex.RUnlock()
1✔
121

1✔
122
        return t.Total == 0
1✔
123
}
1✔
124

125
// MarkAsDone forces completion of the tracker by updating the current value as
126
// the expected Total value.
127
func (t *Tracker) MarkAsDone() {
1✔
128
        t.mutex.Lock()
1✔
129
        t.Total = t.value
1✔
130
        t.stop()
1✔
131
        t.mutex.Unlock()
1✔
132
}
1✔
133

134
// MarkAsErrored forces completion of the tracker by updating the current value as
135
// the expected Total value, and recording as error.
136
func (t *Tracker) MarkAsErrored() {
1✔
137
        t.mutex.Lock()
1✔
138
        // only update error if not done and if not previously set
1✔
139
        if !t.done {
2✔
140
                t.Total = t.value
1✔
141
                t.err = true
1✔
142
                t.stop()
1✔
143
        }
1✔
144
        t.mutex.Unlock()
1✔
145
}
146

147
// PercentDone returns the currently completed percentage value.
148
func (t *Tracker) PercentDone() float64 {
1✔
149
        t.mutex.RLock()
1✔
150
        defer t.mutex.RUnlock()
1✔
151
        return t.percentDoneWithoutLock()
1✔
152
}
1✔
153

154
func (t *Tracker) percentDoneWithoutLock() float64 {
1✔
155
        if t.Total == 0 {
2✔
156
                return 0
1✔
157
        }
1✔
158
        return float64(t.value) * 100.0 / float64(t.Total)
1✔
159
}
160

161
// Reset resets the tracker to its initial state.
162
func (t *Tracker) Reset() {
1✔
163
        t.mutex.Lock()
1✔
164
        t.done = false
1✔
165
        t.err = false
1✔
166
        t.timeStart = time.Time{}
1✔
167
        t.timeStop = time.Time{}
1✔
168
        t.value = 0
1✔
169
        t.mutex.Unlock()
1✔
170
}
1✔
171

172
// SetValue sets the value of the tracker and re-calculates if the tracker is
173
// "done".
174
func (t *Tracker) SetValue(value int64) {
1✔
175
        t.mutex.Lock()
1✔
176
        t.done = false
1✔
177
        t.timeStop = time.Time{}
1✔
178
        t.value = 0
1✔
179
        t.incrementWithoutLock(value)
1✔
180
        t.mutex.Unlock()
1✔
181
}
1✔
182

183
// Start starts the tracking for the case when DeferStart=false.
184
func (t *Tracker) Start() {
1✔
185
        if t.timeStart.IsZero() {
2✔
186
                t.start()
1✔
187
        }
1✔
188
}
189

190
// UpdateMessage updates the message string.
191
func (t *Tracker) UpdateMessage(msg string) {
1✔
192
        t.mutex.Lock()
1✔
193
        t.Message = msg
1✔
194
        t.mutex.Unlock()
1✔
195
}
1✔
196

197
// UpdateTotal updates the total value.
198
func (t *Tracker) UpdateTotal(total int64) {
1✔
199
        t.mutex.Lock()
1✔
200
        if total > t.Total {
2✔
201
                t.done = false
1✔
202
        }
1✔
203
        t.Total = total
1✔
204
        t.mutex.Unlock()
1✔
205
}
206

207
// Value returns the current value of the tracker.
208
func (t *Tracker) Value() int64 {
1✔
209
        t.mutex.RLock()
1✔
210
        defer t.mutex.RUnlock()
1✔
211
        return t.value
1✔
212
}
1✔
213

214
func (t *Tracker) incrementWithoutLock(value int64) {
1✔
215
        if !t.done {
2✔
216
                if t.timeStart.IsZero() {
2✔
217
                        t.startWithoutLock()
1✔
218
                }
1✔
219
                t.value += value
1✔
220
                if !t.AutoStopDisabled && t.Total > 0 && t.value >= t.Total {
2✔
221
                        t.stop()
1✔
222
                }
1✔
223
        }
224
}
225

226
func (t *Tracker) message() string {
1✔
227
        t.mutex.RLock()
1✔
228
        defer t.mutex.RUnlock()
1✔
229
        return t.Message
1✔
230
}
1✔
231

232
func (t *Tracker) start() {
1✔
233
        t.mutex.Lock()
1✔
234
        t.startWithoutLock()
1✔
235
        t.mutex.Unlock()
1✔
236
}
1✔
237

238
func (t *Tracker) startWithoutLock() {
1✔
239
        if t.Total < 0 {
2✔
240
                t.Total = math.MaxInt64
1✔
241
        }
1✔
242
        t.done = false
1✔
243
        t.err = false
1✔
244
        t.timeStart = time.Now()
1✔
245
}
246

247
// this must be called with the mutex held with a write lock
248
func (t *Tracker) stop() {
1✔
249
        t.done = true
1✔
250
        t.timeStop = time.Now()
1✔
251
        if t.value > t.Total {
2✔
252
                t.Total = t.value
1✔
253
        }
1✔
254
}
255

256
func (t *Tracker) valueAndTotal() (int64, int64) {
1✔
257
        t.mutex.RLock()
1✔
258
        value := t.value
1✔
259
        total := t.Total
1✔
260
        t.mutex.RUnlock()
1✔
261
        return value, total
1✔
262
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc