11 "github.com/olekukonko/ts"
15 DefaultLoggingThrottle = 200 * time.Millisecond
18 // Logger logs a series of tasks to an io.Writer, processing each task in order
21 // sink is the writer to write to.
24 // widthFn is a function that returns the width of the terminal that
25 // this logger is running within.
28 // throttle is the minimum amount of time that must pass between each
29 // instant data is logged.
30 throttle time.Duration
32 // queue is the incoming, unbuffered queue of tasks to enqueue.
34 // tasks is the set of tasks to process.
36 // wg is a WaitGroup that is incremented when new tasks are enqueued,
37 // and decremented when tasks finish.
41 // NewLogger retuns a new *Logger instance that logs to "sink" and uses the
42 // current terminal width as the width of the line.
43 func NewLogger(sink io.Writer) *Logger {
50 throttle: DefaultLoggingThrottle,
52 size, err := ts.GetSize()
58 queue: make(chan Task),
59 tasks: make(chan Task),
60 wg: new(sync.WaitGroup),
68 // Close closes the queue and does not allow new Tasks to be `enqueue()`'d. It
69 // waits until the currently running Task has completed.
70 func (l *Logger) Close() {
80 // Waitier creates and enqueues a new *WaitingTask.
81 func (l *Logger) Waiter(msg string) *WaitingTask {
82 t := NewWaitingTask(msg)
88 // Percentage creates and enqueues a new *PercentageTask.
89 func (l *Logger) Percentage(msg string, total uint64) *PercentageTask {
90 t := NewPercentageTask(msg, total)
96 // List creates and enqueues a new *ListTask.
97 func (l *Logger) List(msg string) *ListTask {
104 // List creates and enqueues a new *SimpleTask.
105 func (l *Logger) Simple() *SimpleTask {
112 // Enqueue enqueues the given Tasks "ts".
113 func (l *Logger) Enqueue(ts ...Task) {
115 for _, t := range ts {
117 // NOTE: Do not allow nil tasks which are unable
122 for range t.Updates() {
123 // Discard all updates.
131 for _, t := range ts {
140 // consume creates a pseudo-infinte buffer between the incoming set of tasks and
141 // the queue of tasks to work on.
142 func (l *Logger) consume() {
144 // Process the single next task in sequence until completion,
145 // then consume the next task.
146 for task := range l.tasks {
153 pending := make([]Task, 0)
156 // If there is a pending task, "peek" it off of the set of
159 if len(pending) > 0 {
164 // If there was no pending task, wait for either a)
165 // l.queue to close, or b) a new task to be submitted.
166 task, ok := <-l.queue
168 // If the queue is closed, no more new tasks may
173 // Otherwise, add a new task to the set of tasks to
174 // process immediately, since there is no current
178 // If there is a pending task, wait for either a) a
179 // write to process the task to become non-blocking, or
180 // b) a new task to enter the queue.
182 case task, ok := <-l.queue:
184 // If the queue is closed, no more tasks
188 // Otherwise, add the next task to the set of
189 // pending, active tasks.
190 pending = append(pending, task)
191 case l.tasks <- next:
192 // Or "pop" the peeked task off of the pending
194 pending = pending[1:]
200 // logTask logs the set of updates from a given task to the sink, then logs a
201 // "done" message, and then marks the task as done.
203 // By default, the *Logger throttles log entry updates to once per the duration
204 // of time specified by `l.throttle time.Duration`.
206 // If the duration if 0, or the task is "durable" (by implementing
207 // github.com/git-lfs/git-lfs/tasklog#DurableTask), then all entries will be
209 func (l *Logger) logTask(task Task) {
212 logAll := !task.Throttled()
216 for update = range task.Updates() {
217 if logAll || l.throttle == 0 || !update.Throttled(last.Add(l.throttle)) {
224 // If a task sent no updates, the last recorded update will be
225 // nil. Given this, only log a message when there was at least
227 l.log(fmt.Sprintf("%s, done\n", update.S))
230 if v, ok := task.(interface {
231 // OnComplete is called after the Task "task" is closed, but
232 // before new tasks are accepted.
235 // If the Task implements this interface, call it and block
236 // before accepting new tasks.
241 // logLine writes a complete line and moves the cursor to the beginning of the
244 // It returns the number of bytes "n" written to the sink and the error "err",
245 // if one was encountered.
246 func (l *Logger) logLine(str string) (n int, err error) {
247 padding := strings.Repeat(" ", maxInt(0, l.widthFn()-len(str)))
249 return l.log(str + padding + "\r")
252 // log writes a string verbatim to the sink.
254 // It returns the number of bytes "n" written to the sink and the error "err",
255 // if one was encountered.
256 func (l *Logger) log(str string) (n int, err error) {
257 return fmt.Fprint(l.sink, str)
260 func maxInt(a, b int) int {