Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ var (
execLookPath = exec.LookPath
execCommand = exec.Command
osExit = os.Exit

skipSpinner bool
)

func init() {
Expand Down
163 changes: 163 additions & 0 deletions cmd/internal/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package internal

import (
"bufio"
"bytes"
"fmt"
"io"
"os/exec"

"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
"github.com/muesli/termenv"
)

type SpinnerCmd struct {
p *tea.Program
spinnerModel spinner.Model
err error
title string
cmd *exec.Cmd

stdout chan []byte
stderr chan []byte
errCh chan error
buf []byte
done bool
}

func NewSpinnerCmd(cmd *exec.Cmd, title ...string) *SpinnerCmd {
spinnerModel := spinner.NewModel()
spinnerModel.Frames = spinner.Dot

c := &SpinnerCmd{
spinnerModel: spinnerModel,
title: "Running",
cmd: cmd,
stdout: make(chan []byte),
stderr: make(chan []byte),
errCh: make(chan error, 2),
}

if len(title) > 0 {
c.title = title[0]
}

c.p = tea.NewProgram(c)

return c
}

func (t *SpinnerCmd) Init() tea.Cmd {
return tea.Batch(t.init(), spinner.Tick(t.spinnerModel))
}

func (t *SpinnerCmd) init() tea.Cmd {
return func() tea.Msg {
if p, err := t.cmd.StdoutPipe(); err != nil {
return finishedMsg{err}
} else {
go t.watchOutput(t.stdout, p)
}
if p, err := t.cmd.StderrPipe(); err != nil {
return finishedMsg{err}
} else {
go t.watchOutput(t.stderr, p)
}
return finishedMsg{t.cmd.Start()}
}
}

func (t *SpinnerCmd) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {

case tea.KeyMsg:
switch msg.String() {
case "q", "esc", "ctrl+c":
t.err = fmt.Errorf("quit by %s\n", msg.String())
return t, tea.Quit
default:
return t, nil
}
case finishedMsg:
if t.err = msg.error; t.err != nil {
return t, tea.Quit
}
}

if t.done {
return t, tea.Quit
}

if len(t.errCh) == 2 {
close(t.errCh)
if err := t.cmd.Wait(); err != nil {
t.err = err
}
t.done = true
t.title = "Finished"
t.buf = nil
}

var cmd tea.Cmd
t.spinnerModel, cmd = spinner.Update(msg, t.spinnerModel)
return t, cmd
}

func (t *SpinnerCmd) View() string {
if t.err != nil {
return ""
}

s := termenv.
String(spinner.View(t.spinnerModel)).
Foreground(term.Color("205")).
String()

t.UpdateOutput(t.stdout)
t.UpdateOutput(t.stderr)

return fmt.Sprintf(spinnerCmdTemplate, s, t.title, t.buf)
}

const spinnerCmdTemplate = `
%s %s %s

(esc/q/ctrl+c to quit)

`

func (t *SpinnerCmd) Run() (err error) {
if err = checkConsole(); err != nil {
return
}

if err = t.p.Start(); err != nil {
return
}

return t.err
}

func (t *SpinnerCmd) UpdateOutput(c <-chan []byte) {
select {
case b := <-c:
if !bytes.Equal(t.buf, b) {
t.buf = b
}
default:
}
}

func (t *SpinnerCmd) watchOutput(out chan<- []byte, rc io.ReadCloser) {
defer func() { _ = rc.Close() }()
br := bufio.NewReader(rc)
for {
b, _, err := br.ReadLine()
if err != nil {
t.errCh <- err
return
}
out <- b
}
}
31 changes: 31 additions & 0 deletions cmd/internal/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package internal

import (
"fmt"

tea "github.com/charmbracelet/bubbletea"
"github.com/containerd/console"
"github.com/muesli/termenv"
)

var term = termenv.ColorProfile()

type finishedMsg struct{ error }

func checkConsole() (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}()

console.Current()

return
}

func errCmd(err error) tea.Cmd {
return func() tea.Msg {
return finishedMsg{err}
}
}
25 changes: 6 additions & 19 deletions cmd/internal/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,21 @@ import (

input "github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/containerd/console"
)

type errMsg error

type Prompt struct {
p *tea.Program
textInput input.Model
err error
title string
answer string
p *tea.Program
textInput input.Model
err error
title string
answer string
}

func NewPrompt(title string, placeholder ...string) *Prompt {
p := &Prompt{
title: title,
title: title,
textInput: input.NewModel(),
}

Expand Down Expand Up @@ -100,15 +99,3 @@ func (p *Prompt) View() string {
"(esc to quit)",
)
}

func checkConsole() (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("%v", e)
}
}()

console.Current()

return
}
4 changes: 0 additions & 4 deletions cmd/internal/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"github.com/muesli/termenv"
)

var term = termenv.ColorProfile()

type Task func() error

type SpinnerTask struct {
Expand All @@ -20,8 +18,6 @@ type SpinnerTask struct {
task Task
}

type finishedMsg struct{ error }

func NewSpinnerTask(title string, task Task) *SpinnerTask {
spinnerModel := spinner.NewModel()
spinnerModel.Frames = spinner.Dot
Expand Down
8 changes: 8 additions & 0 deletions cmd/tester_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,11 @@ func setupHomeDir(t *testing.T, pattern string) string {
func teardownHomeDir(dir string) {
_ = os.RemoveAll(dir)
}

func setupSpinner() {
skipSpinner = true
}

func teardownSpinner() {
skipSpinner = false
}
12 changes: 5 additions & 7 deletions cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,13 @@ func upgradeRunE(cmd *cobra.Command, _ []string) error {
}

func upgrade(cmd *cobra.Command, cliLatestVersion string) {
task := internal.NewSpinnerTask("Upgrading...", func() (err error) {
upgrader := execCommand("go", "get", "-u", "github.com/gofiber/cli/fiber")
upgrader.Env = append(upgrader.Env, os.Environ()...)
upgrader.Env = append(upgrader.Env, "GO111MODULE=off")
upgrader := execCommand("go", "get", "-u", "github.com/gofiber/cli/fiber")
upgrader.Env = append(upgrader.Env, os.Environ()...)
upgrader.Env = append(upgrader.Env, "GO111MODULE=off")

return runCmd(upgrader)
})
scmd := internal.NewSpinnerCmd(upgrader, "Upgrading")

if err := task.Run(); err != nil {
if err := scmd.Run(); err != nil && !skipSpinner {
cmd.Printf("fiber: failed to upgrade: %v", err)
return
}
Expand Down
9 changes: 2 additions & 7 deletions cmd/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import (
)

func Test_Upgrade_upgradeRunE(t *testing.T) {
t.Skip("reopen after spinner task finished")

at := assert.New(t)

b := &bytes.Buffer{}
Expand All @@ -28,8 +26,8 @@ func Test_Upgrade_upgradeRunE(t *testing.T) {

httpmock.RegisterResponder(http.MethodGet, latestCliVersionUrl, httpmock.NewBytesResponder(200, fakeCliVersionResponse()))

setupCmd()
defer teardownCmd()
setupSpinner()
defer teardownSpinner()

at.Nil(upgradeRunE(upgradeCmd, nil))

Expand All @@ -51,9 +49,6 @@ func Test_Upgrade_upgrade(t *testing.T) {
upgradeCmd.SetErr(b)
upgradeCmd.SetOut(b)

setupCmd(errFlag)
defer teardownCmd()

upgrade(upgradeCmd, "99.99.99")

at.Contains(b.String(), "failed to upgrade")
Expand Down