commit a2418034767785ae51f5a92fdf0e3d8e5889d55f Author: milarin Date: Fri Mar 3 19:52:34 2023 +0100 initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..23468a7 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.milar.in/milarin/statview + +go 1.19 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/print.go b/print.go new file mode 100644 index 0000000..d5319db --- /dev/null +++ b/print.go @@ -0,0 +1,32 @@ +package statview + +import ( + "fmt" + "io" + "strings" +) + +func clearEOL(w io.Writer) { + w.Write([]byte{0x1b, 0x5b, 0x4b}) +} + +func goUp(w io.Writer) { + w.Write([]byte{0x1b, 0x5b, 0x41}) +} + +func (v *View[K]) print() { + for i := 0; i < v.lastLineAmount; i++ { + goUp(v.writer) + clearEOL(v.writer) + } + + lineAmount := 0 + for _, task := range v.tasks { + if report, ok := v.lastReports[task]; ok { + lineAmount += len(strings.Split(report.Text, "\n")) + fmt.Fprintln(v.writer, report.Text) + } + } + + v.lastLineAmount = lineAmount +} diff --git a/report.go b/report.go new file mode 100644 index 0000000..d8dcee4 --- /dev/null +++ b/report.go @@ -0,0 +1,6 @@ +package statview + +type report[K comparable] struct { + ID K + Text string +} diff --git a/stat_test.go b/stat_test.go new file mode 100644 index 0000000..32d7a8d --- /dev/null +++ b/stat_test.go @@ -0,0 +1,25 @@ +package statview + +import ( + "fmt" + "testing" + "time" +) + +func TestStat(t *testing.T) { + view := New[int]() + + for i := 0; i < 5; i++ { + view.Add(i) + go func(id int) { + defer view.Done(id) + + for p := 0; p <= 100; p++ { + view.Report(id, fmt.Sprintf("task %d: %3d%%", id, p)) + time.Sleep(time.Duration(id+1) * 10 * time.Millisecond) + } + }(i) + } + + view.Show() +} diff --git a/view.go b/view.go new file mode 100644 index 0000000..5dc57ba --- /dev/null +++ b/view.go @@ -0,0 +1,79 @@ +package statview + +import ( + "io" + "os" + "sort" + "sync" +) + +type View[K comparable] struct { + writer io.Writer + sortFunc func(i, j K) bool + + lastLineAmount int + lastReports map[K]report[K] + + tasks []K + taskMap map[K]struct{} + + wg *sync.WaitGroup + reportCh chan report[K] +} + +func New[K comparable]() *View[K] { + return NewForWriter[K](os.Stderr) +} + +func NewForWriter[K comparable](w io.Writer) *View[K] { + return &View[K]{ + writer: w, + tasks: make([]K, 0), + taskMap: map[K]struct{}{}, + + reportCh: make(chan report[K], 100), + lastReports: map[K]report[K]{}, + wg: &sync.WaitGroup{}, + } +} + +func (v *View[K]) Show() { + go func() { + defer close(v.reportCh) + v.wg.Wait() + }() + + for status := range v.reportCh { + v.lastReports[status.ID] = status + v.Add(status.ID) + v.print() + } +} + +func (v *View[K]) SortFunc(f func(i, j K) bool) { + v.sortFunc = f +} + +func (v *View[K]) Add(task K) { + if _, ok := v.taskMap[task]; ok { + return + } + + v.wg.Add(1) + v.tasks = append(v.tasks, task) + v.taskMap[task] = struct{}{} + + if v.sortFunc != nil { + sort.Slice(v.tasks, func(i, j int) bool { + return v.sortFunc(v.tasks[i], v.tasks[j]) + }) + } +} + +func (v *View[K]) Report(task K, status string) { + v.reportCh <- report[K]{ID: task, Text: status} +} + +func (v *View[K]) Done(task K) { + v.wg.Done() +}