From 0d153d05852c1cd0c5bba3ca4f17386e3e6aeece Mon Sep 17 00:00:00 2001 From: Jiri Hnidek Date: Mon, 10 Oct 2022 16:58:45 +0200 Subject: [PATCH] feat: Better support for non-interactive mode * When stdout is not TTY, then do not display spinner and status text and the output of rhc should not be colorful, because output is redirected to some file. When temporary status messages and escape sequences were added to log file, then viewing such file could be complicated. * When NO_COLOR environment variable is set, then do not display color nor animation too. * When CLI option --no-color is used, then colors and animations are suppressed too. Note: this is global option and could not be used after sub-command. * This commit closes #14 issue --- main.go | 140 +++++++++++++++++++++++++++++++++++++++++--------------- util.go | 7 +++ 2 files changed, 109 insertions(+), 38 deletions(-) diff --git a/main.go b/main.go index df3932b..4d852d2 100644 --- a/main.go +++ b/main.go @@ -21,9 +21,15 @@ const redColor = "\u001B[31m" const greenColor = "\u001B[32m" const endColor = "\u001B[0m" -const successPrefix = greenColor + "●" + endColor -const failPrefix = redColor + "●" + endColor -const errorPrefix = redColor + "!" + endColor +// Colorful prefixes +const ttySuccessPrefix = greenColor + "●" + endColor +const ttyFailPrefix = redColor + "●" + endColor +const ttyErrorPrefix = redColor + "!" + endColor + +// Black & white prefixes. Unicode characters +const bwSuccessPrefix = "✓" +const bwFailPrefix = "𐄂" +const bwErrorPrefix = "!" func main() { app := cli.NewApp() @@ -42,6 +48,12 @@ func main() { log.SetFlags(0) log.SetPrefix("") + isColorful := true + + successPrefix := ttySuccessPrefix + failPrefix := ttyFailPrefix + errorPrefix := ttyErrorPrefix + app.Flags = []cli.Flag{ &cli.BoolFlag{ Name: "generate-man-page", @@ -56,6 +68,11 @@ func main() { Hidden: true, Value: "error", }, + &cli.BoolFlag{ + Name: "no-color", + Hidden: false, + Value: false, + }, } app.Commands = []*cli.Command{ { @@ -128,20 +145,24 @@ func main() { } } - s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) - s.Suffix = " Connecting to Red Hat Subscription Management..." - s.Start() + var s *spinner.Spinner + if isColorful { + s = spinner.New(spinner.CharSets[9], 100*time.Millisecond) + s.Suffix = " Connecting to Red Hat Subscription Management..." + s.Start() + } var err error if c.String("organization") != "" { err = registerActivationKey(c.String("organization"), c.StringSlice("activation-key"), c.String("server")) } else { err = registerPassword(username, password, c.String("server")) } - if err != nil { + if isColorful { s.Stop() + } + if err != nil { return cli.Exit(err, 1) } - s.Stop() fmt.Printf(successPrefix + " Connected to Red Hat Subscription Management\n") } else { fmt.Printf(successPrefix + " This system is already connected to Red Hat Subscription Management\n") @@ -149,26 +170,34 @@ func main() { durations["rhsm"] = time.Since(start) start = time.Now() - s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) - - s.Suffix = " Connecting to Red Hat Insights..." - s.Start() - if err := registerInsights(); err != nil { + var s *spinner.Spinner + if isColorful { + s = spinner.New(spinner.CharSets[9], 100*time.Millisecond) + s.Suffix = " Connecting to Red Hat Insights..." + s.Start() + } + err = registerInsights() + if isColorful { s.Stop() + } + if err != nil { return cli.Exit(err, 1) } - s.Stop() fmt.Printf(successPrefix + " Connected to Red Hat Insights\n") durations["insights"] = time.Since(start) start = time.Now() - s.Suffix = fmt.Sprintf(" Activating the %v daemon", BrandName) - s.Start() - if err := activate(); err != nil { + if isColorful { + s.Suffix = fmt.Sprintf(" Activating the %v daemon", BrandName) + s.Start() + } + err = activate() + if isColorful { s.Stop() + } + if err != nil { return cli.Exit(err, 1) } - s.Stop() fmt.Printf(successPrefix+" Activated the %v daemon\n", BrandName) durations[BrandName] = time.Since(start) @@ -205,40 +234,52 @@ func main() { s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) start = time.Now() - s.Suffix = fmt.Sprintf(" Deactivating the %v daemon", BrandName) - s.Start() - if err := deactivate(); err != nil { - errorMessages[BrandName] = fmt.Errorf("cannot deactivate daemon: %w", err) + if isColorful { + s.Suffix = fmt.Sprintf(" Deactivating the %v daemon", BrandName) + s.Start() + } + err = deactivate() + if isColorful { s.Stop() + } + if err != nil { + errorMessages[BrandName] = fmt.Errorf("cannot deactivate daemon: %w", err) fmt.Printf(errorPrefix+" Cannot deactivate the %v daemon\n", BrandName) } else { - s.Stop() fmt.Printf(failPrefix+" Deactivated the %v daemon\n", BrandName) } durations[BrandName] = time.Since(start) start = time.Now() - s.Suffix = " Disconnecting from Red Hat Insights..." - s.Start() - if err := unregisterInsights(); err != nil { - errorMessages["insights"] = fmt.Errorf("cannot disconnect from Red Hat Insights: %w", err) + if isColorful { + s.Suffix = " Disconnecting from Red Hat Insights..." + s.Start() + } + err = unregisterInsights() + if isColorful { s.Stop() + } + if err != nil { + errorMessages["insights"] = fmt.Errorf("cannot disconnect from Red Hat Insights: %w", err) fmt.Printf(errorPrefix + " Cannot disconnect from Red Hat Insights\n") } else { - s.Stop() fmt.Print(failPrefix + " Disconnected from Red Hat Insights\n") } durations["insights"] = time.Since(start) start = time.Now() - s.Suffix = " Disconnecting from Red Hat Subscription Management..." - s.Start() - if err := unregister(); err != nil { - errorMessages["rhsm"] = fmt.Errorf("cannot disconnect from Red Hat Subscription Management: %w", err) + if isColorful { + s.Suffix = " Disconnecting from Red Hat Subscription Management..." + s.Start() + } + err = unregister() + if isColorful { s.Stop() + } + if err != nil { + errorMessages["rhsm"] = fmt.Errorf("cannot disconnect from Red Hat Subscription Management: %w", err) fmt.Printf(errorPrefix + " Cannot disconnect from Red Hat Subscription Management\n") } else { - s.Stop() fmt.Printf(failPrefix + " Disconnected from Red Hat Subscription Management\n") } durations["rhsm"] = time.Since(start) @@ -312,12 +353,16 @@ func main() { fmt.Printf(successPrefix + " Connected to Red Hat Subscription Management\n") } - s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) - - s.Suffix = " Checking Red Hat Insights..." - s.Start() + var s *spinner.Spinner + if isColorful { + s = spinner.New(spinner.CharSets[9], 100*time.Millisecond) + s.Suffix = " Checking Red Hat Insights..." + s.Start() + } isRegistered, err := insightsIsRegistered() - s.Stop() + if isColorful { + s.Stop() + } if isRegistered { fmt.Print(successPrefix + " Connected to Red Hat Insights\n") @@ -381,6 +426,25 @@ func main() { } log.SetLevel(level) + // Detect if the output goes to TTY or some file + isColorful = isTerminal(os.Stdout.Fd()) + + // When environment variable NO_COLOR is set, then do not display colors and animations too. + // We do not care about value of NO_COLOR variable + if _, isNoColorSet := os.LookupEnv("NO_COLOR"); isNoColorSet { + isColorful = false + } + + if c.Bool("no-color") { + isColorful = false + } + + if !isColorful { + successPrefix = bwSuccessPrefix + failPrefix = bwFailPrefix + errorPrefix = bwErrorPrefix + } + return nil } diff --git a/util.go b/util.go index 4234683..d634bbf 100644 --- a/util.go +++ b/util.go @@ -5,8 +5,15 @@ import ( "io" "github.com/urfave/cli/v2" + "golang.org/x/sys/unix" ) +// isTerminal returns true if the file descriptor is terminal. +func isTerminal(fd uintptr) bool { + _, err := unix.IoctlGetTermios(int(fd), unix.TCGETS) + return err == nil +} + // BashCompleteCommand prints all visible flag options for the given command, // and then recursively calls itself on each subcommand. func BashCompleteCommand(cmd *cli.Command, w io.Writer) {