1package pb 2 3import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "strconv" 9 "strings" 10 "sync" 11 "sync/atomic" 12 "text/template" 13 "time" 14 15 "github.com/fatih/color" 16 17 "github.com/mattn/go-colorable" 18 "github.com/mattn/go-isatty" 19 20 "github.com/cheggaaa/pb/v3/termutil" 21) 22 23// Version of ProgressBar library 24const Version = "3.0.8" 25 26type key int 27 28const ( 29 // Bytes means we're working with byte sizes. Numbers will print as Kb, Mb, etc 30 // bar.Set(pb.Bytes, true) 31 Bytes key = 1 << iota 32 33 // Use SI bytes prefix names (kB, MB, etc) instead of IEC prefix names (KiB, MiB, etc) 34 SIBytesPrefix 35 36 // Terminal means we're will print to terminal and can use ascii sequences 37 // Also we're will try to use terminal width 38 Terminal 39 40 // Static means progress bar will not update automaticly 41 Static 42 43 // ReturnSymbol - by default in terminal mode it's '\r' 44 ReturnSymbol 45 46 // Color by default is true when output is tty, but you can set to false for disabling colors 47 Color 48 49 // Hide the progress bar when finished, rather than leaving it up. By default it's false. 50 CleanOnFinish 51 52 // Round elapsed time to this precision. Defaults to time.Second. 53 TimeRound 54) 55 56const ( 57 defaultBarWidth = 100 58 defaultRefreshRate = time.Millisecond * 200 59) 60 61// New creates new ProgressBar object 62func New(total int) *ProgressBar { 63 return New64(int64(total)) 64} 65 66// New64 creates new ProgressBar object using int64 as total 67func New64(total int64) *ProgressBar { 68 pb := new(ProgressBar) 69 return pb.SetTotal(total) 70} 71 72// StartNew starts new ProgressBar with Default template 73func StartNew(total int) *ProgressBar { 74 return New(total).Start() 75} 76 77// Start64 starts new ProgressBar with Default template. Using int64 as total. 78func Start64(total int64) *ProgressBar { 79 return New64(total).Start() 80} 81 82var ( 83 terminalWidth = termutil.TerminalWidth 84 isTerminal = isatty.IsTerminal 85 isCygwinTerminal = isatty.IsCygwinTerminal 86) 87 88// ProgressBar is the main object of bar 89type ProgressBar struct { 90 current, total int64 91 width int 92 maxWidth int 93 mu sync.RWMutex 94 rm sync.Mutex 95 vars map[interface{}]interface{} 96 elements map[string]Element 97 output io.Writer 98 coutput io.Writer 99 nocoutput io.Writer 100 startTime time.Time 101 refreshRate time.Duration 102 tmpl *template.Template 103 state *State 104 buf *bytes.Buffer 105 ticker *time.Ticker 106 finish chan struct{} 107 finished bool 108 configured bool 109 err error 110} 111 112func (pb *ProgressBar) configure() { 113 if pb.configured { 114 return 115 } 116 pb.configured = true 117 118 if pb.vars == nil { 119 pb.vars = make(map[interface{}]interface{}) 120 } 121 if pb.output == nil { 122 pb.output = os.Stderr 123 } 124 125 if pb.tmpl == nil { 126 pb.tmpl, pb.err = getTemplate(string(Default)) 127 if pb.err != nil { 128 return 129 } 130 } 131 if pb.vars[Terminal] == nil { 132 if f, ok := pb.output.(*os.File); ok { 133 if isTerminal(f.Fd()) || isCygwinTerminal(f.Fd()) { 134 pb.vars[Terminal] = true 135 } 136 } 137 } 138 if pb.vars[ReturnSymbol] == nil { 139 if tm, ok := pb.vars[Terminal].(bool); ok && tm { 140 pb.vars[ReturnSymbol] = "\r" 141 } 142 } 143 if pb.vars[Color] == nil { 144 if tm, ok := pb.vars[Terminal].(bool); ok && tm { 145 pb.vars[Color] = true 146 } 147 } 148 if pb.refreshRate == 0 { 149 pb.refreshRate = defaultRefreshRate 150 } 151 if pb.vars[CleanOnFinish] == nil { 152 pb.vars[CleanOnFinish] = false 153 } 154 if f, ok := pb.output.(*os.File); ok { 155 pb.coutput = colorable.NewColorable(f) 156 } else { 157 pb.coutput = pb.output 158 } 159 pb.nocoutput = colorable.NewNonColorable(pb.output) 160} 161 162// Start starts the bar 163func (pb *ProgressBar) Start() *ProgressBar { 164 pb.mu.Lock() 165 defer pb.mu.Unlock() 166 if pb.finish != nil { 167 return pb 168 } 169 pb.configure() 170 pb.finished = false 171 pb.state = nil 172 pb.startTime = time.Now() 173 if st, ok := pb.vars[Static].(bool); ok && st { 174 return pb 175 } 176 pb.finish = make(chan struct{}) 177 pb.ticker = time.NewTicker(pb.refreshRate) 178 go pb.writer(pb.finish) 179 return pb 180} 181 182func (pb *ProgressBar) writer(finish chan struct{}) { 183 for { 184 select { 185 case <-pb.ticker.C: 186 pb.write(false) 187 case <-finish: 188 pb.ticker.Stop() 189 pb.write(true) 190 finish <- struct{}{} 191 return 192 } 193 } 194} 195 196// Write performs write to the output 197func (pb *ProgressBar) Write() *ProgressBar { 198 pb.mu.RLock() 199 finished := pb.finished 200 pb.mu.RUnlock() 201 pb.write(finished) 202 return pb 203} 204 205func (pb *ProgressBar) write(finish bool) { 206 result, width := pb.render() 207 if pb.Err() != nil { 208 return 209 } 210 if pb.GetBool(Terminal) { 211 if r := (width - CellCount(result)); r > 0 { 212 result += strings.Repeat(" ", r) 213 } 214 } 215 if ret, ok := pb.Get(ReturnSymbol).(string); ok { 216 result = ret + result 217 if finish && ret == "\r" { 218 if pb.GetBool(CleanOnFinish) { 219 // "Wipe out" progress bar by overwriting one line with blanks 220 result = "\r" + color.New(color.Reset).Sprintf(strings.Repeat(" ", width)) + "\r" 221 } else { 222 result += "\n" 223 } 224 } 225 } 226 if pb.GetBool(Color) { 227 pb.coutput.Write([]byte(result)) 228 } else { 229 pb.nocoutput.Write([]byte(result)) 230 } 231} 232 233// Total return current total bar value 234func (pb *ProgressBar) Total() int64 { 235 return atomic.LoadInt64(&pb.total) 236} 237 238// SetTotal sets the total bar value 239func (pb *ProgressBar) SetTotal(value int64) *ProgressBar { 240 atomic.StoreInt64(&pb.total, value) 241 return pb 242} 243 244// AddTotal adds to the total bar value 245func (pb *ProgressBar) AddTotal(value int64) *ProgressBar { 246 atomic.AddInt64(&pb.total, value) 247 return pb 248} 249 250// SetCurrent sets the current bar value 251func (pb *ProgressBar) SetCurrent(value int64) *ProgressBar { 252 atomic.StoreInt64(&pb.current, value) 253 return pb 254} 255 256// Current return current bar value 257func (pb *ProgressBar) Current() int64 { 258 return atomic.LoadInt64(&pb.current) 259} 260 261// Add adding given int64 value to bar value 262func (pb *ProgressBar) Add64(value int64) *ProgressBar { 263 atomic.AddInt64(&pb.current, value) 264 return pb 265} 266 267// Add adding given int value to bar value 268func (pb *ProgressBar) Add(value int) *ProgressBar { 269 return pb.Add64(int64(value)) 270} 271 272// Increment atomically increments the progress 273func (pb *ProgressBar) Increment() *ProgressBar { 274 return pb.Add64(1) 275} 276 277// Set sets any value by any key 278func (pb *ProgressBar) Set(key, value interface{}) *ProgressBar { 279 pb.mu.Lock() 280 defer pb.mu.Unlock() 281 if pb.vars == nil { 282 pb.vars = make(map[interface{}]interface{}) 283 } 284 pb.vars[key] = value 285 return pb 286} 287 288// Get return value by key 289func (pb *ProgressBar) Get(key interface{}) interface{} { 290 pb.mu.RLock() 291 defer pb.mu.RUnlock() 292 if pb.vars == nil { 293 return nil 294 } 295 return pb.vars[key] 296} 297 298// GetBool return value by key and try to convert there to boolean 299// If value doesn't set or not boolean - return false 300func (pb *ProgressBar) GetBool(key interface{}) bool { 301 if v, ok := pb.Get(key).(bool); ok { 302 return v 303 } 304 return false 305} 306 307// SetWidth sets the bar width 308// When given value <= 0 would be using the terminal width (if possible) or default value. 309func (pb *ProgressBar) SetWidth(width int) *ProgressBar { 310 pb.mu.Lock() 311 pb.width = width 312 pb.mu.Unlock() 313 return pb 314} 315 316// SetMaxWidth sets the bar maximum width 317// When given value <= 0 would be using the terminal width (if possible) or default value. 318func (pb *ProgressBar) SetMaxWidth(maxWidth int) *ProgressBar { 319 pb.mu.Lock() 320 pb.maxWidth = maxWidth 321 pb.mu.Unlock() 322 return pb 323} 324 325// Width return the bar width 326// It's current terminal width or settled over 'SetWidth' value. 327func (pb *ProgressBar) Width() (width int) { 328 defer func() { 329 if r := recover(); r != nil { 330 width = defaultBarWidth 331 } 332 }() 333 pb.mu.RLock() 334 width = pb.width 335 maxWidth := pb.maxWidth 336 pb.mu.RUnlock() 337 if width <= 0 { 338 var err error 339 if width, err = terminalWidth(); err != nil { 340 return defaultBarWidth 341 } 342 } 343 if maxWidth > 0 && width > maxWidth { 344 width = maxWidth 345 } 346 return 347} 348 349func (pb *ProgressBar) SetRefreshRate(dur time.Duration) *ProgressBar { 350 pb.mu.Lock() 351 if dur > 0 { 352 pb.refreshRate = dur 353 } 354 pb.mu.Unlock() 355 return pb 356} 357 358// SetWriter sets the io.Writer. Bar will write in this writer 359// By default this is os.Stderr 360func (pb *ProgressBar) SetWriter(w io.Writer) *ProgressBar { 361 pb.mu.Lock() 362 pb.output = w 363 pb.configured = false 364 pb.configure() 365 pb.mu.Unlock() 366 return pb 367} 368 369// StartTime return the time when bar started 370func (pb *ProgressBar) StartTime() time.Time { 371 pb.mu.RLock() 372 defer pb.mu.RUnlock() 373 return pb.startTime 374} 375 376// Format convert int64 to string according to the current settings 377func (pb *ProgressBar) Format(v int64) string { 378 if pb.GetBool(Bytes) { 379 return formatBytes(v, pb.GetBool(SIBytesPrefix)) 380 } 381 return strconv.FormatInt(v, 10) 382} 383 384// Finish stops the bar 385func (pb *ProgressBar) Finish() *ProgressBar { 386 pb.mu.Lock() 387 if pb.finished { 388 pb.mu.Unlock() 389 return pb 390 } 391 finishChan := pb.finish 392 pb.finished = true 393 pb.mu.Unlock() 394 if finishChan != nil { 395 finishChan <- struct{}{} 396 <-finishChan 397 pb.mu.Lock() 398 pb.finish = nil 399 pb.mu.Unlock() 400 } 401 return pb 402} 403 404// IsStarted indicates progress bar state 405func (pb *ProgressBar) IsStarted() bool { 406 pb.mu.RLock() 407 defer pb.mu.RUnlock() 408 return pb.finish != nil 409} 410 411// SetTemplateString sets ProgressBar tempate string and parse it 412func (pb *ProgressBar) SetTemplateString(tmpl string) *ProgressBar { 413 pb.mu.Lock() 414 defer pb.mu.Unlock() 415 pb.tmpl, pb.err = getTemplate(tmpl) 416 return pb 417} 418 419// SetTemplateString sets ProgressBarTempate and parse it 420func (pb *ProgressBar) SetTemplate(tmpl ProgressBarTemplate) *ProgressBar { 421 return pb.SetTemplateString(string(tmpl)) 422} 423 424// NewProxyReader creates a wrapper for given reader, but with progress handle 425// Takes io.Reader or io.ReadCloser 426// Also, it automatically switches progress bar to handle units as bytes 427func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { 428 pb.Set(Bytes, true) 429 return &Reader{r, pb} 430} 431 432// NewProxyWriter creates a wrapper for given writer, but with progress handle 433// Takes io.Writer or io.WriteCloser 434// Also, it automatically switches progress bar to handle units as bytes 435func (pb *ProgressBar) NewProxyWriter(r io.Writer) *Writer { 436 pb.Set(Bytes, true) 437 return &Writer{r, pb} 438} 439 440func (pb *ProgressBar) render() (result string, width int) { 441 defer func() { 442 if r := recover(); r != nil { 443 pb.SetErr(fmt.Errorf("render panic: %v", r)) 444 } 445 }() 446 pb.rm.Lock() 447 defer pb.rm.Unlock() 448 pb.mu.Lock() 449 pb.configure() 450 if pb.state == nil { 451 pb.state = &State{ProgressBar: pb} 452 pb.buf = bytes.NewBuffer(nil) 453 } 454 if pb.startTime.IsZero() { 455 pb.startTime = time.Now() 456 } 457 pb.state.id++ 458 pb.state.finished = pb.finished 459 pb.state.time = time.Now() 460 pb.mu.Unlock() 461 462 pb.state.width = pb.Width() 463 width = pb.state.width 464 pb.state.total = pb.Total() 465 pb.state.current = pb.Current() 466 pb.buf.Reset() 467 468 if e := pb.tmpl.Execute(pb.buf, pb.state); e != nil { 469 pb.SetErr(e) 470 return "", 0 471 } 472 473 result = pb.buf.String() 474 475 aec := len(pb.state.recalc) 476 if aec == 0 { 477 // no adaptive elements 478 return 479 } 480 481 staticWidth := CellCount(result) - (aec * adElPlaceholderLen) 482 483 if pb.state.Width()-staticWidth <= 0 { 484 result = strings.Replace(result, adElPlaceholder, "", -1) 485 result = StripString(result, pb.state.Width()) 486 } else { 487 pb.state.adaptiveElWidth = (width - staticWidth) / aec 488 for _, el := range pb.state.recalc { 489 result = strings.Replace(result, adElPlaceholder, el.ProgressElement(pb.state), 1) 490 } 491 } 492 pb.state.recalc = pb.state.recalc[:0] 493 return 494} 495 496// SetErr sets error to the ProgressBar 497// Error will be available over Err() 498func (pb *ProgressBar) SetErr(err error) *ProgressBar { 499 pb.mu.Lock() 500 pb.err = err 501 pb.mu.Unlock() 502 return pb 503} 504 505// Err return possible error 506// When all ok - will be nil 507// May contain template.Execute errors 508func (pb *ProgressBar) Err() error { 509 pb.mu.RLock() 510 defer pb.mu.RUnlock() 511 return pb.err 512} 513 514// String return currrent string representation of ProgressBar 515func (pb *ProgressBar) String() string { 516 res, _ := pb.render() 517 return res 518} 519 520// ProgressElement implements Element interface 521func (pb *ProgressBar) ProgressElement(s *State, args ...string) string { 522 if s.IsAdaptiveWidth() { 523 pb.SetWidth(s.AdaptiveElWidth()) 524 } 525 return pb.String() 526} 527 528// State represents the current state of bar 529// Need for bar elements 530type State struct { 531 *ProgressBar 532 533 id uint64 534 total, current int64 535 width, adaptiveElWidth int 536 finished, adaptive bool 537 time time.Time 538 539 recalc []Element 540} 541 542// Id it's the current state identifier 543// - incremental 544// - starts with 1 545// - resets after finish/start 546func (s *State) Id() uint64 { 547 return s.id 548} 549 550// Total it's bar int64 total 551func (s *State) Total() int64 { 552 return s.total 553} 554 555// Value it's current value 556func (s *State) Value() int64 { 557 return s.current 558} 559 560// Width of bar 561func (s *State) Width() int { 562 return s.width 563} 564 565// AdaptiveElWidth - adaptive elements must return string with given cell count (when AdaptiveElWidth > 0) 566func (s *State) AdaptiveElWidth() int { 567 return s.adaptiveElWidth 568} 569 570// IsAdaptiveWidth returns true when element must be shown as adaptive 571func (s *State) IsAdaptiveWidth() bool { 572 return s.adaptive 573} 574 575// IsFinished return true when bar is finished 576func (s *State) IsFinished() bool { 577 return s.finished 578} 579 580// IsFirst return true only in first render 581func (s *State) IsFirst() bool { 582 return s.id == 1 583} 584 585// Time when state was created 586func (s *State) Time() time.Time { 587 return s.time 588} 589