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.10" 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 ShowFinalTime: true, 47 Units: U_NO, 48 ManualUpdate: false, 49 finish: make(chan struct{}), 50 currentValue: -1, 51 mu: new(sync.Mutex), 52 } 53 return pb.Format(FORMAT) 54} 55 56// Create new object and start 57func StartNew(total int) *ProgressBar { 58 return New(total).Start() 59} 60 61// Callback for custom output 62// For example: 63// bar.Callback = func(s string) { 64// mySuperPrint(s) 65// } 66// 67type Callback func(out string) 68 69type ProgressBar struct { 70 current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) 71 72 Total int64 73 RefreshRate time.Duration 74 ShowPercent, ShowCounters bool 75 ShowSpeed, ShowTimeLeft, ShowBar bool 76 ShowFinalTime 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 currentValue int64 97 98 prefix, postfix string 99 100 mu *sync.Mutex 101 lastPrint string 102 103 BarStart string 104 BarEnd string 105 Empty string 106 Current string 107 CurrentN string 108 109 AlwaysUpdate bool 110} 111 112// Start print 113func (pb *ProgressBar) Start() *ProgressBar { 114 pb.startTime = time.Now() 115 pb.startValue = pb.current 116 if pb.Total == 0 { 117 pb.ShowTimeLeft = false 118 pb.ShowPercent = false 119 pb.AutoStat = false 120 } 121 if !pb.ManualUpdate { 122 pb.Update() // Initial printing of the bar before running the bar refresher. 123 go pb.refresher() 124 } 125 return pb 126} 127 128// Increment current value 129func (pb *ProgressBar) Increment() int { 130 return pb.Add(1) 131} 132 133// Get current value 134func (pb *ProgressBar) Get() int64 { 135 c := atomic.LoadInt64(&pb.current) 136 return c 137} 138 139// Set current value 140func (pb *ProgressBar) Set(current int) *ProgressBar { 141 return pb.Set64(int64(current)) 142} 143 144// Set64 sets the current value as int64 145func (pb *ProgressBar) Set64(current int64) *ProgressBar { 146 atomic.StoreInt64(&pb.current, current) 147 return pb 148} 149 150// Add to current value 151func (pb *ProgressBar) Add(add int) int { 152 return int(pb.Add64(int64(add))) 153} 154 155func (pb *ProgressBar) Add64(add int64) int64 { 156 return atomic.AddInt64(&pb.current, add) 157} 158 159// Set prefix string 160func (pb *ProgressBar) Prefix(prefix string) *ProgressBar { 161 pb.prefix = prefix 162 return pb 163} 164 165// Set postfix string 166func (pb *ProgressBar) Postfix(postfix string) *ProgressBar { 167 pb.postfix = postfix 168 return pb 169} 170 171// Set custom format for bar 172// Example: bar.Format("[=>_]") 173// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter 174func (pb *ProgressBar) Format(format string) *ProgressBar { 175 var formatEntries []string 176 if utf8.RuneCountInString(format) == 5 { 177 formatEntries = strings.Split(format, "") 178 } else { 179 formatEntries = strings.Split(format, "\x00") 180 } 181 if len(formatEntries) == 5 { 182 pb.BarStart = formatEntries[0] 183 pb.BarEnd = formatEntries[4] 184 pb.Empty = formatEntries[3] 185 pb.Current = formatEntries[1] 186 pb.CurrentN = formatEntries[2] 187 } 188 return pb 189} 190 191// Set bar refresh rate 192func (pb *ProgressBar) SetRefreshRate(rate time.Duration) *ProgressBar { 193 pb.RefreshRate = rate 194 return pb 195} 196 197// Set units 198// bar.SetUnits(U_NO) - by default 199// bar.SetUnits(U_BYTES) - for Mb, Kb, etc 200func (pb *ProgressBar) SetUnits(units Units) *ProgressBar { 201 pb.Units = units 202 return pb 203} 204 205// Set max width, if width is bigger than terminal width, will be ignored 206func (pb *ProgressBar) SetMaxWidth(width int) *ProgressBar { 207 pb.Width = width 208 pb.ForceWidth = false 209 return pb 210} 211 212// Set bar width 213func (pb *ProgressBar) SetWidth(width int) *ProgressBar { 214 pb.Width = width 215 pb.ForceWidth = true 216 return pb 217} 218 219// End print 220func (pb *ProgressBar) Finish() { 221 //Protect multiple calls 222 pb.finishOnce.Do(func() { 223 close(pb.finish) 224 pb.write(atomic.LoadInt64(&pb.current)) 225 switch { 226 case pb.Output != nil: 227 fmt.Fprintln(pb.Output) 228 case !pb.NotPrint: 229 fmt.Println() 230 } 231 pb.isFinish = true 232 }) 233} 234 235// End print and write string 'str' 236func (pb *ProgressBar) FinishPrint(str string) { 237 pb.Finish() 238 if pb.Output != nil { 239 fmt.Fprintln(pb.Output, str) 240 } else { 241 fmt.Println(str) 242 } 243} 244 245// implement io.Writer 246func (pb *ProgressBar) Write(p []byte) (n int, err error) { 247 n = len(p) 248 pb.Add(n) 249 return 250} 251 252// implement io.Reader 253func (pb *ProgressBar) Read(p []byte) (n int, err error) { 254 n = len(p) 255 pb.Add(n) 256 return 257} 258 259// Create new proxy reader over bar 260// Takes io.Reader or io.ReadCloser 261func (pb *ProgressBar) NewProxyReader(r io.Reader) *Reader { 262 return &Reader{r, pb} 263} 264 265func (pb *ProgressBar) write(current int64) { 266 width := pb.GetWidth() 267 268 var percentBox, countersBox, timeLeftBox, speedBox, barBox, end, out string 269 270 // percents 271 if pb.ShowPercent { 272 var percent float64 273 if pb.Total > 0 { 274 percent = float64(current) / (float64(pb.Total) / float64(100)) 275 } else { 276 percent = float64(current) / float64(100) 277 } 278 percentBox = fmt.Sprintf(" %6.02f%%", percent) 279 } 280 281 // counters 282 if pb.ShowCounters { 283 current := Format(current).To(pb.Units).Width(pb.UnitsWidth) 284 if pb.Total > 0 { 285 total := Format(pb.Total).To(pb.Units).Width(pb.UnitsWidth) 286 countersBox = fmt.Sprintf(" %s / %s ", current, total) 287 } else { 288 countersBox = fmt.Sprintf(" %s / ? ", current) 289 } 290 } 291 292 // time left 293 fromStart := time.Now().Sub(pb.startTime) 294 currentFromStart := current - pb.startValue 295 select { 296 case <-pb.finish: 297 if pb.ShowFinalTime { 298 var left time.Duration 299 left = (fromStart / time.Second) * time.Second 300 timeLeftBox = fmt.Sprintf(" %s", left.String()) 301 } 302 default: 303 if pb.ShowTimeLeft && currentFromStart > 0 { 304 perEntry := fromStart / time.Duration(currentFromStart) 305 var left time.Duration 306 if pb.Total > 0 { 307 left = time.Duration(pb.Total-currentFromStart) * perEntry 308 left = (left / time.Second) * time.Second 309 } else { 310 left = time.Duration(currentFromStart) * perEntry 311 left = (left / time.Second) * time.Second 312 } 313 timeLeft := Format(int64(left)).To(U_DURATION).String() 314 timeLeftBox = fmt.Sprintf(" %s", timeLeft) 315 } 316 } 317 318 if len(timeLeftBox) < pb.TimeBoxWidth { 319 timeLeftBox = fmt.Sprintf("%s%s", strings.Repeat(" ", pb.TimeBoxWidth-len(timeLeftBox)), timeLeftBox) 320 } 321 322 // speed 323 if pb.ShowSpeed && currentFromStart > 0 { 324 fromStart := time.Now().Sub(pb.startTime) 325 speed := float64(currentFromStart) / (float64(fromStart) / float64(time.Second)) 326 speedBox = " " + Format(int64(speed)).To(pb.Units).Width(pb.UnitsWidth).PerSec().String() 327 } 328 329 barWidth := escapeAwareRuneCountInString(countersBox + pb.BarStart + pb.BarEnd + percentBox + timeLeftBox + speedBox + pb.prefix + pb.postfix) 330 // bar 331 if pb.ShowBar { 332 size := width - barWidth 333 if size > 0 { 334 if pb.Total > 0 { 335 curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size))) 336 emptCount := size - curCount 337 barBox = pb.BarStart 338 if emptCount < 0 { 339 emptCount = 0 340 } 341 if curCount > size { 342 curCount = size 343 } 344 if emptCount <= 0 { 345 barBox += strings.Repeat(pb.Current, curCount) 346 } else if curCount > 0 { 347 barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN 348 } 349 barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd 350 } else { 351 barBox = pb.BarStart 352 pos := size - int(current)%int(size) 353 if pos-1 > 0 { 354 barBox += strings.Repeat(pb.Empty, pos-1) 355 } 356 barBox += pb.Current 357 if size-pos-1 > 0 { 358 barBox += strings.Repeat(pb.Empty, size-pos-1) 359 } 360 barBox += pb.BarEnd 361 } 362 } 363 } 364 365 // check len 366 out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix 367 if cl := escapeAwareRuneCountInString(out); cl < width { 368 end = strings.Repeat(" ", width-cl) 369 } 370 371 // and print! 372 pb.mu.Lock() 373 pb.lastPrint = out + end 374 pb.mu.Unlock() 375 switch { 376 case pb.isFinish: 377 return 378 case pb.Output != nil: 379 fmt.Fprint(pb.Output, "\r"+out+end) 380 case pb.Callback != nil: 381 pb.Callback(out + end) 382 case !pb.NotPrint: 383 fmt.Print("\r" + out + end) 384 } 385} 386 387// GetTerminalWidth - returns terminal width for all platforms. 388func GetTerminalWidth() (int, error) { 389 return terminalWidth() 390} 391 392func (pb *ProgressBar) GetWidth() int { 393 if pb.ForceWidth { 394 return pb.Width 395 } 396 397 width := pb.Width 398 termWidth, _ := terminalWidth() 399 if width == 0 || termWidth <= width { 400 width = termWidth 401 } 402 403 return width 404} 405 406// Write the current state of the progressbar 407func (pb *ProgressBar) Update() { 408 c := atomic.LoadInt64(&pb.current) 409 if pb.AlwaysUpdate || c != pb.currentValue { 410 pb.write(c) 411 pb.currentValue = c 412 } 413 if pb.AutoStat { 414 if c == 0 { 415 pb.startTime = time.Now() 416 pb.startValue = 0 417 } else if c >= pb.Total && pb.isFinish != true { 418 pb.Finish() 419 } 420 } 421} 422 423func (pb *ProgressBar) String() string { 424 return pb.lastPrint 425} 426 427// Internal loop for refreshing the progressbar 428func (pb *ProgressBar) refresher() { 429 for { 430 select { 431 case <-pb.finish: 432 return 433 case <-time.After(pb.RefreshRate): 434 pb.Update() 435 } 436 } 437} 438 439type window struct { 440 Row uint16 441 Col uint16 442 Xpixel uint16 443 Ypixel uint16 444} 445