a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package progress
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
)
|
|
|
|
// Spinner is the single-operation face of the Tracker, kept for the many
|
|
// call sites that want "animate this one label, then ✓ or ✗". It is a thin
|
|
// facade: on a capable terminal the Tracker renders a live compact header
|
|
// (spinner, title, status, elapsed) and turns Reporter stage transitions
|
|
// into a checklist of step rows; elsewhere it degrades to clean line output.
|
|
// Implements Reporter so it can be installed via WithReporter.
|
|
type Spinner struct {
|
|
t *Tracker
|
|
}
|
|
|
|
// NewSpinner constructs a Spinner bound to w. When w isn't a TTY (or NO_COLOR
|
|
// / TERM=dumb / CI is set), the spinner is created in plain-text mode.
|
|
func NewSpinner(w io.Writer) *Spinner {
|
|
return &Spinner{t: NewTracker(w)}
|
|
}
|
|
|
|
// Disable forces the spinner into plain-text mode. Effective before Start.
|
|
func (s *Spinner) Disable() { s.t.disable() }
|
|
|
|
// Enabled reports whether the spinner is animating.
|
|
func (s *Spinner) Enabled() bool { return s.t.Animated() }
|
|
|
|
// Start begins animating with the given label.
|
|
func (s *Spinner) Start(label string) { s.t.Start(label) }
|
|
|
|
// Set updates the label and sub-status mid-animation. Either may be empty to
|
|
// leave the existing value.
|
|
func (s *Spinner) Set(label, sub string) {
|
|
s.t.SetTitle(label)
|
|
s.t.SetStatus(sub)
|
|
}
|
|
|
|
// Report implements Reporter. Each distinct stage becomes its own live step
|
|
// row (with counters and, when the total is known, a progress bar); in plain
|
|
// mode stages print as start / finish line pairs with a slow heartbeat.
|
|
func (s *Spinner) Report(stage string, current, total int) {
|
|
s.t.Report(stage, current, total)
|
|
}
|
|
|
|
// Done stops the spinner and replaces the frame with a green ✓ summary.
|
|
func (s *Spinner) Done() { s.t.Done("", "") }
|
|
|
|
// Fail stops the spinner and replaces the frame with a red ✗ summary.
|
|
func (s *Spinner) Fail(err error) { s.t.Fail(err) }
|
|
|
|
// Tracker exposes the underlying tracker for call sites that outgrow the
|
|
// single-label surface (explicit steps, log lines above the animation).
|
|
func (s *Spinner) Tracker() *Tracker { return s.t }
|
|
|
|
// Multi fans out reporter ticks to all of rs. Nil entries are skipped.
|
|
func Multi(rs ...Reporter) Reporter {
|
|
out := make([]Reporter, 0, len(rs))
|
|
for _, r := range rs {
|
|
if r == nil {
|
|
continue
|
|
}
|
|
out = append(out, r)
|
|
}
|
|
switch len(out) {
|
|
case 0:
|
|
return Nop{}
|
|
case 1:
|
|
return out[0]
|
|
default:
|
|
return multiReporter(out)
|
|
}
|
|
}
|
|
|
|
type multiReporter []Reporter
|
|
|
|
func (m multiReporter) Report(stage string, current, total int) {
|
|
for _, r := range m {
|
|
r.Report(stage, current, total)
|
|
}
|
|
}
|
|
|
|
// Run animates a spinner around fn. The context passed to fn carries the
|
|
// spinner as a Reporter, so any progress.FromContext(ctx).Report(…) inside fn
|
|
// drives the live step rows. The spinner is finished (✓ or ✗) before Run
|
|
// returns.
|
|
func Run(ctx context.Context, w io.Writer, label string, fn func(context.Context) error) error {
|
|
sp := NewSpinner(w)
|
|
return runWith(ctx, sp, label, fn)
|
|
}
|
|
|
|
// RunDisabled is Run with the spinner forced into plain-text mode.
|
|
func RunDisabled(ctx context.Context, w io.Writer, label string, fn func(context.Context) error) error {
|
|
sp := NewSpinner(w)
|
|
sp.Disable()
|
|
return runWith(ctx, sp, label, fn)
|
|
}
|
|
|
|
func runWith(ctx context.Context, sp *Spinner, label string, fn func(context.Context) error) error {
|
|
sp.Start(label)
|
|
ctx = WithReporter(ctx, sp)
|
|
err := fn(ctx)
|
|
if err != nil {
|
|
sp.Fail(err)
|
|
} else {
|
|
sp.Done()
|
|
}
|
|
return err
|
|
}
|