103 lines
1.9 KiB
Go
103 lines
1.9 KiB
Go
|
package progress
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"golang.org/x/term"
|
||
|
)
|
||
|
|
||
|
type Spinner struct {
|
||
|
message string
|
||
|
messageWidth int
|
||
|
|
||
|
parts []string
|
||
|
|
||
|
value int
|
||
|
|
||
|
ticker *time.Ticker
|
||
|
started time.Time
|
||
|
stopped time.Time
|
||
|
}
|
||
|
|
||
|
func NewSpinner(message string) *Spinner {
|
||
|
s := &Spinner{
|
||
|
message: message,
|
||
|
parts: []string{
|
||
|
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
|
||
|
},
|
||
|
started: time.Now(),
|
||
|
}
|
||
|
go s.start()
|
||
|
return s
|
||
|
}
|
||
|
|
||
|
func (s *Spinner) String() string {
|
||
|
termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
var pre strings.Builder
|
||
|
if len(s.message) > 0 {
|
||
|
message := strings.TrimSpace(s.message)
|
||
|
if s.messageWidth > 0 && len(message) > s.messageWidth {
|
||
|
message = message[:s.messageWidth]
|
||
|
}
|
||
|
|
||
|
fmt.Fprintf(&pre, "%s", message)
|
||
|
if s.messageWidth-pre.Len() >= 0 {
|
||
|
pre.WriteString(strings.Repeat(" ", s.messageWidth-pre.Len()))
|
||
|
}
|
||
|
|
||
|
pre.WriteString(" ")
|
||
|
}
|
||
|
|
||
|
var pad int
|
||
|
if s.stopped.IsZero() {
|
||
|
// spinner has a string length of 3 but a rune length of 1
|
||
|
// in order to align correctly, we need to pad with (3 - 1) = 2 spaces
|
||
|
spinner := s.parts[s.value]
|
||
|
pre.WriteString(spinner)
|
||
|
pad = len(spinner) - len([]rune(spinner))
|
||
|
}
|
||
|
|
||
|
var suf strings.Builder
|
||
|
fmt.Fprintf(&suf, "(%s)", s.elapsed())
|
||
|
|
||
|
var mid strings.Builder
|
||
|
f := termWidth - pre.Len() - mid.Len() - suf.Len() + pad
|
||
|
if f > 0 {
|
||
|
mid.WriteString(strings.Repeat(" ", f))
|
||
|
}
|
||
|
|
||
|
return pre.String() + mid.String() + suf.String()
|
||
|
}
|
||
|
|
||
|
func (s *Spinner) start() {
|
||
|
s.ticker = time.NewTicker(100 * time.Millisecond)
|
||
|
for range s.ticker.C {
|
||
|
s.value = (s.value + 1) % len(s.parts)
|
||
|
if !s.stopped.IsZero() {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Spinner) Stop() {
|
||
|
if s.stopped.IsZero() {
|
||
|
s.stopped = time.Now()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *Spinner) elapsed() time.Duration {
|
||
|
stopped := s.stopped
|
||
|
if stopped.IsZero() {
|
||
|
stopped = time.Now()
|
||
|
}
|
||
|
|
||
|
return stopped.Sub(s.started).Round(time.Second)
|
||
|
}
|