summaryrefslogtreecommitdiffstats
path: root/progress.go
blob: 766060512cc0de5affbc9fdfa58bb7dcb9096f1b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package progress

import (
	"fmt"
	"os"
	"strings"
	"time"

	"gitlab.com/rhogenson/vecdeque"
	"golang.org/x/term"
)

const measurements = 20

type measurement struct {
	t time.Time
	i int
}

type Bar struct {
	max          int
	measurements vecdeque.DQ[measurement]
	cols         int
}

func New(max int) *Bar {
	b := &Bar{max: max}
	b.measurements.Grow(measurements)
	return b
}

func (b *Bar) Set(i int) {
	if b.measurements.Len() == measurements {
		b.measurements.PopFront()
	}
	b.measurements.PushBack(measurement{time.Now(), i})
}

func (b *Bar) Print() {
	first := b.measurements.Get(0)
	last := b.measurements.Get(b.measurements.Len() - 1)
	deltaT := last.t.Sub(first.t)
	delta := last.i - first.i
	eta := time.Duration(-1)
	if delta != 0 {
		eta = time.Duration(float64(b.max-last.i) / float64(delta) * float64(deltaT))
	}

	if b.cols == 0 {
		var err error
		if b.cols, _, err = term.GetSize(int(os.Stderr.Fd())); err != nil {
			fmt.Fprintf(os.Stderr, "Warning: unable to determine terminal size: %s\n", err)
		}
	} else {
		p := last.i * b.cols / b.max
		fmt.Fprintf(os.Stderr, "\033[2F\033[J%s>\n", strings.Repeat("=", max(p-1, 0)))
	}
	if eta < 0 {
		fmt.Fprintln(os.Stderr, "ETA: calculating...")
	} else {
		fmt.Fprintf(os.Stderr, "ETA: %s\n", eta.Round(time.Second))
	}
}