mirror of
https://github.com/neilotoole/sq.git
synced 2024-12-18 21:52:28 +03:00
db55986980
- Support for ingest cache, download cache, and progress bars.
191 lines
5.1 KiB
Go
191 lines
5.1 KiB
Go
package progress
|
|
|
|
import (
|
|
"time"
|
|
|
|
humanize "github.com/dustin/go-humanize"
|
|
"github.com/dustin/go-humanize/english"
|
|
"github.com/vbauerster/mpb/v8/decor"
|
|
)
|
|
|
|
// NewByteCounter returns a new determinate bar whose label
|
|
// metric is the size in bytes of the data being processed. The caller is
|
|
// ultimately responsible for calling [Bar.Stop] on the returned Bar.
|
|
func (p *Progress) NewByteCounter(msg string, size int64, opts ...Opt) *Bar {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
cfg := &barConfig{msg: msg, total: size}
|
|
|
|
var counter decor.Decorator
|
|
var percent decor.Decorator
|
|
if size < 0 {
|
|
cfg.style = spinnerStyle(p.colors.Filler)
|
|
counter = decor.Current(decor.SizeB1024(0), "% .1f")
|
|
} else {
|
|
cfg.style = barStyle(p.colors.Filler)
|
|
counter = decor.Counters(decor.SizeB1024(0), "% .1f / % .1f")
|
|
percent = decor.NewPercentage(" %.1f", decor.WCSyncSpace)
|
|
percent = colorize(percent, p.colors.Percent)
|
|
}
|
|
counter = colorize(counter, p.colors.Size)
|
|
cfg.decorators = []decor.Decorator{counter, percent}
|
|
|
|
return p.newBar(cfg, opts)
|
|
}
|
|
|
|
// NewUnitCounter returns a new indeterminate bar whose label
|
|
// metric is the plural of the provided unit. The caller is ultimately
|
|
// responsible for calling [Bar.Stop] on the returned Bar.
|
|
//
|
|
// bar := p.NewUnitCounter("Ingest records", "rec")
|
|
// defer bar.Stop()
|
|
//
|
|
// for i := 0; i < 100; i++ {
|
|
// bar.Incr(1)
|
|
// time.Sleep(100 * time.Millisecond)
|
|
// }
|
|
//
|
|
// This produces output similar to:
|
|
//
|
|
// Ingesting records ∙∙● 87 recs
|
|
//
|
|
// Note that the unit arg is automatically pluralized.
|
|
func (p *Progress) NewUnitCounter(msg, unit string, opts ...Opt) *Bar {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
cfg := &barConfig{
|
|
msg: msg,
|
|
total: -1,
|
|
style: spinnerStyle(p.colors.Filler),
|
|
}
|
|
|
|
d := decor.Any(func(statistics decor.Statistics) string {
|
|
s := humanize.Comma(statistics.Current)
|
|
if unit != "" {
|
|
s += " " + english.PluralWord(int(statistics.Current), unit, "")
|
|
}
|
|
return s
|
|
})
|
|
cfg.decorators = []decor.Decorator{colorize(d, p.colors.Size)}
|
|
|
|
return p.newBar(cfg, opts)
|
|
}
|
|
|
|
// NewWaiter returns a generic indeterminate spinner. If arg clock
|
|
// is true, a timer is shown. This produces output similar to:
|
|
//
|
|
// @excel/remote: start download ●∙∙ 4s
|
|
//
|
|
// The caller is ultimately responsible for calling [Bar.Stop] on the
|
|
// returned Bar.
|
|
func (p *Progress) NewWaiter(msg string, clock bool, opts ...Opt) *Bar {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
cfg := &barConfig{
|
|
msg: msg,
|
|
total: -1,
|
|
style: spinnerStyle(p.colors.Filler),
|
|
}
|
|
|
|
if clock {
|
|
d := newElapsedSeconds(p.colors.Size, time.Now(), decor.WCSyncSpace)
|
|
cfg.decorators = []decor.Decorator{d}
|
|
}
|
|
return p.newBar(cfg, opts)
|
|
}
|
|
|
|
// NewUnitTotalCounter returns a new determinate bar whose label
|
|
// metric is the plural of the provided unit. The caller is ultimately
|
|
// responsible for calling [Bar.Stop] on the returned Bar.
|
|
//
|
|
// This produces output similar to:
|
|
//
|
|
// Ingesting sheets ∙∙∙∙∙● 4 / 16 sheets
|
|
//
|
|
// Note that the unit arg is automatically pluralized.
|
|
func (p *Progress) NewUnitTotalCounter(msg, unit string, total int64, opts ...Opt) *Bar {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
if total <= 0 {
|
|
return p.NewUnitCounter(msg, unit)
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
cfg := &barConfig{
|
|
msg: msg,
|
|
total: total,
|
|
style: barStyle(p.colors.Filler),
|
|
}
|
|
|
|
d := decor.Any(func(statistics decor.Statistics) string {
|
|
s := humanize.Comma(statistics.Current) + " / " + humanize.Comma(statistics.Total)
|
|
if unit != "" {
|
|
s += " " + english.PluralWord(int(statistics.Current), unit, "")
|
|
}
|
|
return s
|
|
})
|
|
cfg.decorators = []decor.Decorator{colorize(d, p.colors.Size)}
|
|
return p.newBar(cfg, opts)
|
|
}
|
|
|
|
// NewTimeoutWaiter returns a new indeterminate bar whose label is the
|
|
// amount of time remaining until expires. It produces output similar to:
|
|
//
|
|
// Locking @sakila ●∙∙ timeout in 7s
|
|
//
|
|
// The caller is ultimately responsible for calling [Bar.Stop] on
|
|
// the returned bar, although the bar will also be stopped when the
|
|
// parent Progress stops.
|
|
func (p *Progress) NewTimeoutWaiter(msg string, expires time.Time, opts ...Opt) *Bar {
|
|
if p == nil {
|
|
return nil
|
|
}
|
|
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
|
|
cfg := &barConfig{
|
|
msg: msg,
|
|
style: spinnerStyle(p.colors.Waiting),
|
|
}
|
|
|
|
d := decor.Any(func(statistics decor.Statistics) string {
|
|
remaining := time.Until(expires)
|
|
switch {
|
|
case remaining > 0:
|
|
return p.colors.Size.Sprintf("timeout in %s", remaining.Round(time.Second))
|
|
case remaining > -time.Second:
|
|
// We do the extra second to prevent a "flash" of the timeout message,
|
|
// and it also prevents "timeout in -1s" etc. This situation should be
|
|
// rare; the caller should have already called Stop() on the Progress
|
|
// when the timeout happened, but we'll play it safe.
|
|
return p.colors.Size.Sprint("timeout in 0s")
|
|
default:
|
|
return p.colors.Warning.Sprintf("timed out")
|
|
}
|
|
})
|
|
|
|
cfg.decorators = []decor.Decorator{d}
|
|
cfg.total = int64(time.Until(expires))
|
|
return p.newBar(cfg, opts)
|
|
}
|