github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/pkg/terminal/basic.go (about) 1 package terminal 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "os" 10 "strings" 11 "text/tabwriter" 12 13 "github.com/bgentry/speakeasy" 14 "github.com/containerd/console" 15 "github.com/fatih/color" 16 "github.com/mattn/go-isatty" 17 sshterm "golang.org/x/crypto/ssh/terminal" 18 ) 19 20 // basicUI 21 type basicUI struct { 22 ctx context.Context 23 status *spinnerStatus 24 } 25 26 // Returns a UI which will write to the current processes 27 // stdout/stderr. 28 func ConsoleUI(ctx context.Context, plain bool) UI { 29 // We do both of these checks because some sneaky environments fool 30 // one or the other and we really only want the glint-based UI in 31 // truly interactive environments. 32 if plain { 33 return NonInteractiveUI(ctx) 34 } 35 36 glint := isatty.IsTerminal(os.Stdout.Fd()) && sshterm.IsTerminal(int(os.Stdout.Fd())) 37 if glint { 38 glint = false 39 if c, err := console.ConsoleFromFile(os.Stdout); err == nil { 40 if sz, err := c.Size(); err == nil { 41 glint = sz.Height > 0 && sz.Width > 0 42 } 43 } 44 } 45 46 if glint { 47 return GlintUI(ctx) 48 } else { 49 return NonInteractiveUI(ctx) 50 } 51 } 52 53 // Input implements UI 54 func (ui *basicUI) Input(input *Input) (string, error) { 55 var buf bytes.Buffer 56 57 // Write the prompt, add a space. 58 ui.Output(input.Prompt, WithStyle(input.Style), WithWriter(&buf)) 59 fmt.Fprint(color.Output, strings.TrimRight(buf.String(), "\r\n")) 60 fmt.Fprint(color.Output, " ") 61 62 // Ask for input in a go-routine so that we can ignore it. 63 errCh := make(chan error, 1) 64 lineCh := make(chan string, 1) 65 go func() { 66 var line string 67 var err error 68 if input.Secret && isatty.IsTerminal(os.Stdin.Fd()) { 69 line, err = speakeasy.Ask("") 70 } else { 71 r := bufio.NewReader(os.Stdin) 72 line, err = r.ReadString('\n') 73 } 74 if err != nil { 75 errCh <- err 76 return 77 } 78 79 lineCh <- strings.TrimRight(line, "\r\n") 80 }() 81 82 select { 83 case err := <-errCh: 84 return "", err 85 case line := <-lineCh: 86 return line, nil 87 case <-ui.ctx.Done(): 88 // Print newline so that any further output starts properly 89 fmt.Fprintln(color.Output) 90 return "", ui.ctx.Err() 91 } 92 } 93 94 // Interactive implements UI 95 func (ui *basicUI) Interactive() bool { 96 return isatty.IsTerminal(os.Stdin.Fd()) 97 } 98 99 // Output implements UI 100 func (ui *basicUI) Output(msg string, raw ...interface{}) { 101 msg, style, w := Interpret(msg, raw...) 102 103 switch style { 104 case HeaderStyle: 105 msg = colorHeader.Sprintf("\n==> %s", msg) 106 case ErrorStyle: 107 msg = colorError.Sprint(msg) 108 case ErrorBoldStyle: 109 msg = colorErrorBold.Sprint(msg) 110 case WarningStyle: 111 msg = colorWarning.Sprint(msg) 112 case WarningBoldStyle: 113 msg = colorWarningBold.Sprint(msg) 114 case SuccessStyle: 115 msg = colorSuccess.Sprint(msg) 116 case SuccessBoldStyle: 117 msg = colorSuccessBold.Sprint(msg) 118 case InfoStyle: 119 lines := strings.Split(msg, "\n") 120 for i, line := range lines { 121 lines[i] = colorInfo.Sprintf(" %s", line) 122 } 123 124 msg = strings.Join(lines, "\n") 125 } 126 127 st := ui.status 128 if st != nil { 129 if st.Pause() { 130 defer st.Start() 131 } 132 } 133 134 // Write it 135 fmt.Fprintln(w, msg) 136 } 137 138 // NamedValues implements UI 139 func (ui *basicUI) NamedValues(rows []NamedValue, opts ...Option) { 140 cfg := &config{Writer: color.Output} 141 for _, opt := range opts { 142 opt(cfg) 143 } 144 145 var buf bytes.Buffer 146 tr := tabwriter.NewWriter(&buf, 1, 8, 0, ' ', tabwriter.AlignRight) 147 for _, row := range rows { 148 switch v := row.Value.(type) { 149 case int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64: 150 fmt.Fprintf(tr, " %s: \t%d\n", row.Name, row.Value) 151 case float32, float64: 152 fmt.Fprintf(tr, " %s: \t%f\n", row.Name, row.Value) 153 case bool: 154 fmt.Fprintf(tr, " %s: \t%v\n", row.Name, row.Value) 155 case string: 156 if v == "" { 157 continue 158 } 159 fmt.Fprintf(tr, " %s: \t%s\n", row.Name, row.Value) 160 default: 161 fmt.Fprintf(tr, " %s: \t%s\n", row.Name, row.Value) 162 } 163 } 164 165 tr.Flush() 166 colorInfo.Fprintln(cfg.Writer, buf.String()) 167 } 168 169 // OutputWriters implements UI 170 func (ui *basicUI) OutputWriters() (io.Writer, io.Writer, error) { 171 return os.Stdout, os.Stderr, nil 172 } 173 174 // Status implements UI 175 func (ui *basicUI) Status() Status { 176 if ui.status == nil { 177 ui.status = newSpinnerStatus(ui.ctx) 178 } 179 180 return ui.status 181 } 182 183 func (ui *basicUI) StepGroup() StepGroup { 184 ctx, cancel := context.WithCancel(ui.ctx) 185 display := NewDisplay(ctx, color.Output) 186 187 return &fancyStepGroup{ 188 ctx: ctx, 189 cancel: cancel, 190 display: display, 191 done: make(chan struct{}), 192 } 193 }