From 9b447b035f272a0973b1c19a273cc85d5ca5b0aa Mon Sep 17 00:00:00 2001 From: Craig Gumbley Date: Sun, 14 Aug 2022 14:03:18 +0100 Subject: [PATCH] Improve TTY detection Prior to this commit there was no TTY detection in the manager or tput package. The result was that during non-interactive executions escape sequences would also be written to the output stream. Additionally each frame would also be written. For example, running the following would result in a messy log file: ``` go run examples/advanced/main.go |& tee log.log ``` This introduces changes that do the following: * If a TTY is not detected, do not render any spinner frames * If a TTY is not detected, do not write escape sequences It is also possible to override the above by setting the environment variable `YSMRR_FORCE_TTY` to `true`. This will make ysmrr ignore tty detection and render frames and write escape sequences. An example of the above override can be seen in the `tput_test.go` file. --- go.mod | 2 +- manager.go | 12 +++++++++++- pkg/tput/tput.go | 31 +++++++++++++++++++++++++------ pkg/tput/tput_test.go | 15 +++++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 07aa6ed..20688d6 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.18 require ( github.com/fatih/color v1.13.0 github.com/mattn/go-colorable v0.1.12 + github.com/mattn/go-isatty v0.0.14 github.com/stretchr/testify v1.8.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/manager.go b/manager.go index 0811e6b..2c11945 100644 --- a/manager.go +++ b/manager.go @@ -13,6 +13,7 @@ import ( "github.com/chelnak/ysmrr/pkg/colors" "github.com/chelnak/ysmrr/pkg/tput" "github.com/mattn/go-colorable" + "github.com/mattn/go-isatty" ) // SpinnerManager manages spinners @@ -42,6 +43,7 @@ type spinnerManager struct { done chan bool ticks *time.Ticker frame int + tty bool } // AddSpinner adds a new spinner to the manager. @@ -156,7 +158,10 @@ func (sm *spinnerManager) render() { case <-sm.done: return case <-sm.ticks.C: - sm.renderFrame() + // Only render the frame if we are in a terminal. + if sm.tty { + sm.renderFrame() + } } tput.Rc(sm.writer) @@ -208,6 +213,7 @@ func NewSpinnerManager(options ...managerOption) SpinnerManager { messageColor: colors.NoColor, writer: getWriter(), done: make(chan bool), + tty: tty(), } for _, option := range options { @@ -217,6 +223,10 @@ func NewSpinnerManager(options ...managerOption) SpinnerManager { return sm } +func tty() bool { + return isatty.IsTerminal(os.Stdout.Fd()) || os.Getenv("YSMRR_FORCE_TTY") == "true" +} + func getWriter() io.Writer { // Windows support conveniently provided by github.com/mattn/go-colorable <3. if runtime.GOOS == "windows" { diff --git a/pkg/tput/tput.go b/pkg/tput/tput.go index 18af1c2..a835524 100644 --- a/pkg/tput/tput.go +++ b/pkg/tput/tput.go @@ -5,36 +5,55 @@ package tput import ( "fmt" "io" + "os" "strings" + + "github.com/mattn/go-isatty" ) // Sc saves the current position of the cursor. func Sc(w io.Writer) { - fmt.Fprint(w, "\u001b7") + write(w, "\u001b7") } // Rc restores the cursor to the saved position. func Rc(w io.Writer) { - fmt.Fprint(w, "\u001b8") + write(w, "\u001b8") } // Civis hides the cursor. func Civis(w io.Writer) { - fmt.Fprint(w, "\u001b[?25l") + write(w, "\u001b[?25l") } // Cnorm shows the cursor. func Cnorm(w io.Writer) { - fmt.Fprintf(w, "\u001b[?25h") + writef(w, "\u001b[?25h") } // Cuu moves the cursor up by n lines. func Cuu(w io.Writer, n int) { - fmt.Fprintf(w, "\u001b[%dA", n) + writef(w, "\u001b[%dA", n) } // BufScreen ensures that there are enough lines available // by sending n * newlines to the writer. func BufScreen(w io.Writer, n int) { - fmt.Fprintf(w, "%s", strings.Repeat("\n", n)) + writef(w, "%s", strings.Repeat("\n", n)) +} + +func tty() bool { + return isatty.IsTerminal(os.Stdout.Fd()) || os.Getenv("YSMRR_FORCE_TTY") == "true" +} + +func write(w io.Writer, s string) { + if tty() { + fmt.Fprint(w, s) + } +} + +func writef(w io.Writer, format string, a ...interface{}) { + if tty() { + fmt.Fprintf(w, format, a...) + } } diff --git a/pkg/tput/tput_test.go b/pkg/tput/tput_test.go index b47243d..335b1a0 100644 --- a/pkg/tput/tput_test.go +++ b/pkg/tput/tput_test.go @@ -3,6 +3,7 @@ package tput_test import ( "bytes" "io" + "os" "strings" "testing" @@ -10,7 +11,18 @@ import ( "github.com/stretchr/testify/assert" ) +func setup() { + _ = os.Setenv("YSMRR_FORCE_TTY", "true") +} + +func cleanup() { + _ = os.Unsetenv("YSMRR_FORCE_TTY") +} + func TestTput(t *testing.T) { + setup() + defer cleanup() + tests := []struct { name string fn func(w io.Writer) @@ -48,6 +60,9 @@ func TestTput(t *testing.T) { } func TestTputCommandsWithInputs(t *testing.T) { + setup() + defer cleanup() + tests := []struct { name string input int