github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/pkg/terminal/step.go (about) 1 package terminal 2 3 import ( 4 "context" 5 "io" 6 ) 7 8 const ( 9 TermRows = 10 10 TermColumns = 100 11 ) 12 13 // fancyStepGroup implements StepGroup with live updating and a display 14 // "window" for live terminal output (when using TermOutput). 15 type fancyStepGroup struct { 16 ctx context.Context 17 cancel func() 18 19 display *Display 20 21 steps int 22 done chan struct{} 23 } 24 25 // Start a step in the output 26 func (f *fancyStepGroup) Add(str string, args ...interface{}) Step { 27 f.steps++ 28 29 ent := f.display.NewStatus(0) 30 31 ent.StartSpinner() 32 ent.Update(str, args...) 33 34 return &fancyStep{ 35 sg: f, 36 ent: ent, 37 } 38 } 39 40 func (f *fancyStepGroup) Wait() { 41 if f.steps > 0 { 42 loop: 43 for { 44 select { 45 case <-f.done: 46 f.steps-- 47 48 if f.steps <= 0 { 49 break loop 50 } 51 case <-f.ctx.Done(): 52 break loop 53 } 54 } 55 } 56 57 f.cancel() 58 59 f.display.Close() 60 } 61 62 type fancyStep struct { 63 sg *fancyStepGroup 64 ent *DisplayEntry 65 66 done bool 67 status string 68 69 term *Term 70 } 71 72 func (f *fancyStep) TermOutput() io.Writer { 73 if f.term == nil { 74 t, err := NewTerm(f.sg.ctx, f.ent, TermRows, TermColumns) 75 if err != nil { 76 panic(err) 77 } 78 79 f.term = t 80 } 81 82 return f.term 83 } 84 85 func (f *fancyStep) Update(str string, args ...interface{}) { 86 f.ent.Update(str, args...) 87 } 88 89 func (f *fancyStep) Status(status string) { 90 f.status = status 91 f.ent.SetStatus(status) 92 } 93 94 func (f *fancyStep) Done() { 95 if f.done { 96 return 97 } 98 99 if f.status == "" { 100 f.Status(StatusOK) 101 } 102 103 f.signalDone() 104 } 105 106 func (f *fancyStep) Abort() { 107 if f.done { 108 return 109 } 110 111 f.Status(StatusError) 112 f.signalDone() 113 } 114 115 func (f *fancyStep) signalDone() { 116 f.done = true 117 f.ent.StopSpinner() 118 119 // We don't want to block here because Wait might not yet have been 120 // called. So instead we just spawn the wait update in a goroutine 121 // that can also be cancaled by the context. 122 go func() { 123 select { 124 case f.sg.done <- struct{}{}: 125 case <-f.sg.ctx.Done(): 126 return 127 } 128 }() 129 }