github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/pkg/terminal/noninteractive.go (about)

     1  package terminal
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"strings"
    10  	"sync"
    11  	"text/tabwriter"
    12  
    13  	"github.com/fatih/color"
    14  	"github.com/olekukonko/tablewriter"
    15  )
    16  
    17  type nonInteractiveUI struct {
    18  	mu sync.Mutex
    19  }
    20  
    21  func NonInteractiveUI(ctx context.Context) UI {
    22  	result := &nonInteractiveUI{}
    23  	return result
    24  }
    25  
    26  func (ui *nonInteractiveUI) Input(input *Input) (string, error) {
    27  	return "", ErrNonInteractive
    28  }
    29  
    30  // Interactive implements UI
    31  func (ui *nonInteractiveUI) Interactive() bool {
    32  	return false
    33  }
    34  
    35  // Output implements UI
    36  func (ui *nonInteractiveUI) Output(msg string, raw ...interface{}) {
    37  	ui.mu.Lock()
    38  	defer ui.mu.Unlock()
    39  	msg, style, w := Interpret(msg, raw...)
    40  
    41  	switch style {
    42  	case HeaderStyle:
    43  		msg = "\n» " + msg
    44  	case ErrorStyle, ErrorBoldStyle:
    45  		lines := strings.Split(msg, "\n")
    46  		if len(lines) > 0 {
    47  			fmt.Fprintln(w, "! "+lines[0])
    48  			for _, line := range lines[1:] {
    49  				fmt.Fprintln(w, "  "+line)
    50  			}
    51  		}
    52  
    53  		return
    54  
    55  	case WarningStyle, WarningBoldStyle:
    56  		msg = "warning: " + msg
    57  
    58  	case SuccessStyle, SuccessBoldStyle:
    59  
    60  	case InfoStyle:
    61  		lines := strings.Split(msg, "\n")
    62  		for i, line := range lines {
    63  			lines[i] = colorInfo.Sprintf("  %s", line)
    64  		}
    65  
    66  		msg = strings.Join(lines, "\n")
    67  	}
    68  
    69  	fmt.Fprintln(w, msg)
    70  }
    71  
    72  // NamedValues implements UI
    73  func (ui *nonInteractiveUI) NamedValues(rows []NamedValue, opts ...Option) {
    74  	ui.mu.Lock()
    75  	defer ui.mu.Unlock()
    76  
    77  	cfg := &config{Writer: color.Output}
    78  	for _, opt := range opts {
    79  		opt(cfg)
    80  	}
    81  
    82  	var buf bytes.Buffer
    83  	tr := tabwriter.NewWriter(&buf, 1, 8, 0, ' ', tabwriter.AlignRight)
    84  	for _, row := range rows {
    85  		switch v := row.Value.(type) {
    86  		case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
    87  			fmt.Fprintf(tr, "  %s: \t%d\n", row.Name, row.Value)
    88  		case float32, float64:
    89  			fmt.Fprintf(tr, "  %s: \t%f\n", row.Name, row.Value)
    90  		case bool:
    91  			fmt.Fprintf(tr, "  %s: \t%v\n", row.Name, row.Value)
    92  		case string:
    93  			if v == "" {
    94  				continue
    95  			}
    96  			fmt.Fprintf(tr, "  %s: \t%s\n", row.Name, row.Value)
    97  		default:
    98  			fmt.Fprintf(tr, "  %s: \t%s\n", row.Name, row.Value)
    99  		}
   100  	}
   101  	tr.Flush()
   102  
   103  	fmt.Fprintln(cfg.Writer, buf.String())
   104  }
   105  
   106  // OutputWriters implements UI
   107  func (ui *nonInteractiveUI) OutputWriters() (io.Writer, io.Writer, error) {
   108  	return os.Stdout, os.Stderr, nil
   109  }
   110  
   111  // Status implements UI
   112  func (ui *nonInteractiveUI) Status() Status {
   113  	return &nonInteractiveStatus{mu: &ui.mu}
   114  }
   115  
   116  func (ui *nonInteractiveUI) StepGroup() StepGroup {
   117  	return &nonInteractiveStepGroup{mu: &ui.mu}
   118  }
   119  
   120  // Table implements UI
   121  func (ui *nonInteractiveUI) Table(tbl *Table, opts ...Option) {
   122  	ui.mu.Lock()
   123  	defer ui.mu.Unlock()
   124  
   125  	// Build our config and set our options
   126  	cfg := &config{Writer: color.Output}
   127  	for _, opt := range opts {
   128  		opt(cfg)
   129  	}
   130  
   131  	table := tablewriter.NewWriter(cfg.Writer)
   132  	table.SetHeader(tbl.Headers)
   133  	table.SetBorder(false)
   134  	table.SetAutoWrapText(false)
   135  
   136  	if cfg.Style == "Simple" {
   137  		// Format the table without borders, simple output
   138  
   139  		table.SetAutoFormatHeaders(true)
   140  		table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
   141  		table.SetAlignment(tablewriter.ALIGN_LEFT)
   142  		table.SetCenterSeparator("")
   143  		table.SetColumnSeparator("")
   144  		table.SetRowSeparator("")
   145  		table.SetHeaderLine(false)
   146  		table.SetTablePadding("\t") // pad with tabs
   147  		table.SetNoWhiteSpace(true)
   148  	}
   149  
   150  	for _, row := range tbl.Rows {
   151  		colors := make([]tablewriter.Colors, len(row))
   152  		entries := make([]string, len(row))
   153  
   154  		for i, ent := range row {
   155  			entries[i] = ent.Value
   156  
   157  			color, ok := colorMapping[ent.Color]
   158  			if ok {
   159  				colors[i] = tablewriter.Colors{color}
   160  			}
   161  		}
   162  
   163  		table.Rich(entries, colors)
   164  	}
   165  
   166  	table.Render()
   167  }
   168  
   169  type nonInteractiveStatus struct {
   170  	mu *sync.Mutex
   171  }
   172  
   173  func (s *nonInteractiveStatus) Update(msg string) {
   174  	s.mu.Lock()
   175  	defer s.mu.Unlock()
   176  	fmt.Fprintln(color.Output, msg)
   177  }
   178  
   179  func (s *nonInteractiveStatus) Step(status, msg string) {
   180  	s.mu.Lock()
   181  	defer s.mu.Unlock()
   182  	fmt.Fprintf(color.Output, "%s: %s\n", textStatus[status], msg)
   183  }
   184  
   185  func (s *nonInteractiveStatus) Close() error {
   186  	return nil
   187  }
   188  
   189  type nonInteractiveStepGroup struct {
   190  	mu     *sync.Mutex
   191  	wg     sync.WaitGroup
   192  	closed bool
   193  }
   194  
   195  // Start a step in the output
   196  func (f *nonInteractiveStepGroup) Add(str string, args ...interface{}) Step {
   197  	// Build our step
   198  	step := &nonInteractiveStep{mu: f.mu}
   199  
   200  	// Setup initial status
   201  	step.Update(str, args...)
   202  
   203  	// Grab the lock now so we can update our fields
   204  	f.mu.Lock()
   205  	defer f.mu.Unlock()
   206  
   207  	// If we're closed we don't add this step to our waitgroup or document.
   208  	// We still create a step and return a non-nil step so downstreams don't
   209  	// crash.
   210  	if !f.closed {
   211  		// Add since we have a step
   212  		step.wg = &f.wg
   213  		f.wg.Add(1)
   214  	}
   215  
   216  	return step
   217  }
   218  
   219  func (f *nonInteractiveStepGroup) Wait() {
   220  	f.mu.Lock()
   221  	f.closed = true
   222  	wg := &f.wg
   223  	f.mu.Unlock()
   224  
   225  	wg.Wait()
   226  }
   227  
   228  type nonInteractiveStep struct {
   229  	mu   *sync.Mutex
   230  	wg   *sync.WaitGroup
   231  	done bool
   232  }
   233  
   234  func (f *nonInteractiveStep) TermOutput() io.Writer {
   235  	return &stripAnsiWriter{Next: color.Output}
   236  }
   237  
   238  func (f *nonInteractiveStep) Update(str string, args ...interface{}) {
   239  	f.mu.Lock()
   240  	defer f.mu.Unlock()
   241  	fmt.Fprintln(color.Output, "-> "+fmt.Sprintf(str, args...))
   242  }
   243  
   244  func (f *nonInteractiveStep) Status(status string) {}
   245  
   246  func (f *nonInteractiveStep) Done() {
   247  	f.mu.Lock()
   248  	defer f.mu.Unlock()
   249  
   250  	if f.done {
   251  		return
   252  	}
   253  
   254  	// Set done
   255  	f.done = true
   256  
   257  	// Unset the waitgroup
   258  	f.wg.Done()
   259  }
   260  
   261  func (f *nonInteractiveStep) Abort() {
   262  	f.Done()
   263  }
   264  
   265  type stripAnsiWriter struct {
   266  	Next io.Writer
   267  }
   268  
   269  func (w *stripAnsiWriter) Write(p []byte) (n int, err error) {
   270  	return w.Next.Write(p)
   271  }