diff --git a/examples/main.go b/examples/main.go index 8acbdf0..a25a70f 100644 --- a/examples/main.go +++ b/examples/main.go @@ -4,11 +4,16 @@ import ( "time" "github.com/chelnak/ysmrr" + "github.com/chelnak/ysmrr/pkg/charmap" + "github.com/chelnak/ysmrr/pkg/colors" ) func main() { // Create a new spinner manager - sm := ysmrr.NewSpinnerManager() + sm := ysmrr.NewSpinnerManager( + ysmrr.WithCharMap(charmap.Arrows), + ysmrr.WithSpinnerColor(colors.FgHiBlue), + ) // Set up our spinners downloading := sm.AddSpinner("Downloading...") diff --git a/manager.go b/manager.go index d0c4e2e..26ee8f2 100644 --- a/manager.go +++ b/manager.go @@ -8,23 +8,11 @@ import ( "os" "time" + "github.com/chelnak/ysmrr/pkg/charmap" + "github.com/chelnak/ysmrr/pkg/colors" "github.com/chelnak/ysmrr/pkg/tput" - "github.com/fatih/color" ) -var Dots = []string{ - "⠋", - "⠙", - "⠹", - "⠸", - "⠼", - "⠴", - "⠦", - "⠧", - "⠇", - "⠏", -} - // SpinnerManager manages spinners type SpinnerManager interface { AddSpinner(msg string) *spinner @@ -36,6 +24,9 @@ type spinnerManager struct { Spinners []*spinner chars []string frameDuration time.Duration + spinnerColor colors.Color + completeColor colors.Color + errorColor colors.Color writer io.Writer done chan bool ticks *time.Ticker @@ -44,9 +35,7 @@ type spinnerManager struct { // AddSpinner adds a new spinner to the manager func (sm *spinnerManager) AddSpinner(msg string) *spinner { - c := color.New(color.FgHiGreen) - spinner := NewSpinner(msg, c) - + spinner := NewSpinner(msg, sm.spinnerColor, sm.completeColor, sm.errorColor) sm.Spinners = append(sm.Spinners, spinner) return spinner } @@ -75,11 +64,13 @@ func (sm *spinnerManager) setNextPos() { func (sm *spinnerManager) renderFrame() { for _, s := range sm.Spinners { if s.complete { - fmt.Fprintf(sm.writer, "\r✓ %s\n", s.msg) + s.completeColor.Fprint(sm.writer, "\r✓") + fmt.Fprintf(sm.writer, " %s\n", s.msg) } else if s.err { - fmt.Fprintf(sm.writer, "\r✗ %s\n", s.msg) + s.errorColor.Fprint(sm.writer, "\r✗") + fmt.Fprintf(sm.writer, " %s\n", s.msg) } else { - s.c.Fprintf(sm.writer, "%s", sm.chars[sm.pos]) + s.spinnerColor.Fprintf(sm.writer, "%s", sm.chars[sm.pos]) fmt.Fprintf(sm.writer, " %s\r", s.msg) fmt.Fprint(sm.writer, "\n") } @@ -103,12 +94,52 @@ func (sm *spinnerManager) render() { } } -// NewSpinnerManager creates a new spinner manager -func NewSpinnerManager() SpinnerManager { - return &spinnerManager{ - chars: Dots, +// Option represents a spinner manager option. +type Option func(*spinnerManager) + +// NewSpinnerManager creates a new spinner manager. +func NewSpinnerManager(options ...Option) SpinnerManager { + sm := &spinnerManager{ + chars: charmap.Dots, frameDuration: 250 * time.Millisecond, + spinnerColor: colors.FgHiGreen, + errorColor: colors.FgHiRed, + completeColor: colors.FgHiGreen, writer: os.Stdout, done: make(chan bool), } + + for _, option := range options { + option(sm) + } + + return sm +} + +// WithChars sets the characters used for the spinners. +func WithCharMap(chars []string) Option { + return func(sm *spinnerManager) { + sm.chars = chars + } +} + +// WithFrameDuration sets the duration of each frame. +func WithFrameDuration(d time.Duration) Option { + return func(sm *spinnerManager) { + sm.frameDuration = d + } +} + +// WithSpinnerColor sets the color of the spinners. +func WithSpinnerColor(c colors.Color) Option { + return func(sm *spinnerManager) { + sm.spinnerColor = c + } +} + +// WithWriter sets the writer used for the spinners. +func WithWriter(w io.Writer) Option { + return func(sm *spinnerManager) { + sm.writer = w + } } diff --git a/pkg/charmap/charmap.go b/pkg/charmap/charmap.go new file mode 100644 index 0000000..8ea6d10 --- /dev/null +++ b/pkg/charmap/charmap.go @@ -0,0 +1,25 @@ +package charmap + +var Dots = []string{ + "⠋", + "⠙", + "⠹", + "⠸", + "⠼", + "⠴", + "⠦", + "⠧", + "⠇", + "⠏", +} + +var Arrows = []string{ + "←", + "↖", + "↑", + "↗", + "→", + "↘", + "↓", + "↙", +} diff --git a/pkg/colors/colors.go b/pkg/colors/colors.go new file mode 100644 index 0000000..2cda44b --- /dev/null +++ b/pkg/colors/colors.go @@ -0,0 +1,32 @@ +package colors + +import "github.com/fatih/color" + +// Color represents an item in the color map. +type Color int + +const ( + // FgHiGreen is a foreground high intensity green color. + FgHiGreen Color = iota + + // FgHiYellow is a foreground high intensity yellow color. + FgHiYellow + + // FgHiBlue is a foreground high intensity blue color. + FgHiBlue + + // FgHiRed is a foreground high intensity red color. + FgHiRed +) + +var lookup = map[Color]color.Attribute{ + FgHiGreen: color.FgHiGreen, + FgHiYellow: color.FgHiYellow, + FgHiBlue: color.FgHiBlue, + FgHiRed: color.FgHiRed, +} + +// Get returns a color.Color for the given color. +func GetColor(c Color) *color.Color { + return color.New(lookup[c]) +} diff --git a/spinner.go b/spinner.go index 4f855db..4b452c5 100644 --- a/spinner.go +++ b/spinner.go @@ -1,33 +1,52 @@ package ysmrr -import "github.com/fatih/color" +import ( + "sync" + + "github.com/chelnak/ysmrr/pkg/colors" + "github.com/fatih/color" +) // Spinner manages a single spinner type spinner struct { - c *color.Color - msg string - complete bool - err bool + mutex sync.Mutex + spinnerColor *color.Color + completeColor *color.Color + errorColor *color.Color + msg string + complete bool + err bool } // Update updates the spinner message func (s *spinner) Update(msg string) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.msg = msg } // Complete marks the spinner as complete func (s *spinner) Complete() { + s.mutex.Lock() + defer s.mutex.Unlock() + s.complete = true } // Error marks the spinner as error func (s *spinner) Error() { + s.mutex.Lock() + defer s.mutex.Unlock() + s.err = true } -func NewSpinner(msg string, c *color.Color) *spinner { +func NewSpinner(msg string, spinnerColor, completeColor, errorColor colors.Color) *spinner { return &spinner{ - c: c, - msg: msg, + spinnerColor: colors.GetColor(spinnerColor), + completeColor: colors.GetColor(completeColor), + errorColor: colors.GetColor(errorColor), + msg: msg, } }