github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/pkg/terminal/glint.go (about) 1 package terminal 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "os" 9 "strings" 10 "text/tabwriter" 11 "time" 12 13 "github.com/mitchellh/go-glint" 14 "github.com/olekukonko/tablewriter" 15 ) 16 17 type glintUI struct { 18 d *glint.Document 19 } 20 21 func GlintUI(ctx context.Context) UI { 22 result := &glintUI{ 23 d: glint.New(), 24 } 25 26 go result.d.Render(ctx) 27 28 return result 29 } 30 31 func (ui *glintUI) Close() error { 32 return ui.d.Close() 33 } 34 35 func (ui *glintUI) Input(input *Input) (string, error) { 36 return "", ErrNonInteractive 37 } 38 39 // Interactive implements UI 40 func (ui *glintUI) Interactive() bool { 41 // TODO(mitchellh): We can make this interactive later but Glint itself 42 // doesn't support input yet. We can pause the document, do some input, 43 // then resume potentially. 44 return false 45 } 46 47 // Output implements UI 48 func (ui *glintUI) Output(msg string, raw ...interface{}) { 49 msg, style, _ := Interpret(msg, raw...) 50 51 var cs []glint.StyleOption 52 switch style { 53 case HeaderStyle: 54 cs = append(cs, glint.Bold()) 55 msg = "\n» " + msg 56 case ErrorStyle, ErrorBoldStyle: 57 cs = append(cs, glint.Color("lightRed")) 58 if style == ErrorBoldStyle { 59 cs = append(cs, glint.Bold()) 60 } 61 62 ui.d.Append(glint.Finalize( 63 glint.Style( 64 glint.Text("✗ "+msg), 65 cs..., 66 ), 67 )) 68 69 return 70 71 case WarningStyle, WarningBoldStyle: 72 cs = append(cs, glint.Color("lightYellow")) 73 if style == WarningBoldStyle { 74 cs = append(cs, glint.Bold()) 75 } 76 77 case SuccessStyle, SuccessBoldStyle: 78 cs = append(cs, glint.Color("lightGreen")) 79 if style == SuccessBoldStyle { 80 cs = append(cs, glint.Bold()) 81 } 82 83 msg = colorSuccess.Sprint(msg) 84 85 case InfoStyle: 86 lines := strings.Split(msg, "\n") 87 for i, line := range lines { 88 lines[i] = colorInfo.Sprintf(" %s", line) 89 } 90 91 msg = strings.Join(lines, "\n") 92 } 93 94 ui.d.Append(glint.Finalize( 95 glint.Style( 96 glint.Text(msg), 97 cs..., 98 ), 99 )) 100 101 time.Sleep(ui.minimumLag()) 102 } 103 104 // NamedValues implements UI 105 func (ui *glintUI) NamedValues(rows []NamedValue, opts ...Option) { 106 cfg := &config{} 107 for _, opt := range opts { 108 opt(cfg) 109 } 110 111 var buf bytes.Buffer 112 tr := tabwriter.NewWriter(&buf, 1, 8, 0, ' ', tabwriter.AlignRight) 113 for _, row := range rows { 114 switch v := row.Value.(type) { 115 case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64: 116 fmt.Fprintf(tr, " %s: \t%d\n", row.Name, row.Value) 117 case float32, float64: 118 fmt.Fprintf(tr, " %s: \t%f\n", row.Name, row.Value) 119 case bool: 120 fmt.Fprintf(tr, " %s: \t%v\n", row.Name, row.Value) 121 case string: 122 if v == "" { 123 continue 124 } 125 fmt.Fprintf(tr, " %s: \t%s\n", row.Name, row.Value) 126 default: 127 fmt.Fprintf(tr, " %s: \t%s\n", row.Name, row.Value) 128 } 129 } 130 tr.Flush() 131 132 // We want to trim the trailing newline 133 text := buf.String() 134 if len(text) > 0 && text[len(text)-1] == '\n' { 135 text = text[:len(text)-1] 136 } 137 138 ui.d.Append(glint.Finalize(glint.Text(text))) 139 } 140 141 // OutputWriters implements UI 142 func (ui *glintUI) OutputWriters() (io.Writer, io.Writer, error) { 143 return os.Stdout, os.Stderr, nil 144 } 145 146 // Status implements UI 147 func (ui *glintUI) Status() Status { 148 st := newGlintStatus() 149 ui.d.Append(st) 150 return st 151 } 152 153 func (ui *glintUI) StepGroup() StepGroup { 154 ctx, cancel := context.WithCancel(context.Background()) 155 sg := &glintStepGroup{ctx: ctx, cancel: cancel} 156 ui.d.Append(sg) 157 return sg 158 } 159 160 // Table implements UI 161 func (ui *glintUI) Table(tbl *Table, opts ...Option) { 162 // Build our config and set our options 163 cfg := &config{} 164 for _, opt := range opts { 165 opt(cfg) 166 } 167 168 var buf bytes.Buffer 169 table := tablewriter.NewWriter(&buf) 170 table.SetHeader(tbl.Headers) 171 table.SetBorder(false) 172 table.SetAutoWrapText(false) 173 174 if cfg.Style == "Simple" { 175 // Format the table without borders, simple output 176 177 table.SetAutoFormatHeaders(true) 178 table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) 179 table.SetAlignment(tablewriter.ALIGN_LEFT) 180 table.SetCenterSeparator("") 181 table.SetColumnSeparator("") 182 table.SetRowSeparator("") 183 table.SetHeaderLine(false) 184 table.SetTablePadding("\t") // pad with tabs 185 table.SetNoWhiteSpace(true) 186 } 187 188 for _, row := range tbl.Rows { 189 colors := make([]tablewriter.Colors, len(row)) 190 entries := make([]string, len(row)) 191 192 for i, ent := range row { 193 entries[i] = ent.Value 194 195 color, ok := colorMapping[ent.Color] 196 if ok { 197 colors[i] = tablewriter.Colors{color} 198 } 199 } 200 201 table.Rich(entries, colors) 202 } 203 204 table.Render() 205 206 ui.d.Append(glint.Finalize(glint.Text(buf.String()))) 207 } 208 209 func (ui *glintUI) minimumLag() time.Duration { 210 return time.Millisecond * 100 211 }