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