1package output
2
3import (
4	"fmt"
5	"io"
6	"sort"
7	"strings"
8	"sync"
9	"time"
10
11	"github.com/logrusorgru/aurora"
12
13	"github.com/briandowns/spinner"
14
15	"github.com/taskctl/taskctl/pkg/task"
16)
17
18var frame = 100 * time.Millisecond
19var base *baseCockpit
20
21type baseCockpit struct {
22	w       io.Writer
23	tasks   []*task.Task
24	mu      sync.Mutex
25	spinner *spinner.Spinner
26	charSet int
27	closeCh chan bool
28}
29
30type cockpitOutputDecorator struct {
31	b *baseCockpit
32	t *task.Task
33}
34
35func (b *baseCockpit) start() *spinner.Spinner {
36	if b.spinner != nil {
37		return b.spinner
38	}
39
40	s := spinner.New(spinner.CharSets[b.charSet], frame, spinner.WithColor("yellow"))
41	s.Writer = b.w
42	s.PreUpdate = func(s *spinner.Spinner) {
43		tasks := make([]string, 0)
44		b.mu.Lock()
45		for _, v := range b.tasks {
46			tasks = append(tasks, v.Name)
47		}
48		defer b.mu.Unlock()
49		sort.Strings(tasks)
50		s.Suffix = " Running: " + strings.Join(tasks, ", ")
51	}
52	s.Start()
53
54	return s
55}
56
57func (b *baseCockpit) add(t *task.Task) {
58	b.mu.Lock()
59	defer b.mu.Unlock()
60
61	b.tasks = append(b.tasks, t)
62
63	if b.spinner == nil {
64		b.spinner = b.start()
65		go func() {
66			<-b.closeCh
67			b.spinner.Stop()
68		}()
69	}
70}
71
72func (b *baseCockpit) remove(t *task.Task) {
73	b.mu.Lock()
74	defer b.mu.Unlock()
75
76	for k, v := range b.tasks {
77		if v == t {
78			b.tasks = append(b.tasks[:k], b.tasks[k+1:]...)
79		}
80	}
81
82	var mark = aurora.Green("✔")
83	if t.Errored {
84		mark = aurora.Red("✗")
85	}
86	b.spinner.FinalMSG = fmt.Sprintf("%s Finished %s in %s\r\n", mark, aurora.Bold(t.Name), t.Duration())
87	b.spinner.Restart()
88	b.spinner.FinalMSG = ""
89}
90
91func newCockpitOutputWriter(t *task.Task, w io.Writer, close chan bool) *cockpitOutputDecorator {
92	if base == nil {
93		base = &baseCockpit{
94			charSet: 14,
95			w:       w,
96			tasks:   make([]*task.Task, 0),
97			closeCh: close,
98		}
99	}
100
101	return &cockpitOutputDecorator{
102		t: t,
103		b: base,
104	}
105}
106
107func (d *cockpitOutputDecorator) Write(p []byte) (int, error) {
108	return len(p), nil
109}
110
111func (d *cockpitOutputDecorator) WriteHeader() error {
112	d.b.add(d.t)
113	return nil
114}
115
116func (d *cockpitOutputDecorator) WriteFooter() error {
117	d.b.remove(d.t)
118	return nil
119}
120