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 }