github.com/seeker-insurance/kit@v0.0.13/tickertape/tickertape.go (about) 1 //Package tickertape provides an implenetation of a concurrency-safe 'ticker tape' of current information during a running program - that is, repeatedly updating the same line with new information. 2 package tickertape 3 4 import ( 5 "fmt" 6 "io" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/seeker-insurance/kit/errorlib" 12 ) 13 14 const ( 15 bufSize = 30 16 defaultPollingPeriod = 300 * time.Millisecond 17 defaultSignalPeriod = 1000 * time.Millisecond 18 19 ErrNotListening errorlib.ErrorString = "tickertape is not listening" 20 ErrAlreadyClosed errorlib.ErrorString = "cannot print to a closed tickertape" 21 ) 22 23 type _signal struct{} 24 25 var signal _signal 26 var internalTicker TickerTape 27 28 var _ io.WriteCloser = &TickerTape{} 29 30 //TickerTape is a struct for printing progress information one at a time, on the same line. 31 type TickerTape struct { 32 mux sync.Mutex //guards listening and closed 33 listening bool 34 closed bool 35 36 done chan _signal 37 events chan string 38 signals chan _signal 39 40 signalPeriod, pollingPeriod time.Duration 41 } 42 43 func (ticker *TickerTape) startListening() { 44 if ticker.signalPeriod == 0 { 45 ticker.signalPeriod = defaultSignalPeriod 46 } 47 if ticker.pollingPeriod == 0 { 48 ticker.pollingPeriod = defaultPollingPeriod 49 } 50 ticker.listening = true 51 ticker.events = make(chan string, bufSize) 52 ticker.signals = make(chan _signal, bufSize) 53 ticker.done = make(chan _signal, 2) 54 go ticker.listen() 55 go ticker.repeatSignal() 56 } 57 58 func (ticker *TickerTape) repeatSignal() { 59 defer close(ticker.signals) 60 for { 61 select { 62 case <-ticker.done: 63 return 64 65 default: 66 ticker.signals <- signal 67 time.Sleep(ticker.signalPeriod) 68 } 69 } 70 } 71 72 //Close the tickertape. 73 func (ticker *TickerTape) Close() error { 74 defer close(ticker.done) 75 ticker.done <- signal // stop repeatSignal 76 ticker.done <- signal // stop listen 77 return nil 78 } 79 80 func (ticker *TickerTape) listen() { 81 defer close(ticker.events) 82 for { 83 select { 84 case event := <-ticker.events: 85 fmt.Print("\r", strings.Repeat(" ", 120)) 86 fmt.Print(strings.Repeat("\b", 120)) 87 fmt.Print("\r", event) 88 // we want to be able to see each message as it comes up. 89 case <-ticker.signals: 90 fmt.Print(".") 91 case <-ticker.done: 92 close(ticker.events) 93 return 94 } 95 } 96 97 } 98 99 //Printf prints to a ticker 100 func (ticker *TickerTape) Printf(format string, args ...interface{}) bool { 101 return ticker.print(fmt.Sprintf(format, args...)) 102 } 103 104 func (ticker *TickerTape) print(s string) bool { 105 ticker.mux.Lock() 106 if ticker.closed { 107 return false 108 } 109 110 if !ticker.listening { 111 ticker.startListening() 112 } 113 ticker.mux.Unlock() 114 ticker.events <- s 115 return true 116 } 117 118 //Print a line through the ticker like fmt.Print 119 func (ticker *TickerTape) Print(args ...interface{}) bool { 120 return ticker.print(fmt.Sprint(args...)) 121 } 122 123 func (ticker *TickerTape) Write(b []byte) (n int, err error) { 124 if !ticker.listening { 125 return 0, ErrNotListening 126 } 127 ok := ticker.print(string(b)) 128 if !ok { 129 return 0, ErrAlreadyClosed 130 } 131 return len(b), nil 132 } 133 134 //Printf to the internal ticker. 135 func Printf(format string, args ...interface{}) { 136 internalTicker.Printf(format, args...) 137 } 138 139 //Print to the internal ticker. 140 func Print(args ...interface{}) { 141 internalTicker.Print(args...) 142 }