From 3e08c0960c7313cddf50985b9f50e1630d6acba8 Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Fri, 30 Aug 2024 12:36:58 +0300 Subject: [PATCH] fix: add better colors control --- cmd/root.go | 11 ++- internal/lefthook/lefthook.go | 7 +- internal/lefthook/run.go | 15 +--- internal/lefthook/runner/exec/execute_unix.go | 6 ++ .../lefthook/runner/exec/execute_windows.go | 7 ++ internal/log/log.go | 88 ++++++++++++++++--- 6 files changed, 109 insertions(+), 25 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 2190990a..7fcb1953 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -29,6 +29,11 @@ func newRootCmd() *cobra.Command { rootCmd.PersistentFlags().BoolVarP( &options.Verbose, "verbose", "v", false, "verbose output", ) + + rootCmd.PersistentFlags().StringVar( + &options.Colors, "colors", "auto", "'auto', 'on', or 'off'", + ) + rootCmd.PersistentFlags().BoolVar( &options.NoColors, "no-colors", false, "disable colored output", ) @@ -42,7 +47,11 @@ func newRootCmd() *cobra.Command { &options.Aggressive, "aggressive", "a", false, "use --force flag instead", ) - err := rootCmd.Flags().MarkDeprecated("aggressive", "use command-specific --force option") + err := rootCmd.PersistentFlags().MarkDeprecated("no-colors", "use --colors") + if err != nil { + log.Warn("Unexpected error:", err) + } + err = rootCmd.Flags().MarkDeprecated("aggressive", "use command-specific --force option") if err != nil { log.Warn("Unexpected error:", err) } diff --git a/internal/lefthook/lefthook.go b/internal/lefthook/lefthook.go index b2c8e00c..c0f07e2d 100644 --- a/internal/lefthook/lefthook.go +++ b/internal/lefthook/lefthook.go @@ -26,6 +26,7 @@ var lefthookContentRegexp = regexp.MustCompile("LEFTHOOK") type Options struct { Fs afero.Fs Verbose, NoColors bool + Colors string // DEPRECATED. Will be removed in 1.3.0. Force, Aggressive bool @@ -49,7 +50,11 @@ func initialize(opts *Options) (*Lefthook, error) { log.SetLevel(log.DebugLevel) } - log.SetColors(!opts.NoColors) + // DEPRECATED: Will be removed with a --no-colors option + if opts.NoColors && opts.Colors == "auto" { + opts.Colors = "off" + } + log.SetColors(opts.Colors) repo, err := git.NewRepository(opts.Fs, git.NewExecutor(system.Cmd)) if err != nil { diff --git a/internal/lefthook/run.go b/internal/lefthook/run.go index 1906574d..0c38c5c7 100644 --- a/internal/lefthook/run.go +++ b/internal/lefthook/run.go @@ -14,7 +14,6 @@ import ( "github.com/evilmartians/lefthook/internal/config" "github.com/evilmartians/lefthook/internal/lefthook/runner" "github.com/evilmartians/lefthook/internal/log" - "github.com/evilmartians/lefthook/internal/version" ) const ( @@ -91,10 +90,7 @@ func (l *Lefthook) Run(hookName string, args RunArgs, gitArgs []string) error { // } if logSettings.LogMeta() { - log.Box( - log.Cyan("🥊 lefthook ")+log.Gray(fmt.Sprintf("v%s", version.Version(false))), - log.Gray("hook: ")+log.Bold(hookName), - ) + log.LogMeta(hookName) } if !args.NoAutoInstall { @@ -231,7 +227,7 @@ func printSummary( continue } - log.Infof("✔️ %s\n", log.Green(result.Name)) + log.Success(result.Name) } } @@ -241,12 +237,7 @@ func printSummary( continue } - failText := result.Text() - if len(failText) != 0 { - failText = fmt.Sprintf(": %s", failText) - } - - log.Infof("🥊 %s%s\n", log.Red(result.Name), log.Red(failText)) + log.Failure(result.Name, result.Text()) } } } diff --git a/internal/lefthook/runner/exec/execute_unix.go b/internal/lefthook/runner/exec/execute_unix.go index ccaa9a9b..45efaab0 100644 --- a/internal/lefthook/runner/exec/execute_unix.go +++ b/internal/lefthook/runner/exec/execute_unix.go @@ -47,6 +47,12 @@ func (e CommandExecutor) Execute(ctx context.Context, opts Options, in io.Reader fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)), ) } + switch log.Colors() { + case log.ColorOn: + envs = append(envs, "CLICOLOR_FORCE=true") + case log.ColorOff: + envs = append(envs, "NO_COLOR=true") + } args := &executeArgs{ in: in, diff --git a/internal/lefthook/runner/exec/execute_windows.go b/internal/lefthook/runner/exec/execute_windows.go index 47d6bc1c..47d72757 100644 --- a/internal/lefthook/runner/exec/execute_windows.go +++ b/internal/lefthook/runner/exec/execute_windows.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/evilmartians/lefthook/internal/log" + "github.com/mattn/go-isatty" "github.com/mattn/go-tty" ) @@ -42,6 +43,12 @@ func (e CommandExecutor) Execute(ctx context.Context, opts Options, in io.Reader fmt.Sprintf("%s=%s", strings.ToUpper(name), os.ExpandEnv(value)), ) } + switch log.Colors() { + case log.ColorOn: + envs = append(envs, "CLICOLOR_FORCE=true") + case log.ColorOff: + envs = append(envs, "NO_COLOR=true") + } args := &executeArgs{ in: in, diff --git a/internal/log/log.go b/internal/log/log.go index 4461226d..b0843cc4 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -11,6 +11,8 @@ import ( "github.com/briandowns/spinner" "github.com/charmbracelet/lipgloss" + + "github.com/evilmartians/lefthook/internal/version" ) var ( @@ -38,6 +40,10 @@ const ( spinnerCharSet = 14 spinnerRefreshRate = 100 * time.Millisecond spinnerText = " waiting" + + ColorAuto = iota + ColorOn + ColorOff ) type StyleLogger struct { @@ -48,7 +54,7 @@ type Logger struct { level Level out io.Writer mu sync.Mutex - colors bool + colors int names []string spinner *spinner.Spinner } @@ -57,7 +63,7 @@ func New() *Logger { return &Logger{ level: InfoLevel, out: os.Stdout, - colors: true, + colors: ColorAuto, spinner: spinner.New( spinner.CharSets[spinnerCharSet], spinnerRefreshRate, @@ -66,6 +72,14 @@ func New() *Logger { } } +func Colors() int { + return std.colors +} + +func Colorized() bool { + return std.colors == ColorAuto || std.colors == ColorOn +} + func StartSpinner() { std.spinner.Start() } @@ -159,29 +173,49 @@ func SetLevel(level Level) { } func SetColors(colors interface{}) { + if colors == nil { + return + } + switch typedColors := colors.(type) { - case bool: - std.colors = typedColors - if !std.colors { + case string: + switch typedColors { + case "on": + std.colors = ColorOn + case "off": + std.colors = ColorOff setColor(lipgloss.NoColor{}, &ColorRed) setColor(lipgloss.NoColor{}, &ColorGreen) setColor(lipgloss.NoColor{}, &ColorYellow) setColor(lipgloss.NoColor{}, &ColorCyan) setColor(lipgloss.NoColor{}, &GolorGray) setColor(lipgloss.NoColor{}, &colorBorder) + default: + std.colors = ColorAuto } - return + case bool: + if typedColors { + std.colors = ColorOn + return + } + + std.colors = ColorOff + setColor(lipgloss.NoColor{}, &ColorRed) + setColor(lipgloss.NoColor{}, &ColorGreen) + setColor(lipgloss.NoColor{}, &ColorYellow) + setColor(lipgloss.NoColor{}, &ColorCyan) + setColor(lipgloss.NoColor{}, &GolorGray) + setColor(lipgloss.NoColor{}, &colorBorder) case map[string]interface{}: - std.colors = true + std.colors = ColorOn setColor(typedColors["red"], &ColorRed) setColor(typedColors["green"], &ColorGreen) setColor(typedColors["yellow"], &ColorYellow) setColor(typedColors["cyan"], &ColorCyan) setColor(typedColors["gray"], &GolorGray) setColor(typedColors["gray"], &colorBorder) - return default: - std.colors = true + std.colors = ColorAuto } } @@ -227,14 +261,46 @@ func Gray(s string) string { } func Bold(s string) string { - if !std.colors { + if !Colorized() { return lipgloss.NewStyle().Render(s) } return lipgloss.NewStyle().Bold(true).Render(s) } -func Box(left, right string) { +func LogMeta(hookName string) { + name := "🥊 lefthook " + if !Colorized() { + name = "lefthook " + } + + box( + Cyan(name)+Gray(fmt.Sprintf("v%s", version.Version(false))), + Gray("hook: ")+Bold(hookName), + ) +} + +func Success(name string) { + format := "✔️ %s\n" + if !Colorized() { + format = "✓ %s\n" + } + Infof(format, Green(name)) +} + +func Failure(name, failText string) { + if len(failText) != 0 { + failText = fmt.Sprintf(": %s", failText) + } + + format := "🥊 %s%s\n" + if !Colorized() { + format = "✗ %s%s\n" + } + Infof(format, Red(name), Red(failText)) +} + +func box(left, right string) { Info( lipgloss.JoinHorizontal( lipgloss.Top,