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  }