1// Simple console progress bars 2package pb 3 4import ( 5 "fmt" 6 "io" 7 "math" 8 "strings" 9 "sync" 10 "sync/atomic" 11 "time" 12 "unicode/utf8" 13) 14 15// Current version 16const Version = "1.0.25" 17 18const ( 19 // Default refresh rate - 200ms 20 DEFAULT_REFRESH_RATE = time.Millisecond * 200 21 FORMAT = "[=>-]" 22) 23 24// DEPRECATED 25// variables for backward compatibility, from now do not work 26// use pb.Format and pb.SetRefreshRate 27var ( 28 DefaultRefreshRate = DEFAULT_REFRESH_RATE 29 BarStart, BarEnd, Empty, Current, CurrentN string 30) 31 32// Create new progress bar object 33func New(total int) *ProgressBar { 34 return New64(int64(total)) 35} 36 37// Create new progress bar object using int64 as total 38func New64(total int64) *ProgressBar { 39 pb := &ProgressBar{ 40 Total: total, 41 RefreshRate: DEFAULT_REFRESH_RATE, 42 ShowPercent: true, 43 ShowCounters: true, 44 ShowBar: true, 45 ShowTimeLeft: true, 46 ShowElapsedTime: false, 47 ShowFinalTime: true, 48 Units: U_NO, 49 ManualUpdate: false, 50 finish: make(chan struct{}), 51 } 52 return pb.Format(FORMAT) 53} 54 55// Create new object and start 56func StartNew(total int) *ProgressBar { 57 return New(total).Start() 58} 59 60// Callback for custom output 61// For example: 62// bar.Callback = func(s string) { 63// mySuperPrint(s) 64// } 65// 66type Callback func(out string) 67 68type ProgressBar struct { 69 current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) 70 previous int64 71 72 Total int64 73 RefreshRate time.Duration 74 ShowPercent, ShowCounters bool 75 ShowSpeed, ShowTimeLeft, ShowBar bool 76 ShowFinalTime, ShowElapsedTime bool 77 Output io.Writer 78 Callback Callback 79 NotPrint bool 80 Units Units 81 Width int 82 ForceWidth bool 83 ManualUpdate bool 84 AutoStat bool 85 86 // Default width for the time box. 87 UnitsWidth int 88 TimeBoxWidth int 89 90 finishOnce sync.Once //Guards isFinish 91 finish chan struct{} 92 isFinish bool 93 94 startTime time.Time 95 startValue int64 96 97 changeTime time.Time 98 99 prefix, postfix string 100 101 mu sync.Mutex 102 lastPrint string 103 104 BarStart string 105 BarEnd string 106 Empty string 107 Current string 108 CurrentN string 109 110 AlwaysUpdate bool 111} 112 113// Start print 114func (pb *ProgressBar) Start() *ProgressBar { 115 pb.startTime = time.Now() 116 pb.startValue = atomic.LoadInt64(&pb.current) 117 if atomic.LoadInt64(&pb.Total) == 0 { 118 pb.ShowTimeLeft = false 119 pb.ShowPercent = false 120 pb.AutoStat = false 121 } 122 if !pb.ManualUpdate { 123 pb.Update() // Initial printing of the bar before running the bar refresher. 124 go pb.refresher() 125 } 126 return pb 127} 128 129// Increment current value 130func (pb *ProgressBar) Increment() int { 131 return pb.Add(1) 132} 133 134// Get current value 135func (pb *ProgressBar) Get() int64 { 136 c := atomic.LoadInt64(&pb.current) 137 return c 138} 139 140// Set current value 141func (pb *ProgressBar) Set(current int) *ProgressBar { 142 return pb.Set64(int64(current)) 143} 144 145// Set64 sets the current value as int64 146func (pb *ProgressBar) Set64(current int64) *ProgressBar { 147 atomic.StoreInt64(&pb.current, current) 148 return pb 149} 150 151// Add to current value 152func (pb *ProgressBar) Add(add int) int { 153 return int(pb.Add64(int64(add))) 154} 155 156func (pb *ProgressBar) Add64(add int64) int64 { 157 return atomic.AddInt64(&pb.current, add) 158} 159 160// Set prefix string 161func (pb *ProgressBar) Prefix(prefix string) *ProgressBar { 162 pb.prefix = prefix 163 return pb 164} 165 166// Set postfix string 167func (pb *ProgressBar) Postfix(postfix string) *ProgressBar { 168 pb.postfix = postfix 169 return pb 170} 171 172// Set custom format for bar 173// Example: bar.Format("[=>_]") 174// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter 175func (pb *ProgressBar) Format(format string) *ProgressBar { 176 var formatEntries []string 177 if utf8.RuneCountInString(format) == 5 { 178 formatEntries = strings.Split(format, "") 179 } else { 180 formatEntries = strings.Split(format, "\x00") 181 } 182 if len(formatEntries) == 5 { 183 pb.BarStart = formatEntries[0] 184 pb.BarEnd = formatEntries[4] 185 pb.Empty = formatEntries[3] 186 pb.Current = formatEntries[1] 187 pb.CurrentN = formatEntries[2] 188 } 189 return pb 190} 191 192// Set bar refresh rate 193func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar { 194 pb.RefreshRate = rate 195 return pb 196} 197 198// Set units 199// bar.SetUnits(U_NO) - by default 200// bar.SetUnits(U_BYTES) - for Mb, Kb, etc 201func (pb *ProgressBar) SetUnits(units Units) *ProgressBar { 202 pb.Units = units 203 return pb 204} 205 206// Set max width, if width is bigger than terminal width, will be ignored 207func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar { 208 pb.Width = width 209 pb.ForceWidth = false 210 return pb 211} 212 213// Set bar width 214func (pb *ProgressBar) SetWidth(width int) *ProgressBar { 215 pb.Width = width 216 pb.ForceWidth = true 217 return pb 218} 219 220// End print 221func (pb *ProgressBar) Finish() { 222 //Protect multiple calls 223 pb.finishOnce.Do(func() { 224 close(pb.finish) 225 pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current)) 226 pb.mu.Lock() 227 defer pb.mu.Unlock() 228 switch { 229 case pb.Output != nil: 230 fmt.Fprintln(pb.Output) 231 case !pb.NotPrint: 232 fmt.Println() 233 } 234 pb.isFinish = true 235 }) 236} 237 238// IsFinished return boolean 239func (pb *ProgressBar) IsFinished() bool { 240 pb.mu.Lock() 241 defer pb.mu.Unlock() 242 return pb.isFinish 243} 244 245// End print and write string 'str' 246func (pb *ProgressBar) FinishPrint(str string) { 247 pb.Finish() 248 if pb.Output != nil { 249 fmt.Fprintln(pb.Output, str) 250 } else { 251 fmt.Println(str) 252 } 253} 254 255// implement io.Writer 256func (pb *ProgressBar) Write(p []byte) (n int, err error) { 257 n = len(p) 258 pb.Add(n) 259 return 260} 261 262// implement io.Reader 263func (pb *ProgressBar) Read(p []byte) (n int, err error) { 264 n = len(p) 265 pb.Add(n) 266 return 267} 268 269// Create new proxy reader over bar 270// Takes io.Reader or io.ReadCloser 271func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { 272 return &Reader{r, pb} 273} 274 275func (pb *ProgressBar) write(total, current int64) { 276 width := pb.GetWidth() 277 278 var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string 279 280 // percents 281 if pb.ShowPercent { 282 var percent float64 283 if total > 0 { 284 percent = float64(current) / (float64(total) / float64(100)) 285 } else { 286 percent = float64(current) / float64(100) 287 } 288 percentBox = fmt.Sprintf(" %6.02f%%", percent) 289 } 290 291 // counters 292 if pb.ShowCounters { 293 current := Format(current).To(pb.Units).Width(pb.UnitsWidth) 294 if total > 0 { 295 totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth) 296 countersBox = fmt.Sprintf(" %s / %s ", current, totalS) 297 } else { 298 countersBox = fmt.Sprintf(" %s / ? ", current) 299 } 300 } 301 302 // time left 303 pb.mu.Lock() 304 currentFromStart := current - pb.startValue 305 fromStart := time.Now().Sub(pb.startTime) 306 lastChangeTime := pb.changeTime 307 fromChange := lastChangeTime.Sub(pb.startTime) 308 pb.mu.Unlock() 309 310 if pb.ShowElapsedTime { 311 timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second) 312 } 313 314 select { 315 case <-pb.finish: 316 if pb.ShowFinalTime { 317 var left time.Duration 318 left = (fromStart / time.Second) * time.Second 319 timeLeftBox = fmt.Sprintf(" %s", left.String()) 320 } 321 default: 322 if pb.ShowTimeLeft && currentFromStart > 0 { 323 perEntry := fromChange / time.Duration(currentFromStart) 324 var left time.Duration 325 if total > 0 { 326 left = time.Duration(total-currentFromStart) * perEntry 327 left -= time.Since(lastChangeTime) 328 left = (left / time.Second) * time.Second 329 } else { 330 left = time.Duration(currentFromStart) * perEntry 331 left = (left / time.Second) * time.Second 332 } 333 if left > 0 { 334 timeLeft := Format(int64(left)).To(U_DURATION).String() 335 timeLeftBox = fmt.Sprintf(" %s", timeLeft) 336 } 337 } 338 } 339 340 if len(timeLeftBox) < pb.TimeBoxWidth { 341 timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox) 342 } 343 344 // speed 345 if pb.ShowSpeed && currentFromStart > 0 { 346 fromStart := time.Now().Sub(pb.startTime) 347 speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second)) 348 speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() 349 } 350 351 barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) 352 // bar 353 if pb.ShowBar { 354 size := width - barWidth 355 if size > 0 { 356 if total > 0 { 357 curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size))) 358 emptySize := size - curSize 359 barBox = pb.BarStart 360 if emptySize < 0 { 361 emptySize = 0 362 } 363 if curSize > size { 364 curSize = size 365 } 366 367 cursorLen := escapeAwareRuneCountInString(pb.Current) 368 if emptySize <= 0 { 369 barBox += strings.Repeat(pb.Current, curSize/cursorLen) 370 } else if curSize > 0 { 371 cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN) 372 cursorRepetitions := (curSize - cursorEndLen) / cursorLen 373 barBox += strings.Repeat(pb.Current, cursorRepetitions) 374 barBox += pb.CurrentN 375 } 376 377 emptyLen := escapeAwareRuneCountInString(pb.Empty) 378 barBox += strings.Repeat(pb.Empty, emptySize/emptyLen) 379 barBox += pb.BarEnd 380 } else { 381 pos := size - int(current)%int(size) 382 barBox = pb.BarStart 383 if pos-1 > 0 { 384 barBox += strings.Repeat(pb.Empty, pos-1) 385 } 386 barBox += pb.Current 387 if size-pos-1 > 0 { 388 barBox += strings.Repeat(pb.Empty, size-pos-1) 389 } 390 barBox += pb.BarEnd 391 } 392 } 393 } 394 395 // check len 396 out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix 397 if cl := escapeAwareRuneCountInString(out); cl < width { 398 end = strings.Repeat(" ", width-cl) 399 } 400 401 // and print! 402 pb.mu.Lock() 403 defer pb.mu.Unlock() 404 pb.lastPrint = out + end 405 isFinish := pb.isFinish 406 407 switch { 408 case isFinish: 409 return 410 case pb.Output != nil: 411 fmt.Fprint(pb.Output, "\r"+out+end) 412 case pb.Callback != nil: 413 pb.Callback(out + end) 414 case !pb.NotPrint: 415 fmt.Print("\r" + out + end) 416 } 417} 418 419// GetTerminalWidth - returns terminal width for all platforms. 420func GetTerminalWidth() (int, error) { 421 return terminalWidth() 422} 423 424func (pb *ProgressBar) GetWidth() int { 425 if pb.ForceWidth { 426 return pb.Width 427 } 428 429 width := pb.Width 430 termWidth, _ := terminalWidth() 431 if width == 0 || termWidth <= width { 432 width = termWidth 433 } 434 435 return width 436} 437 438// Write the current state of the progressbar 439func (pb *ProgressBar) Update() { 440 c := atomic.LoadInt64(&pb.current) 441 p := atomic.LoadInt64(&pb.previous) 442 t := atomic.LoadInt64(&pb.Total) 443 if p != c { 444 pb.mu.Lock() 445 pb.changeTime = time.Now() 446 pb.mu.Unlock() 447 atomic.StoreInt64(&pb.previous, c) 448 } 449 pb.write(t, c) 450 if pb.AutoStat { 451 if c == 0 { 452 pb.startTime = time.Now() 453 pb.startValue = 0 454 } else if c >= t && pb.isFinish != true { 455 pb.Finish() 456 } 457 } 458} 459 460// String return the last bar print 461func (pb *ProgressBar) String() string { 462 pb.mu.Lock() 463 defer pb.mu.Unlock() 464 return pb.lastPrint 465} 466 467// SetTotal atomically sets new total count 468func (pb *ProgressBar) SetTotal(total int) *ProgressBar { 469 return pb.SetTotal64(int64(total)) 470} 471 472// SetTotal64 atomically sets new total count 473func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar { 474 atomic.StoreInt64(&pb.Total, total) 475 return pb 476} 477 478// Reset bar and set new total count 479// Does effect only on finished bar 480func (pb *ProgressBar) Reset(total int) *ProgressBar { 481 pb.mu.Lock() 482 defer pb.mu.Unlock() 483 if pb.isFinish { 484 pb.SetTotal(total).Set(0) 485 atomic.StoreInt64(&pb.previous, 0) 486 } 487 return pb 488} 489 490// Internal loop for refreshing the progressbar 491func (pb *ProgressBar) refresher() { 492 for { 493 select { 494 case <-pb.finish: 495 return 496 case <-time.After(pb.RefreshRate): 497 pb.Update() 498 } 499 } 500} 501