feat: Add gum spin command

Spin provides a shell script interface for the spinner bubble. It is
useful for displaying that some task is running in the background while
consuming it's output so that it is not shown to the user.

For example, let's do a long running task:
```
sleep 5
```

We can simply prepend a spinner to this task to show it to the user,
while performing the task / command in the background.

```
gum spin -t "Taking a nap..." -- sleep 5
```

The spinner will automatically exit when the task is complete.
This commit is contained in:
Maas Lalani 2022-07-06 12:08:17 -04:00
parent c906d1904d
commit 454040cf4d
No known key found for this signature in database
GPG key ID: 5A6ED5CBF1A0A000
4 changed files with 100 additions and 0 deletions

22
spin/command.go Normal file
View file

@ -0,0 +1,22 @@
package spin
import (
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// Run provides a shell script interface for the spinner bubble.
// https://github.com/charmbracelet/bubbles/spinner
func (o Options) Run() {
s := spinner.New()
s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color(o.Color))
s.Spinner = spinnerMap[o.Spinner]
m := model{
spinner: s,
title: o.Title,
command: o.Command,
}
p := tea.NewProgram(m)
_ = p.Start()
}

10
spin/options.go Normal file
View file

@ -0,0 +1,10 @@
package spin
// Options is the customization options for the spin command.
type Options struct {
Command []string `arg:"" help:"Command to run"`
Color string `help:"Spinner color" default:"#FF06B7"`
Title string `help:"Text to display to user while spinning" default:"Loading..."`
Spinner string `help:"Spinner type" type:"spinner" enum:"line,dot,minidot,jump,pulse,points,globe,moon,monkey,meter,hamburger" default:"dot"`
}

51
spin/spin.go Normal file
View file

@ -0,0 +1,51 @@
package spin
import (
"os/exec"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
)
type model struct {
spinner spinner.Model
title string
command []string
}
type finishCommandMsg struct{ output string }
func commandStart(command []string) tea.Cmd {
return func() tea.Msg {
var args []string
if len(command) > 1 {
args = command[1:]
}
out, _ := exec.Command(command[0], args...).Output()
return finishCommandMsg{output: string(out)}
}
}
func (m model) Init() tea.Cmd {
return tea.Batch(
m.spinner.Tick,
commandStart(m.command),
)
}
func (m model) View() string { return m.spinner.View() + " " + m.title }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case finishCommandMsg:
return m, tea.Quit
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c":
return m, tea.Quit
}
}
m.spinner, cmd = m.spinner.Update(msg)
return m, cmd
}

17
spin/spinners.go Normal file
View file

@ -0,0 +1,17 @@
package spin
import "github.com/charmbracelet/bubbles/spinner"
var spinnerMap = map[string]spinner.Spinner{
"line": spinner.Line,
"dot": spinner.Dot,
"minidot": spinner.MiniDot,
"jump": spinner.Jump,
"pulse": spinner.Pulse,
"points": spinner.Points,
"globe": spinner.Globe,
"moon": spinner.Moon,
"monkey": spinner.Monkey,
"meter": spinner.Meter,
"hamburger": spinner.Hamburger,
}