internal/validation/printer.go (169 lines of code) (raw):

package validation import ( "context" "fmt" "io" "os" ) // Informer is an informer that prints the validation steps to stdout. type Printer struct { out io.Writer externalLogs LineReader color Colorer } var _ Informer = Printer{} // PrinterOpt allows to configure the Printer. type PrinterOpt func(*Printer) func NewPrinter(opts ...PrinterOpt) Printer { p := Printer{ out: os.Stdout, color: Colorer{}, } for _, opt := range opts { opt(&p) } return p } // WithNoColor disables color output. func WithNoColor() PrinterOpt { return func(p *Printer) { p.color.noColor = true } } // WithOutWriter allows to configure the output destination. func WithOutWriter(out io.Writer) PrinterOpt { return func(p *Printer) { p.out = out } } // WithExternalLogs allows to configure an external source of logs // that will get included in the output when a validation fails. // This is useful for correctly displaying the output of external // processes that are invoked during the validation. You can just capture // their output and let the printer handle its display. func WithExternalLogs(in LineReader) PrinterOpt { return func(p *Printer) { p.externalLogs = in } } // Starting prints the starting message of a validation. func (p Printer) Starting(ctx context.Context, name, message string) { p.print("* %s ", message) } // Done prints the result of a validation. // If a validation fails, it will print the error, external output if any and // the error remediation if the error is Remediable. func (p Printer) Done(ctx context.Context, name string, err error) { if err == nil { p.println("[%s]", p.color.Green("Success")) return } p.println("[%s]", p.color.Red("Failed")) errs := Unwrap(err) for _, e := range errs[:len(errs)-1] { p.printError(e) } p.printLastError(errs[len(errs)-1]) } func (p Printer) printError(err error) { p.println(" ├─ %s", p.color.Red("Error")) p.println(" │ └─ %s", err) if IsRemediable(err) { p.println(" │ └─ %s", p.color.Blue("Remediation")) p.println(" │ └─ %s", Remediation(err)) } } func (p Printer) printLastError(err error) { external := p.readExternal() if len(external) == 0 { p.println(" └─ %s", p.color.Red("Error")) p.println(" └─ %s", err) } else { p.println(" └─ %s", p.color.Red("Error")) p.println(" ├─ %s", err) if IsRemediable(err) { p.printExternalLogs(" ├─", " │", external) } else { p.printExternalLogs(" └─", " ", external) } } if IsRemediable(err) { p.println(" └─ %s", p.color.Blue("Remediation")) p.println(" └─ %s", Remediation(err)) } } func (p Printer) println(msg string, args ...any) { fmt.Fprintf(p.out, msg+"\n", args...) } func (p Printer) print(msg string, args ...any) { fmt.Fprintf(p.out, msg, args...) } func (p Printer) readExternal() []string { if p.externalLogs == nil { return nil } var lines []string for line, ok := p.externalLogs.Line(); ok; line, ok = p.externalLogs.Line() { lines = append(lines, line) } return lines } func (p Printer) printExternalLogs(prefixHeader, prefixLog string, lines []string) { if len(lines) > 0 { p.println("%s %s", prefixHeader, p.color.Yellow(p.externalLogs.Name())) for _, line := range lines[:len(lines)-1] { p.println("%s ├─ %s", prefixLog, line) } p.println("%s └─ %s", prefixLog, lines[len(lines)-1]) } } type NoOpInformer struct{} var _ Informer = NoOpInformer{} func (NoOpInformer) Starting(ctx context.Context, name, message string) {} func (NoOpInformer) Done(ctx context.Context, name string, err error) {} const ( resetC = "\033[0m" blackC = "\033[30m" redC = "\033[31m" greenC = "\033[32m" yellowC = "\033[33m" blueC = "\033[34m" purpleC = "\033[35m" cyanC = "\033[36m" greyC = "\033[37m" whiteC = "\033[97m" magentaC = "\033[95m" underlineC = "\033[4m" resetUnderline = "\033[24m" boldC = "\033[1m" resetBold = "\033[22m" ) type Colorer struct { noColor bool } func (c Colorer) wrap(m, color, reset string) string { if c.noColor { return m } return color + m + reset } func (c Colorer) color(m, color string) string { return c.wrap(m, color, resetC) } func (c Colorer) Blue(m string) string { return c.color(m, blueC) } func (c Colorer) Cyan(m string) string { return c.color(m, cyanC) } func (c Colorer) Red(m string) string { return c.color(m, redC) } func (c Colorer) Green(m string) string { return c.color(m, greenC) } func (c Colorer) Yellow(m string) string { return c.color(m, yellowC) } func (c Colorer) Black(m string) string { return c.color(m, blackC) } func (c Colorer) Grey(m string) string { return c.color(m, greyC) } func (c Colorer) Magenta(m string) string { return c.color(m, magentaC) } func (c Colorer) Underline(m string) string { return c.wrap(m, underlineC, resetUnderline) } func (c Colorer) Bold(m string) string { return c.wrap(m, boldC, resetBold) }