Files
summercms/modules/bonfire/output.go
Jakub Zych 5e50b166ef refactor(10.2-01): nest framework packages under modules
- Move remaining beach packages and embedded admin assets\n- Rewrite framework, example, build, and gate paths
2026-09-28 02:21:02 +02:00

171 lines
3.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package bonfire
import (
"fmt"
"io"
"os"
"golang.org/x/term"
)
const (
ansiReset = "\x1b[0m"
ansiRed = "\x1b[31m"
ansiGreen = "\x1b[32m"
ansiYellow = "\x1b[33m"
ansiCyan = "\x1b[36m"
ansiDim = "\x1b[90m"
symCheck = "✓"
symCross = "✗"
symWarn = "⚠"
symInfo = "ℹ"
symArrow = "→"
symPrompt = "?"
)
// Output is the injected console used by commands. Tests supply streams.
type Output interface {
io.Writer
Println(a ...any)
Printf(format string, a ...any)
Info(message string)
Success(message string)
Error(message string)
Warning(message string)
Table(headers []string, rows [][]string)
Spinner(message string, fn func() error) error
Progress(total int, fn func(Progress) error) error
Ask(question, def string) (string, error)
Confirm(question string, def bool) (bool, error)
Choice(question string, choices []string, def int) (string, error)
Secret(question string) (string, error)
}
// Progress is a single progress bar advanced from a command.
type Progress interface {
Advance(step int)
}
type console struct {
in io.Reader
out io.Writer
err io.Writer
lookup func(string) string
tty bool
interactive bool
color bool
}
// NewOutput builds Output from injected streams and the process color policy.
func NewOutput(in io.Reader, out, errW io.Writer) Output {
return newConsole(in, out, errW, os.Getenv, nil, nil)
}
func newConsole(in io.Reader, out, errW io.Writer, lookup func(string) string, tty, interactive *bool) *console {
if in == nil {
in = os.Stdin
}
if out == nil {
out = os.Stdout
}
if errW == nil {
errW = out
}
if lookup == nil {
lookup = os.Getenv
}
c := &console{in: in, out: out, err: errW, lookup: lookup}
if tty != nil {
c.tty = *tty
} else {
c.tty = isTerminal(out)
}
if interactive != nil {
c.interactive = *interactive
} else {
c.interactive = isTerminal(in) && c.tty
}
c.color = supportsColor(lookup, c.tty)
return c
}
func supportsColor(lookup func(string) string, tty bool) bool {
if lookup == nil {
lookup = os.Getenv
}
if lookup("NO_COLOR") != "" || lookup("TERM") == "dumb" {
return false
}
if lookup("FORCE_COLOR") != "" {
return true
}
return tty
}
func isTerminal(rw any) bool {
type fd interface{ Fd() uintptr }
f, ok := rw.(fd)
if !ok {
return false
}
return term.IsTerminal(int(f.Fd()))
}
func (c *console) Write(p []byte) (int, error) {
if c.out == nil {
return 0, io.ErrClosedPipe
}
return c.out.Write(p)
}
func (c *console) Println(a ...any) {
if c.out == nil {
return
}
fmt.Fprintln(c.out, a...)
}
func (c *console) Printf(format string, a ...any) {
if c.out == nil {
return
}
fmt.Fprintf(c.out, format, a...)
}
func (c *console) Info(message string) {
c.Println(c.style(ansiCyan, symInfo) + " " + message)
}
func (c *console) Success(message string) {
c.Println(c.style(ansiGreen, symCheck) + " " + message)
}
func (c *console) Error(message string) {
w := c.err
if w == nil {
w = c.out
}
if w == nil {
return
}
fmt.Fprintln(w, c.style(ansiRed, symCross)+" "+message)
}
func (c *console) Warning(message string) {
c.Println(c.style(ansiYellow, symWarn) + " " + message)
}
func (c *console) style(code, s string) string {
if !c.color {
return s
}
return code + s + ansiReset
}
func (c *console) dim(s string) string {
return c.style(ansiDim, s)
}
func boolPtr(v bool) *bool { return &v }