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