From b69214c640723dbb85539b6ebca3fd61ab1be471 Mon Sep 17 00:00:00 2001 From: Craig Gumbley Date: Sun, 17 Jul 2022 15:42:47 +0100 Subject: [PATCH 1/5] Break out character maps This commit moves character maps in to their own package. This change may well be overkill but for now it feels right! --- pkg/charmap/charmap.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 pkg/charmap/charmap.go 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{ + "←", + "↖", + "↑", + "↗", + "→", + "↘", + "↓", + "↙", +} From d55f48649a00119ab3c8d55f41a7ae2b422f74c3 Mon Sep 17 00:00:00 2001 From: Craig Gumbley Date: Sun, 17 Jul 2022 15:45:07 +0100 Subject: [PATCH 2/5] Create color package This commit adds a new color package. It's purpose is to create an abstraction between the user and the underlying color package (github.com/faith/color). --- pkg/colors/colors.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 pkg/colors/colors.go 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]) +} From c7978d3d33b8c3cc00f38de79a3fdacc8b376455 Mon Sep 17 00:00:00 2001 From: Craig Gumbley Date: Sun, 17 Jul 2022 15:46:57 +0100 Subject: [PATCH 3/5] Use new packages This commit implements the two new packages added in the previous commits. It also expands configuration of the SpinnerManager using an options style pattern. --- manager.go | 79 +++++++++++++++++++++++++++++++++++++----------------- spinner.go | 23 ++++++++++------ 2 files changed, 70 insertions(+), 32 deletions(-) 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/spinner.go b/spinner.go index 4f855db..dbfca78 100644 --- a/spinner.go +++ b/spinner.go @@ -1,13 +1,18 @@ package ysmrr -import "github.com/fatih/color" +import ( + "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 + spinnerColor *color.Color + completeColor *color.Color + errorColor *color.Color + msg string + complete bool + err bool } // Update updates the spinner message @@ -25,9 +30,11 @@ func (s *spinner) Error() { 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, } } From a5528050f89756a80320218ce3d7b836977090d8 Mon Sep 17 00:00:00 2001 From: Craig Gumbley Date: Sun, 17 Jul 2022 15:48:18 +0100 Subject: [PATCH 4/5] Expand examples --- examples/main.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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...") From 7e55a805f76dbb96a46b877100b9b613123d211e Mon Sep 17 00:00:00 2001 From: Craig Gumbley Date: Sun, 17 Jul 2022 15:59:24 +0100 Subject: [PATCH 5/5] Add mutext to update operations --- spinner.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spinner.go b/spinner.go index dbfca78..4b452c5 100644 --- a/spinner.go +++ b/spinner.go @@ -1,12 +1,15 @@ package ysmrr import ( + "sync" + "github.com/chelnak/ysmrr/pkg/colors" "github.com/fatih/color" ) // Spinner manages a single spinner type spinner struct { + mutex sync.Mutex spinnerColor *color.Color completeColor *color.Color errorColor *color.Color @@ -17,16 +20,25 @@ type spinner struct { // 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 }