From f4d198396fcdbb1a7e8f3c55f585dbdda1c4b64f Mon Sep 17 00:00:00 2001 From: Jelle Besseling Date: Thu, 28 Mar 2024 18:11:07 +0100 Subject: [PATCH] feat(spin): Add support for --show-error for the spinner. (rebase #440) (#518) * feat(spin): Add support for `--show-error` for the spinner. This makes it so that if the `--show-error` flag is provided then the full output of the command will be printed if the command fails. This kind of works in conjuncture with `--show-output` in that if the command succeeds only STDOUT is pushed. If the command fails both `STDOUT` and `STDERR` are pushed. This builds off of https://github.com/charmbracelet/gum/pull/371 Resolves #55 * chore: Fix formatting --------- Co-authored-by: Elliot Courant --- spin/command.go | 25 ++++++++++++++++++------- spin/options.go | 1 + spin/spin.go | 18 ++++++++++++++++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/spin/command.go b/spin/command.go index 1f7f3bf..3c55e1d 100644 --- a/spin/command.go +++ b/spin/command.go @@ -44,15 +44,26 @@ func (o Options) Run() error { return fmt.Errorf("failed to access stdout: %w", err) } - if o.ShowOutput { - // BubbleTea writes the View() to stderr. - // If the program is being piped then put the accumulated output in stdout. - if !isTTY { - _, err := os.Stdout.WriteString(m.stdout) - if err != nil { - return fmt.Errorf("failed to write to stdout: %w", err) + // If the command succeeds, and we are printing output and we are in a TTY then push the STDOUT we got to the actual + // STDOUT for piping or other things. + if m.status == 0 { + if o.ShowOutput { + // BubbleTea writes the View() to stderr. + // If the program is being piped then put the accumulated output in stdout. + if !isTTY { + _, err := os.Stdout.WriteString(m.stdout) + if err != nil { + return fmt.Errorf("failed to write to stdout: %w", err) + } } } + } else if o.ShowError { + // Otherwise if we are showing errors and the command did not exit with a 0 status code then push all of the command + // output to the terminal. This way failed commands can be debugged. + _, err := os.Stdout.WriteString(m.output) + if err != nil { + return fmt.Errorf("failed to write to stdout: %w", err) + } } os.Exit(m.status) diff --git a/spin/options.go b/spin/options.go index 9b55c4a..542ff2b 100644 --- a/spin/options.go +++ b/spin/options.go @@ -11,6 +11,7 @@ type Options struct { Command []string `arg:"" help:"Command to run"` ShowOutput bool `help:"Show or pipe output of command during execution" default:"false" env:"GUM_SPIN_SHOW_OUTPUT"` + ShowError bool `help:"Show output of command only if the command fails" default:"false" env:"GUM_SPIN_SHOW_ERROR"` Spinner string `help:"Spinner type" short:"s" type:"spinner" enum:"line,dot,minidot,jump,pulse,points,globe,moon,monkey,meter,hamburger" default:"dot" env:"GUM_SPIN_SPINNER"` SpinnerStyle style.Styles `embed:"" prefix:"spinner." set:"defaultForeground=212" envprefix:"GUM_SPIN_SPINNER_"` Title string `help:"Text to display to user while spinning" default:"Loading..." env:"GUM_SPIN_TITLE"` diff --git a/spin/spin.go b/spin/spin.go index 0daa2d4..ee30326 100644 --- a/spin/spin.go +++ b/spin/spin.go @@ -15,6 +15,7 @@ package spin import ( + "io" "os" "os/exec" "strings" @@ -37,16 +38,22 @@ type model struct { aborted bool status int stdout string + stderr string + output string showOutput bool + showError bool timeout time.Duration hasTimeout bool } +var bothbuf strings.Builder var outbuf strings.Builder var errbuf strings.Builder type finishCommandMsg struct { stdout string + stderr string + output string status int } @@ -59,8 +66,11 @@ func commandStart(command []string) tea.Cmd { cmd := exec.Command(command[0], args...) //nolint:gosec if isatty.IsTerminal(os.Stdout.Fd()) { - cmd.Stdout = &outbuf - cmd.Stderr = &errbuf + stdout := io.MultiWriter(&bothbuf, &errbuf) + stderr := io.MultiWriter(&bothbuf, &outbuf) + + cmd.Stdout = stdout + cmd.Stderr = stderr } else { cmd.Stdout = os.Stdout } @@ -75,6 +85,8 @@ func commandStart(command []string) tea.Cmd { return finishCommandMsg{ stdout: outbuf.String(), + stderr: errbuf.String(), + output: bothbuf.String(), status: status, } } @@ -123,6 +135,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, timeout.Tick(msg.TimeoutValue, msg.Data) case finishCommandMsg: m.stdout = msg.stdout + m.stderr = msg.stderr + m.output = msg.output m.status = msg.status m.quitting = true return m, tea.Quit