Skip to content

Commit

Permalink
Merge pull request #90 from briandowns/issue-89
Browse files Browse the repository at this point in the history
Issue 89
  • Loading branch information
briandowns authored Mar 25, 2020
2 parents 6dc2240 + 4de45d3 commit db9a250
Show file tree
Hide file tree
Showing 21 changed files with 70 additions and 4,114 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go
go:
- 1.11
- 1.12.5
- 1.13
- 1.14.1
env:
- GOARCH: amd64
- GOARCH: 386
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,13 @@ fmt.Println(s.Active())

Feature suggested and write up by [dekz](https://github.com/dekz)

Setting the Spinner Writer to Stderr helps show progress to the user, with the enhancement to chain, pipe or redirect the output.
Setting the Spinner Writer to Stderr helps show progress to the user, with the enhancement to chain, pipe or redirect the output.

This is the preferred method of setting a Writer at this time.

```go
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
s := spinner.New(spinner.CharSets[11], 100*time.Millisecond, spinner.WithWriter(os.Stderr))
s.Suffix = " Encrypting data..."
s.Writer = os.Stderr
s.Start()
// Encrypt the data into ciphertext
fmt.Println(os.Stdout, ciphertext)
Expand Down
2 changes: 0 additions & 2 deletions _example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ func main() {
}

s.UpdateCharSet(spinner.CharSets[20])

s.Reverse()

s.Restart()

time.Sleep(4 * time.Second) // Run for some time to simulate work
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module github.com/briandowns/spinner

go 1.14

require (
github.com/fatih/color v1.7.0
github.com/mattn/go-colorable v0.1.2 // indirect
Expand Down
94 changes: 52 additions & 42 deletions spinner.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,39 +161,39 @@ var colorAttributeMap = map[string]color.Attribute{
"bgHiWhite": color.BgHiWhite,
}

// validColor will make sure the given color is actually allowed
// validColor will make sure the given color is actually allowed.
func validColor(c string) bool {
if validColors[c] {
return true
}
return false
}

// Spinner struct to hold the provided options
// Spinner struct to hold the provided options.
type Spinner struct {
mu *sync.RWMutex //
Delay time.Duration // Delay is the speed of the indicator
chars []string // chars holds the chosen character set
Prefix string // Prefix is the text preppended to the indicator
Suffix string // Suffix is the text appended to the indicator
FinalMSG string // string displayed after Stop() is called
lastOutput string // last character(set) written
color func(a ...interface{}) string // default color is white
lock *sync.RWMutex //
Writer io.Writer // to make testing better, exported so users have access
Writer io.Writer // to make testing better, exported so users have access. Use `WithWriter` to update after initialization.
active bool // active holds the state of the spinner
stopChan chan struct{} // stopChan is a channel used to stop the indicator
HideCursor bool // hideCursor determines if the cursor is visible
PreUpdate func(s *Spinner) // will be triggered before every spinner update
PostUpdate func(s *Spinner) // will be triggered after every spinner update
}

// New provides a pointer to an instance of Spinner with the supplied options
// New provides a pointer to an instance of Spinner with the supplied options.
func New(cs []string, d time.Duration, options ...Option) *Spinner {
s := &Spinner{
Delay: d,
chars: cs,
color: color.New(color.FgWhite).SprintFunc(),
lock: &sync.RWMutex{},
mu: &sync.RWMutex{},
Writer: color.Output,
active: false,
stopChan: make(chan struct{}, 1),
Expand All @@ -206,66 +206,77 @@ func New(cs []string, d time.Duration, options ...Option) *Spinner {
}

// Option is a function that takes a spinner and applies
// a given configuration
// a given configuration.
type Option func(*Spinner)

// Options contains fields to configure the spinner
// Options contains fields to configure the spinner.
type Options struct {
Color string
Suffix string
FinalMSG string
HideCursor bool
}

// WithColor adds the given color to the spinner
// WithColor adds the given color to the spinner.
func WithColor(color string) Option {
return func(s *Spinner) {
s.Color(color)
}
}

// WithSuffix adds the given string to the spinner
// as the suffix
// as the suffix.
func WithSuffix(suffix string) Option {
return func(s *Spinner) {
s.Suffix = suffix
}
}

// WithFinalMSG adds the given string ot the spinner
// as the final message to be written
// as the final message to be written.
func WithFinalMSG(finalMsg string) Option {
return func(s *Spinner) {
s.FinalMSG = finalMsg
}
}

// WithHiddenCursor hides the cursor
// if hideCursor = true given
// if hideCursor = true given.
func WithHiddenCursor(hideCursor bool) Option {
return func(s *Spinner) {
s.HideCursor = hideCursor
}
}

// Active will return whether or not the spinner is currently active
// WithWriter adds the given writer to the spinner. This
// function should be favored over directly assigning to
// the struct value.
func WithWriter(w io.Writer) Option {
return func(s *Spinner) {
s.mu.Lock()
s.Writer = w
s.mu.Unlock()
}
}

// Active will return whether or not the spinner is currently active.
func (s *Spinner) Active() bool {
return s.active
}

// Start will start the indicator
// Start will start the indicator.
func (s *Spinner) Start() {
s.lock.Lock()
s.mu.Lock()
if s.active {
s.lock.Unlock()
s.mu.Unlock()
return
}
if s.HideCursor && runtime.GOOS != "windows" {
// hides the cursor
fmt.Print("\033[?25l")
}
s.active = true
s.lock.Unlock()
s.mu.Unlock()

go func() {
for {
Expand All @@ -274,7 +285,7 @@ func (s *Spinner) Start() {
case <-s.stopChan:
return
default:
s.lock.Lock()
s.mu.Lock()
s.erase()
if !s.active {
return
Expand Down Expand Up @@ -303,18 +314,18 @@ func (s *Spinner) Start() {
s.PostUpdate(s)
}

s.lock.Unlock()
s.mu.Unlock()
time.Sleep(delay)
}
}
}
}()
}

// Stop stops the indicator
// Stop stops the indicator.
func (s *Spinner) Stop() {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
if s.active {
s.active = false
if s.HideCursor && runtime.GOOS != "windows" {
Expand All @@ -329,22 +340,22 @@ func (s *Spinner) Stop() {
}
}

// Restart will stop and start the indicator
// Restart will stop and start the indicator.
func (s *Spinner) Restart() {
s.Stop()
s.Start()
}

// Reverse will reverse the order of the slice assigned to the indicator
// Reverse will reverse the order of the slice assigned to the indicator.
func (s *Spinner) Reverse() {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
for i, j := 0, len(s.chars)-1; i < j; i, j = i+1, j-1 {
s.chars[i], s.chars[j] = s.chars[j], s.chars[i]
}
}

// Color will set the struct field for the given color to be used
// Color will set the struct field for the given color to be used.
func (s *Spinner) Color(colors ...string) error {
colorAttributes := make([]color.Attribute, len(colors))

Expand All @@ -356,29 +367,28 @@ func (s *Spinner) Color(colors ...string) error {
colorAttributes[index] = colorAttributeMap[c]
}

s.lock.Lock()
s.mu.Lock()
s.color = color.New(colorAttributes...).SprintFunc()
s.lock.Unlock()
s.mu.Unlock()
s.Restart()
return nil
}

// UpdateSpeed will set the indicator delay to the given value
// UpdateSpeed will set the indicator delay to the given value.
func (s *Spinner) UpdateSpeed(d time.Duration) {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
s.Delay = d
}

// UpdateCharSet will change the current character set to the given one
// UpdateCharSet will change the current character set to the given one.
func (s *Spinner) UpdateCharSet(cs []string) {
s.lock.Lock()
defer s.lock.Unlock()
s.mu.Lock()
defer s.mu.Unlock()
s.chars = cs
}

// erase deletes written characters
//
// erase deletes written characters.
// Caller must already hold s.lock.
func (s *Spinner) erase() {
n := utf8.RuneCountInString(s.lastOutput)
Expand All @@ -401,18 +411,18 @@ func (s *Spinner) erase() {
s.lastOutput = ""
}

// Lock allows for manual control to lock the spinner
// Lock allows for manual control to lock the spinner.
func (s *Spinner) Lock() {
s.lock.Lock()
s.mu.Lock()
}

// Unlock allows for manual control to unlock the spinner
// Unlock allows for manual control to unlock the spinner.
func (s *Spinner) Unlock() {
s.lock.Unlock()
s.mu.Unlock()
}

// GenerateNumberSequence will generate a slice of integers at the
// provided length and convert them each to a string
// provided length and convert them each to a string.
func GenerateNumberSequence(length int) []string {
numSeq := make([]string, length)
for i := 0; i < length; i++ {
Expand Down
10 changes: 8 additions & 2 deletions spinner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package spinner
import (
"bytes"
"fmt"
"io/ioutil"
"reflect"
"sync"
"testing"
Expand Down Expand Up @@ -231,8 +232,8 @@ func TestBackspace(t *testing.T) {
func TestColorError(t *testing.T) {
s := New(CharSets[0], 100*time.Millisecond)

invalidColorName := "bluez"
validColorName := "green"
const invalidColorName = "bluez"
const validColorName = "green"

if s.Color(invalidColorName) != errInvalidColor {
t.Error("Color method did not return an error when given an invalid color.")
Expand All @@ -243,6 +244,11 @@ func TestColorError(t *testing.T) {
}
}

func TestWithWriter(t *testing.T) {
s := New(CharSets[9], time.Millisecond*400, WithWriter(ioutil.Discard))
_ = s
}

/*
Benchmarks
*/
Expand Down
61 changes: 0 additions & 61 deletions vendor/golang.org/x/sys/unix/mkasm_darwin.go

This file was deleted.

Loading

0 comments on commit db9a250

Please sign in to comment.