1package spinner 2 3import ( 4 "fmt" 5 "os" 6 "time" 7) 8 9var symbolList = []string{ 10 " | ", 11 " / ", 12 " – ", 13 " \\ ", 14} 15 16var symbolMap = map[string]string{ 17 " ⏳ ": "\033[37m", 18 " ⌛ ": "\033[38m", 19} 20 21type Spinner struct { 22 duration time.Duration 23 trigger interface{} 24 sentinel interface{} 25 closed bool 26 // sigChan: is the pipe that receives the start and stop 27 sigChan chan interface{} 28 // waitChan waits till the shutdown has fully propagated 29 waitChan chan interface{} 30} 31 32func New(freq int64) *Spinner { 33 if freq < 1 { 34 freq = 10 35 } 36 sp := Spinner{ 37 duration: time.Duration(1e9 / freq), 38 sentinel: nil, 39 // sigChan will be created on .Start() 40 sigChan: nil, 41 waitChan: make(chan interface{}), 42 } 43 44 sp.trigger = &sp 45 return &sp 46} 47 48func (s *Spinner) Start() error { 49 err := s.spin() 50 if err == nil { 51 s.sigChan <- s.trigger 52 } 53 return err 54} 55 56func (s *Spinner) Stop() { 57 if !s.closed && s.sigChan != nil { 58 s.sigChan <- s.sentinel 59 close(s.sigChan) 60 <-s.waitChan 61 s.closed = true 62 } 63} 64 65func (s *Spinner) Reset() { 66 s.Stop() 67 s.sigChan = nil 68 s.closed = true 69} 70 71func (s *Spinner) Duration() time.Duration { 72 return s.duration 73} 74 75func (s *Spinner) spin() error { 76 if s.sigChan != nil { // Already in use 77 return fmt.Errorf("already in use") 78 } 79 s.sigChan = make(chan interface{}) 80 go func() { 81 // Block till the first symbol comes through 82 <-s.sigChan 83 84 throttle := time.Tick(s.duration) 85 running := true 86 for running { 87 for _, segment := range symbolList { 88 select { 89 case in := <-s.sigChan: 90 if in == s.sentinel { 91 os.Stderr.Sync() 92 running = false 93 s.waitChan <- s.sentinel 94 break 95 } 96 default: 97 } 98 // Print it to stderr to avoid symbol getting into piped content 99 fmt.Fprintf(os.Stderr, "%s\r", segment) 100 <-throttle 101 } 102 } 103 }() 104 return nil 105} 106