mirror of
https://github.com/turbot/steampipe.git
synced 2025-12-19 18:12:43 -05:00
152 lines
3.5 KiB
Go
152 lines
3.5 KiB
Go
package statushooks
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/briandowns/spinner"
|
|
"github.com/fatih/color"
|
|
"github.com/karrick/gows"
|
|
"github.com/turbot/pipe-fittings/v2/constants"
|
|
)
|
|
|
|
// spinner format:
|
|
// <spinner><space><message><space><dot><dot><dot><cursor>
|
|
//
|
|
// 1 1 [.......] 1 1 1 1 1
|
|
//
|
|
// # We need at least seven characters to show the spinner properly
|
|
//
|
|
// Not using the (…) character, since it is too small
|
|
const minSpinnerWidth = 7
|
|
|
|
// StatusSpinner is a struct which implements StatusHooks, and uses a spinner to display status messages
|
|
type StatusSpinner struct {
|
|
spinner *spinner.Spinner
|
|
cancel chan struct{}
|
|
delay time.Duration
|
|
visible bool
|
|
mu sync.RWMutex // protects spinner.Suffix and visible fields
|
|
}
|
|
|
|
type StatusSpinnerOpt func(*StatusSpinner)
|
|
|
|
func WithMessage(msg string) StatusSpinnerOpt {
|
|
return func(s *StatusSpinner) {
|
|
s.UpdateSpinnerMessage(msg)
|
|
}
|
|
}
|
|
|
|
func WithDelay(delay time.Duration) StatusSpinnerOpt {
|
|
return func(s *StatusSpinner) {
|
|
s.delay = delay
|
|
}
|
|
}
|
|
|
|
// this is used in the root command to setup a default cmd execution context
|
|
// with a status spinner built in
|
|
// to update this, use the statushooks.AddStatusHooksToContext
|
|
//
|
|
// We should never create a StatusSpinner directly. To use a spinner
|
|
// DO NOT use a StatusSpinner directly, since using it may have
|
|
// unintended side-effect around the spinner lifecycle
|
|
func NewStatusSpinnerHook(opts ...StatusSpinnerOpt) *StatusSpinner {
|
|
res := &StatusSpinner{}
|
|
|
|
res.spinner = spinner.New(
|
|
spinner.CharSets[14],
|
|
100*time.Millisecond,
|
|
spinner.WithHiddenCursor(true),
|
|
spinner.WithWriter(os.Stdout),
|
|
)
|
|
for _, opt := range opts {
|
|
opt(res)
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// SetStatus implements StatusHooks
|
|
func (s *StatusSpinner) SetStatus(msg string) {
|
|
s.UpdateSpinnerMessage(msg)
|
|
}
|
|
|
|
func (s *StatusSpinner) Message(msgs ...string) {
|
|
if s.spinner.Active() {
|
|
s.spinner.Stop()
|
|
defer s.spinner.Start()
|
|
}
|
|
for _, msg := range msgs {
|
|
fmt.Println(msg)
|
|
}
|
|
}
|
|
|
|
func (s *StatusSpinner) Warn(msg string) {
|
|
if s.spinner.Active() {
|
|
s.spinner.Stop()
|
|
defer s.spinner.Start()
|
|
}
|
|
fmt.Fprintf(color.Output, "%s: %v\n", constants.ColoredWarn, msg)
|
|
}
|
|
|
|
// Hide implements StatusHooks
|
|
func (s *StatusSpinner) Hide() {
|
|
s.mu.Lock()
|
|
s.visible = false
|
|
s.mu.Unlock()
|
|
if s.cancel != nil {
|
|
close(s.cancel)
|
|
}
|
|
s.closeSpinner()
|
|
}
|
|
|
|
func (s *StatusSpinner) Show() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.visible = true
|
|
if len(strings.TrimSpace(s.spinner.Suffix)) > 0 {
|
|
// only show the spinner if there's an actual message to show
|
|
s.spinner.Start()
|
|
}
|
|
}
|
|
|
|
// UpdateSpinnerMessage updates the message of the given spinner
|
|
func (s *StatusSpinner) UpdateSpinnerMessage(newMessage string) {
|
|
newMessage = s.truncateSpinnerMessageToScreen(newMessage)
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.spinner.Suffix = fmt.Sprintf(" %s", newMessage)
|
|
// if the spinner is not active, start it
|
|
if s.visible && !s.spinner.Active() {
|
|
s.spinner.Start()
|
|
}
|
|
}
|
|
|
|
func (s *StatusSpinner) closeSpinner() {
|
|
if s.spinner != nil {
|
|
s.spinner.Stop()
|
|
}
|
|
}
|
|
|
|
func (s *StatusSpinner) truncateSpinnerMessageToScreen(msg string) string {
|
|
if len(strings.TrimSpace(msg)) == 0 {
|
|
// if this is a blank message, return it as is
|
|
return msg
|
|
}
|
|
|
|
maxCols, _, _ := gows.GetWinSize()
|
|
// if the screen is smaller than the minimum spinner width, we cannot truncate
|
|
if maxCols < minSpinnerWidth {
|
|
return msg
|
|
}
|
|
availableColumns := maxCols - minSpinnerWidth
|
|
if len(msg) > availableColumns {
|
|
msg = msg[:availableColumns]
|
|
msg = fmt.Sprintf("%s …", msg)
|
|
}
|
|
return msg
|
|
}
|