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  }