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.29" 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.mu.Lock() 163 defer pb.mu.Unlock() 164 pb.prefix = prefix 165 return pb 166} 167 168// Set postfix string 169func (pb *ProgressBar) Postfix(postfix string) *ProgressBar { 170 pb.mu.Lock() 171 defer pb.mu.Unlock() 172 pb.postfix = postfix 173 return pb 174} 175 176// Set custom format for bar 177// Example: bar.Format("[=>_]") 178// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter 179func (pb *ProgressBar) Format(format string) *ProgressBar { 180 var formatEntries []string 181 if utf8.RuneCountInString(format) == 5 { 182 formatEntries = strings.Split(format, "") 183 } else { 184 formatEntries = strings.Split(format, "\x00") 185 } 186 if len(formatEntries) == 5 { 187 pb.BarStart = formatEntries[0] 188 pb.BarEnd = formatEntries[4] 189 pb.Empty = formatEntries[3] 190 pb.Current = formatEntries[1] 191 pb.CurrentN = formatEntries[2] 192 } 193 return pb 194} 195 196// Set bar refresh rate 197func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar { 198 pb.RefreshRate = rate 199 return pb 200} 201 202// Set units 203// bar.SetUnits(U_NO) - by default 204// bar.SetUnits(U_BYTES) - for Mb, Kb, etc 205func (pb *ProgressBar) SetUnits(units Units) *ProgressBar { 206 pb.Units = units 207 return pb 208} 209 210// Set max width, if width is bigger than terminal width, will be ignored 211func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar { 212 pb.Width = width 213 pb.ForceWidth = false 214 return pb 215} 216 217// Set bar width 218func (pb *ProgressBar) SetWidth(width int) *ProgressBar { 219 pb.Width = width 220 pb.ForceWidth = true 221 return pb 222} 223 224// End print 225func (pb *ProgressBar) Finish() { 226 //Protect multiple calls 227 pb.finishOnce.Do(func() { 228 close(pb.finish) 229 pb.write(atomic.LoadInt64(&pb.Total), atomic.LoadInt64(&pb.current)) 230 pb.mu.Lock() 231 defer pb.mu.Unlock() 232 switch { 233 case pb.Output != nil: 234 fmt.Fprintln(pb.Output) 235 case !pb.NotPrint: 236 fmt.Println() 237 } 238 pb.isFinish = true 239 }) 240} 241 242// IsFinished return boolean 243func (pb *ProgressBar) IsFinished() bool { 244 pb.mu.Lock() 245 defer pb.mu.Unlock() 246 return pb.isFinish 247} 248 249// End print and write string 'str' 250func (pb *ProgressBar) FinishPrint(str string) { 251 pb.Finish() 252 if pb.Output != nil { 253 fmt.Fprintln(pb.Output, str) 254 } else { 255 fmt.Println(str) 256 } 257} 258 259// implement io.Writer 260func (pb *ProgressBar) Write(p []byte) (n int, err error) { 261 n = len(p) 262 pb.Add(n) 263 return 264} 265 266// implement io.Reader 267func (pb *ProgressBar) Read(p []byte) (n int, err error) { 268 n = len(p) 269 pb.Add(n) 270 return 271} 272 273// Create new proxy reader over bar 274// Takes io.Reader or io.ReadCloser 275func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { 276 return &Reader{r, pb} 277} 278 279// Create new proxy writer over bar 280// Takes io.Writer or io.WriteCloser 281func (pb *ProgressBar) NewProxyWriter(r io.Writer) *Writer { 282 return &Writer{r, pb} 283} 284 285func (pb *ProgressBar) write(total, current int64) { 286 pb.mu.Lock() 287 defer pb.mu.Unlock() 288 width := pb.GetWidth() 289 290 var percentBox, countersBox, timeLeftBox, timeSpentBox, speedBox, barBox, end, out string 291 292 // percents 293 if pb.ShowPercent { 294 var percent float64 295 if total > 0 { 296 percent = float64(current) / (float64(total) / float64(100)) 297 } else { 298 percent = float64(current) / float64(100) 299 } 300 percentBox = fmt.Sprintf(" %6.02f%%", percent) 301 } 302 303 // counters 304 if pb.ShowCounters { 305 current := Format(current).To(pb.Units).Width(pb.UnitsWidth) 306 if total > 0 { 307 totalS := Format(total).To(pb.Units).Width(pb.UnitsWidth) 308 countersBox = fmt.Sprintf(" %s / %s ", current, totalS) 309 } else { 310 countersBox = fmt.Sprintf(" %s / ? ", current) 311 } 312 } 313 314 // time left 315 currentFromStart := current - pb.startValue 316 fromStart := time.Now().Sub(pb.startTime) 317 lastChangeTime := pb.changeTime 318 fromChange := lastChangeTime.Sub(pb.startTime) 319 320 if pb.ShowElapsedTime { 321 timeSpentBox = fmt.Sprintf(" %s ", (fromStart/time.Second)*time.Second) 322 } 323 324 select { 325 case <-pb.finish: 326 if pb.ShowFinalTime { 327 var left time.Duration 328 left = (fromStart / time.Second) * time.Second 329 timeLeftBox = fmt.Sprintf(" %s", left.String()) 330 } 331 default: 332 if pb.ShowTimeLeft && currentFromStart > 0 { 333 perEntry := fromChange / time.Duration(currentFromStart) 334 var left time.Duration 335 if total > 0 { 336 left = time.Duration(total-current) * perEntry 337 left -= time.Since(lastChangeTime) 338 left = (left / time.Second) * time.Second 339 } 340 if left > 0 { 341 timeLeft := Format(int64(left)).To(U_DURATION).String() 342 timeLeftBox = fmt.Sprintf(" %s", timeLeft) 343 } 344 } 345 } 346 347 if len(timeLeftBox) < pb.TimeBoxWidth { 348 timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox) 349 } 350 351 // speed 352 if pb.ShowSpeed && currentFromStart > 0 { 353 fromStart := time.Now().Sub(pb.startTime) 354 speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second)) 355 speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() 356 } 357 358 barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeSpentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) 359 // bar 360 if pb.ShowBar { 361 size := width - barWidth 362 if size > 0 { 363 if total > 0 { 364 curSize := int(math.Ceil((float64(current) / float64(total)) * float64(size))) 365 emptySize := size - curSize 366 barBox = pb.BarStart 367 if emptySize < 0 { 368 emptySize = 0 369 } 370 if curSize > size { 371 curSize = size 372 } 373 374 cursorLen := escapeAwareRuneCountInString(pb.Current) 375 if emptySize <= 0 { 376 barBox += strings.Repeat(pb.Current, curSize/cursorLen) 377 } else if curSize > 0 { 378 cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN) 379 cursorRepetitions := (curSize - cursorEndLen) / cursorLen 380 barBox += strings.Repeat(pb.Current, cursorRepetitions) 381 barBox += pb.CurrentN 382 } 383 384 emptyLen := escapeAwareRuneCountInString(pb.Empty) 385 barBox += strings.Repeat(pb.Empty, emptySize/emptyLen) 386 barBox += pb.BarEnd 387 } else { 388 pos := size - int(current)%int(size) 389 barBox = pb.BarStart 390 if pos-1 > 0 { 391 barBox += strings.Repeat(pb.Empty, pos-1) 392 } 393 barBox += pb.Current 394 if size-pos-1 > 0 { 395 barBox += strings.Repeat(pb.Empty, size-pos-1) 396 } 397 barBox += pb.BarEnd 398 } 399 } 400 } 401 402 // check len 403 out = pb.prefix + timeSpentBox + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix 404 405 if cl := escapeAwareRuneCountInString(out); cl < width { 406 end = strings.Repeat(" ", width-cl) 407 } 408 409 // and print! 410 pb.lastPrint = out + end 411 isFinish := pb.isFinish 412 413 switch { 414 case isFinish: 415 return 416 case pb.Output != nil: 417 fmt.Fprint(pb.Output, "\r"+out+end) 418 case pb.Callback != nil: 419 pb.Callback(out + end) 420 case !pb.NotPrint: 421 fmt.Print("\r" + out + end) 422 } 423} 424 425// GetTerminalWidth - returns terminal width for all platforms. 426func GetTerminalWidth() (int, error) { 427 return terminalWidth() 428} 429 430func (pb *ProgressBar) GetWidth() int { 431 if pb.ForceWidth { 432 return pb.Width 433 } 434 435 width := pb.Width 436 termWidth, _ := terminalWidth() 437 if width == 0 || termWidth <= width { 438 width = termWidth 439 } 440 441 return width 442} 443 444// Write the current state of the progressbar 445func (pb *ProgressBar) Update() { 446 c := atomic.LoadInt64(&pb.current) 447 p := atomic.LoadInt64(&pb.previous) 448 t := atomic.LoadInt64(&pb.Total) 449 if p != c { 450 pb.mu.Lock() 451 pb.changeTime = time.Now() 452 pb.mu.Unlock() 453 atomic.StoreInt64(&pb.previous, c) 454 } 455 pb.write(t, c) 456 if pb.AutoStat { 457 if c == 0 { 458 pb.startTime = time.Now() 459 pb.startValue = 0 460 } else if c >= t && pb.isFinish != true { 461 pb.Finish() 462 } 463 } 464} 465 466// String return the last bar print 467func (pb *ProgressBar) String() string { 468 pb.mu.Lock() 469 defer pb.mu.Unlock() 470 return pb.lastPrint 471} 472 473// SetTotal atomically sets new total count 474func (pb *ProgressBar) SetTotal(total int) *ProgressBar { 475 return pb.SetTotal64(int64(total)) 476} 477 478// SetTotal64 atomically sets new total count 479func (pb *ProgressBar) SetTotal64(total int64) *ProgressBar { 480 atomic.StoreInt64(&pb.Total, total) 481 return pb 482} 483 484// Reset bar and set new total count 485// Does effect only on finished bar 486func (pb *ProgressBar) Reset(total int) *ProgressBar { 487 pb.mu.Lock() 488 defer pb.mu.Unlock() 489 if pb.isFinish { 490 pb.SetTotal(total).Set(0) 491 atomic.StoreInt64(&pb.previous, 0) 492 } 493 return pb 494} 495 496// Internal loop for refreshing the progressbar 497func (pb *ProgressBar) refresher() { 498 for { 499 select { 500 case <-pb.finish: 501 return 502 case <-time.After(pb.RefreshRate): 503 pb.Update() 504 } 505 } 506} 507