feat(Spin): Option to show live output (#303)

* Added live output buffer and option flag

* Update Spin on README.md

* Returned output formatting to previous version.

* Separated the showOutput and liveOutput flags.
Both flags can now be used at once.

* Removed liveOutout flag
showOutput flag is now realtime

* (spin) Consolodated stderr and stdout

* (spin) Consolodated stdout and stderr

* (spin) If being piped, writes to stdout

* Added error check and did some housekeeping

* No longer outputs to tea.View if piped

* Cleaned up the combining of stderr and stdout

* Fixed spinner alignment.  Updated Readme
This commit is contained in:
Rose Thatcher 2023-06-27 07:31:54 -07:00 committed by GitHub
parent f1b99f0aa4
commit f048bd8d87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 45 additions and 26 deletions

View file

@ -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
```

View file

@ -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
}

View file

@ -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"`

View file

@ -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: