diff --git a/README.md b/README.md index 2252aa8..9ef4493 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,8 @@ gum pager < README.md Display a spinner while running a script or command. The spinner will automatically stop after the given command exits. +To view or pipe the command's output, use the `--show-output` flag. + ```bash gum spin --spinner dot --title "Buying Bubble Gum..." -- sleep 5 ``` diff --git a/spin/command.go b/spin/command.go index 46ca5d5..4c3987d 100644 --- a/spin/command.go +++ b/spin/command.go @@ -15,14 +15,19 @@ import ( // Run provides a shell script interface for the spinner bubble. // https://github.com/charmbracelet/bubbles/spinner func (o Options) Run() error { + var isTTY bool + info, err := os.Stdout.Stat() + isTTY = info.Mode()&os.ModeCharDevice == os.ModeCharDevice + s := spinner.New() s.Style = o.SpinnerStyle.ToLipgloss() s.Spinner = spinnerMap[o.Spinner] m := model{ - spinner: s, - title: o.TitleStyle.ToLipgloss().Render(o.Title), - command: o.Command, - align: o.Align, + spinner: s, + title: o.TitleStyle.ToLipgloss().Render(o.Title), + command: o.Command, + align: o.Align, + showOutput: o.ShowOutput && isTTY, } p := tea.NewProgram(m, tea.WithOutput(os.Stderr)) mm, err := p.Run() @@ -32,15 +37,23 @@ func (o Options) Run() error { return fmt.Errorf("failed to run spin: %w", err) } - if o.ShowOutput { - fmt.Fprint(os.Stdout, m.stdout) - fmt.Fprint(os.Stderr, m.stderr) - } - if m.aborted { return exit.ErrAborted } + if err != nil { + return fmt.Errorf("failed to access stdout: %w", err) + } + + if o.ShowOutput { + if !isTTY { + _, err := os.Stdout.WriteString(m.stdout) + if err != nil { + return fmt.Errorf("failed to write to stdout: %w", err) + } + } + } + os.Exit(m.status) return nil } diff --git a/spin/options.go b/spin/options.go index 920e788..e277fb5 100644 --- a/spin/options.go +++ b/spin/options.go @@ -6,7 +6,7 @@ import "github.com/charmbracelet/gum/style" type Options struct { Command []string `arg:"" help:"Command to run"` - ShowOutput bool `help:"Show output of command" default:"false" env:"GUM_SPIN_SHOW_OUTPUT"` + ShowOutput bool `help:"Show or pipe output of command during execution" default:"false" env:"GUM_SPIN_SHOW_OUTPUT"` 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 f1dffea..4f36246 100644 --- a/spin/spin.go +++ b/spin/spin.go @@ -20,23 +20,25 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" ) type model struct { - spinner spinner.Model - title string - align string - command []string - aborted bool - - status int - stdout string - stderr string + spinner spinner.Model + title string + align string + command []string + aborted bool + status int + stdout string + showOutput bool } +var outbuf strings.Builder +var errbuf strings.Builder + type finishCommandMsg struct { stdout string - stderr string status int } @@ -48,7 +50,6 @@ func commandStart(command []string) tea.Cmd { } cmd := exec.Command(command[0], args...) //nolint:gosec - var outbuf, errbuf strings.Builder cmd.Stdout = &outbuf cmd.Stderr = &errbuf @@ -62,7 +63,6 @@ func commandStart(command []string) tea.Cmd { return finishCommandMsg{ stdout: outbuf.String(), - stderr: errbuf.String(), status: status, } } @@ -75,11 +75,16 @@ func (m model) Init() tea.Cmd { ) } func (m model) View() string { + var header string if m.align == "left" { - return m.spinner.View() + " " + m.title + header = m.spinner.View() + " " + m.title + } else { + header = m.title + " " + m.spinner.View() } - - return m.title + " " + m.spinner.View() + if !m.showOutput { + return header + } + return lipgloss.JoinVertical(lipgloss.Top, header, errbuf.String(), outbuf.String()) } func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -87,7 +92,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case finishCommandMsg: m.stdout = msg.stdout - m.stderr = msg.stderr m.status = msg.status return m, tea.Quit case tea.KeyMsg: