From 57f2bb1eb40013e81fc30f08b6ef1726a9ba2fec Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Sun, 12 Apr 2020 22:30:10 -0700 Subject: [PATCH] Create printer pkg, fix color force enable/disable Signed-off-by: Ahmet Alp Balkan --- cmd/kubectx/delete.go | 5 ++- cmd/kubectx/env.go | 27 ------------ cmd/kubectx/flags.go | 4 -- cmd/kubectx/fzf.go | 9 ++-- cmd/kubectx/kubeconfig_test.go | 16 +++++++ cmd/kubectx/list.go | 5 ++- cmd/kubectx/main.go | 22 ++++------ cmd/kubectx/rename.go | 5 ++- cmd/kubectx/switch.go | 3 +- internal/env/constants.go | 19 +++++++++ internal/printer/color.go | 20 +++++++++ .../printer/color_test.go | 20 +++++---- internal/printer/printer.go | 42 +++++++++++++++++++ 13 files changed, 135 insertions(+), 62 deletions(-) create mode 100644 internal/env/constants.go create mode 100644 internal/printer/color.go rename cmd/kubectx/env_test.go => internal/printer/color_test.go (62%) create mode 100644 internal/printer/printer.go diff --git a/cmd/kubectx/delete.go b/cmd/kubectx/delete.go index 85f35a0..fe28afd 100644 --- a/cmd/kubectx/delete.go +++ b/cmd/kubectx/delete.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/ahmetb/kubectx/internal/kubeconfig" + "github.com/ahmetb/kubectx/internal/printer" ) // DeleteOp indicates intention to delete contexts. @@ -23,10 +24,10 @@ func (op DeleteOp) Run(_, stderr io.Writer) error { } if wasActiveContext { // TODO we don't always run as kubectx (sometimes "kubectl ctx") - printWarning(stderr, "You deleted the current context. use \"kubectx\" to select a different one.") + printer.Warning(stderr, "You deleted the current context. use \"kubectx\" to select a different one.") } - printSuccess(stderr, "deleted context %q", deletedName) + printer.Success(stderr, "deleted context %q", deletedName) } return nil } diff --git a/cmd/kubectx/env.go b/cmd/kubectx/env.go index 98319b0..c9ecbf5 100644 --- a/cmd/kubectx/env.go +++ b/cmd/kubectx/env.go @@ -1,29 +1,2 @@ package main -import "os" - -const ( - // EnvFZFIgnore describes the environment variable to set to disable - // interactive context selection when fzf is installed. - EnvFZFIgnore = "KUBECTX_IGNORE_FZF" - - // EnvForceColor describes the environment variable to disable color usage - // when printing current context in a list. - EnvNoColor = `NO_COLOR` - - // EnvForceColor describes the "internal" environment variable to force - // color usage to show current context in a list. - EnvForceColor = `_KUBECTX_FORCE_COLOR` - - // EnvDebug describes the internal environment variable for more verbose logging. - EnvDebug = `DEBUG` -) - -func useColors() bool { - if os.Getenv(EnvForceColor) != "" { - return true - } else if os.Getenv(EnvNoColor) != "" { - return false - } - return true -} diff --git a/cmd/kubectx/flags.go b/cmd/kubectx/flags.go index 87cd7c7..6b73e53 100644 --- a/cmd/kubectx/flags.go +++ b/cmd/kubectx/flags.go @@ -7,10 +7,6 @@ import ( "strings" ) -type Op interface { - Run(stdout, stderr io.Writer) error -} - // UnsupportedOp indicates an unsupported flag. type UnsupportedOp struct{ Err error } diff --git a/cmd/kubectx/fzf.go b/cmd/kubectx/fzf.go index 4d466f8..2db1f8f 100644 --- a/cmd/kubectx/fzf.go +++ b/cmd/kubectx/fzf.go @@ -11,6 +11,9 @@ import ( "github.com/pkg/errors" "github.com/mattn/go-isatty" + + "github.com/ahmetb/kubectx/internal/env" + "github.com/ahmetb/kubectx/internal/printer" ) type InteractiveSwitchOp struct { @@ -26,7 +29,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { cmd.Env = append(os.Environ(), fmt.Sprintf("FZF_DEFAULT_COMMAND=%s", op.SelfCmd), - fmt.Sprintf("%s=1", EnvForceColor)) + fmt.Sprintf("%s=1", env.EnvForceColor)) if err := cmd.Run(); err != nil { if _, ok := err.(*exec.ExitError); !ok { return err @@ -40,7 +43,7 @@ func (op InteractiveSwitchOp) Run(_, stderr io.Writer) error { if err != nil { return errors.Wrap(err, "failed to switch context") } - printSuccess(stderr, "Switched to context %q.", name) + printer.Success(stderr, "Switched to context %q.", name) return nil } @@ -60,6 +63,6 @@ func fzfInstalled() bool { // isInteractiveMode determines if we can do choosing with fzf. func isInteractiveMode(stdout *os.File) bool { - v := os.Getenv(EnvFZFIgnore) + v := os.Getenv(env.EnvFZFIgnore) return v == "" && isTerminal(stdout) && fzfInstalled() } diff --git a/cmd/kubectx/kubeconfig_test.go b/cmd/kubectx/kubeconfig_test.go index f6316c5..1630b32 100644 --- a/cmd/kubectx/kubeconfig_test.go +++ b/cmd/kubectx/kubeconfig_test.go @@ -8,6 +8,22 @@ import ( "testing" ) + +func withTestVar(key, value string) func() { + // TODO(ahmetb) this method is currently duplicated + // consider extracting to internal/testutil or something + + orig, ok := os.LookupEnv(key) + os.Setenv(key, value) + return func() { + if ok { + os.Setenv(key, orig) + } else { + os.Unsetenv(key) + } + } +} + func Test_homeDir(t *testing.T) { type env struct{ k, v string } cases := []struct { diff --git a/cmd/kubectx/list.go b/cmd/kubectx/list.go index e3d28b7..e1a9530 100644 --- a/cmd/kubectx/list.go +++ b/cmd/kubectx/list.go @@ -9,6 +9,7 @@ import ( "github.com/pkg/errors" "github.com/ahmetb/kubectx/internal/kubeconfig" + "github.com/ahmetb/kubectx/internal/printer" ) // ListOp describes listing contexts. @@ -28,9 +29,9 @@ func (_ ListOp) Run(stdout, _ io.Writer) error { // TODO support KUBECTX_CURRENT_BGCOLOR currentColor := color.New(color.FgGreen, color.Bold) - if useColors() { + if v := printer.UseColors(); v != nil && *v { currentColor.EnableColor() - } else { + } else if v != nil && !*v { currentColor.DisableColor() } diff --git a/cmd/kubectx/main.go b/cmd/kubectx/main.go index c9657ec..7564434 100644 --- a/cmd/kubectx/main.go +++ b/cmd/kubectx/main.go @@ -5,15 +5,20 @@ import ( "io" "os" - "github.com/fatih/color" + "github.com/ahmetb/kubectx/internal/env" + "github.com/ahmetb/kubectx/internal/printer" ) +type Op interface { + Run(stdout, stderr io.Writer) error +} + func main() { op := parseArgs(os.Args[1:]) if err := op.Run(os.Stdout, os.Stderr); err != nil { - printError(os.Stderr, err.Error()) + printer.Error(os.Stderr, err.Error()) - if _, ok := os.LookupEnv(EnvDebug); ok { + if _, ok := os.LookupEnv(env.EnvDebug); ok { // print stack trace in verbose mode fmt.Fprintf(os.Stderr, "[DEBUG] error: %+v\n", err) } @@ -21,14 +26,3 @@ func main() { } } -func printError(w io.Writer, format string, args ...interface{}) { - fmt.Fprintf(w, color.RedString("error: ")+format+"\n", args...) -} - -func printWarning(w io.Writer, format string, args ...interface{}) { - fmt.Fprintf(w, color.YellowString("warning: ")+format+"\n", args...) -} - -func printSuccess(w io.Writer, format string, args ...interface{}) { - fmt.Fprintf(w, color.GreenString(fmt.Sprintf(format+"\n", args...))) -} diff --git a/cmd/kubectx/rename.go b/cmd/kubectx/rename.go index b564e98..7bd54ef 100644 --- a/cmd/kubectx/rename.go +++ b/cmd/kubectx/rename.go @@ -7,6 +7,7 @@ import ( "github.com/pkg/errors" "github.com/ahmetb/kubectx/internal/kubeconfig" + "github.com/ahmetb/kubectx/internal/printer" ) // RenameOp indicates intention to rename contexts. @@ -49,7 +50,7 @@ func (op RenameOp) Run(_, stderr io.Writer) error { } if kc.ContextExists(op.New) { - printWarning(stderr, "context %q exists, overwriting it.", op.New) + printer.Warning(stderr, "context %q exists, overwriting it.", op.New) if err := kc.DeleteContextEntry(op.New); err != nil { return errors.Wrap(err, "failed to delete new context to overwrite it") } @@ -66,6 +67,6 @@ func (op RenameOp) Run(_, stderr io.Writer) error { if err := kc.Save(); err != nil { return errors.Wrap(err, "failed to save modified kubeconfig") } - printSuccess(stderr, "Context %q renamed to %q.", op.Old, op.New) + printer.Success(stderr, "Context %q renamed to %q.", op.Old, op.New) return nil } diff --git a/cmd/kubectx/switch.go b/cmd/kubectx/switch.go index f5087b8..b6e0981 100644 --- a/cmd/kubectx/switch.go +++ b/cmd/kubectx/switch.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/ahmetb/kubectx/internal/kubeconfig" + "github.com/ahmetb/kubectx/internal/printer" ) // SwitchOp indicates intention to switch contexts. @@ -24,7 +25,7 @@ func (op SwitchOp) Run(_, stderr io.Writer) error { if err != nil { return errors.Wrap(err, "failed to switch context") } - printSuccess(stderr, "Switched to context %q.", newCtx) + printer.Success(stderr, "Switched to context %q.", newCtx) return nil } diff --git a/internal/env/constants.go b/internal/env/constants.go new file mode 100644 index 0000000..77fa8ba --- /dev/null +++ b/internal/env/constants.go @@ -0,0 +1,19 @@ +package env + +const ( + // EnvFZFIgnore describes the environment variable to set to disable + // interactive context selection when fzf is installed. + EnvFZFIgnore = "KUBECTX_IGNORE_FZF" + + // EnvForceColor describes the environment variable to disable color usage + // when printing current context in a list. + EnvNoColor = `NO_COLOR` + + // EnvForceColor describes the "internal" environment variable to force + // color usage to show current context in a list. + EnvForceColor = `_KUBECTX_FORCE_COLOR` + + // EnvDebug describes the internal environment variable for more verbose logging. + EnvDebug = `DEBUG` +) + diff --git a/internal/printer/color.go b/internal/printer/color.go new file mode 100644 index 0000000..42a957b --- /dev/null +++ b/internal/printer/color.go @@ -0,0 +1,20 @@ +package printer + +import ( + "os" + + "github.com/ahmetb/kubectx/internal/env" +) + +// UseColors returns true if colors are force-enabled, +// false if colors are disabled, or nil for default behavior +// which is determined based on factors like if stdout is tty. +func UseColors() *bool { + tr, fa := true, false + if os.Getenv(env.EnvForceColor) != "" { + return &tr + } else if os.Getenv(env.EnvNoColor) != "" { + return &fa + } + return nil +} diff --git a/cmd/kubectx/env_test.go b/internal/printer/color_test.go similarity index 62% rename from cmd/kubectx/env_test.go rename to internal/printer/color_test.go index 5bac28e..9db2518 100644 --- a/cmd/kubectx/env_test.go +++ b/internal/printer/color_test.go @@ -1,8 +1,10 @@ -package main +package printer import ( "os" "testing" + + "github.com/google/go-cmp/cmp" ) func withTestVar(key, value string) func() { @@ -17,20 +19,24 @@ func withTestVar(key, value string) func() { } } +var ( + tr, fa = true, false +) + func Test_useColors_forceColors(t *testing.T) { defer withTestVar("_KUBECTX_FORCE_COLOR", "1")() defer withTestVar("NO_COLOR", "1")() - if !useColors() { - t.Fatal("expected useColors() = true") + if v := UseColors(); !cmp.Equal(v, &tr) { + t.Fatalf("expected UseColors() = true; got = %v", v) } } func Test_useColors_disableColors(t *testing.T) { defer withTestVar("NO_COLOR", "1")() - if useColors() { - t.Fatal("expected useColors() = false") + if v := UseColors(); !cmp.Equal(v, &fa) { + t.Fatalf("expected UseColors() = false; got = %v", v) } } @@ -38,7 +44,7 @@ func Test_useColors_default(t *testing.T) { defer withTestVar("NO_COLOR", "")() defer withTestVar("_KUBECTX_FORCE_COLOR", "")() - if !useColors() { - t.Fatal("expected useColors() = true") + if v := UseColors(); v != nil { + t.Fatalf("expected UseColors() = nil; got=%v", *v) } } diff --git a/internal/printer/printer.go b/internal/printer/printer.go new file mode 100644 index 0000000..7fcc7c9 --- /dev/null +++ b/internal/printer/printer.go @@ -0,0 +1,42 @@ +package printer + +import ( + "fmt" + "io" + + "github.com/fatih/color" +) + +var ( + errorColor = color.New(color.FgRed, color.Bold) + warningColor = color.New(color.FgYellow, color.Bold) + successColor = color.New(color.FgGreen) +) + +func init() { + colors := UseColors() + if colors == nil { + return + } + if *colors { + errorColor.EnableColor() + warningColor.EnableColor() + successColor.EnableColor() + } else { + errorColor.DisableColor() + warningColor.DisableColor() + successColor.DisableColor() + } +} + +func Error(w io.Writer, format string, args ...interface{}) { + fmt.Fprintf(w, color.RedString("error: ")+format+"\n", args...) +} + +func Warning(w io.Writer, format string, args ...interface{}) { + fmt.Fprintf(w, color.YellowString("warning: ")+format+"\n", args...) +} + +func Success(w io.Writer, format string, args ...interface{}) { + fmt.Fprintf(w, color.GreenString(fmt.Sprintf(format+"\n", args...))) +}