101 lines
2.6 KiB
Go
101 lines
2.6 KiB
Go
package cmd
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// hinted wraps an error with an actionable hint shown on a separate line.
|
|
type hinted struct {
|
|
err error
|
|
hint string
|
|
}
|
|
|
|
func (h hinted) Error() string { return h.err.Error() }
|
|
func (h hinted) Unwrap() error { return h.err }
|
|
|
|
func withHint(err error, hint string) error {
|
|
return hinted{err: err, hint: hint}
|
|
}
|
|
|
|
func stderrIsTerminal() bool {
|
|
return term.IsTerminal(int(os.Stderr.Fd()))
|
|
}
|
|
|
|
func stdoutIsTerminal() bool {
|
|
return term.IsTerminal(int(os.Stdout.Fd()))
|
|
}
|
|
|
|
// keyword returns a right-aligned, colored keyword (color only on TTY).
|
|
//
|
|
// FAIL red (stderr)
|
|
// hint dim (stderr)
|
|
// WARN yellow (stderr)
|
|
// info blue (stderr)
|
|
// ok green (stderr)
|
|
// ? cyan (stdout)
|
|
// > dim (stdout)
|
|
func keyword(code, word string, tty bool) string {
|
|
padded := fmt.Sprintf("%4s", word)
|
|
if tty {
|
|
return fmt.Sprintf("\033[%sm%s\033[0m", code, padded)
|
|
}
|
|
return padded
|
|
}
|
|
|
|
func printError(err error) {
|
|
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("31", "FAIL", stderrIsTerminal()), err)
|
|
}
|
|
|
|
func printHint(format string, args ...any) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("2", "hint", stderrIsTerminal()), msg)
|
|
}
|
|
|
|
func warnf(format string, args ...any) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("33", "WARN", stderrIsTerminal()), msg)
|
|
}
|
|
|
|
func infof(format string, args ...any) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("34", "info", stderrIsTerminal()), msg)
|
|
}
|
|
|
|
func okf(format string, args ...any) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
fmt.Fprintf(os.Stderr, "%s %s\n", keyword("32", "ok", stderrIsTerminal()), msg)
|
|
}
|
|
|
|
func promptf(format string, args ...any) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
fmt.Fprintf(os.Stdout, "%s %s\n", keyword("36", "???", stdoutIsTerminal()), msg)
|
|
}
|
|
|
|
func progressf(format string, args ...any) {
|
|
msg := fmt.Sprintf(format, args...)
|
|
fmt.Fprintf(os.Stdout, "%s %s\n", keyword("2", ">", stdoutIsTerminal()), msg)
|
|
}
|
|
|
|
func scanln(dest *string) error {
|
|
fmt.Fprintf(os.Stdout, "%s ", keyword("2", "==>", stdoutIsTerminal()))
|
|
_, err := fmt.Scanln(dest)
|
|
return err
|
|
}
|
|
|
|
// printErrorWithHints prints the error and any hints found in the error chain.
|
|
func printErrorWithHints(err error) {
|
|
printError(err)
|
|
var h hinted
|
|
if errors.As(err, &h) {
|
|
printHint("%s", h.hint)
|
|
}
|
|
var nf errNotFound
|
|
if errors.As(err, &nf) && len(nf.suggestions) > 0 {
|
|
printHint("did you mean '%s'?", strings.Join(nf.suggestions, "', '"))
|
|
}
|
|
}
|