Skip to content

Commit

Permalink
Refactor text.Progress to not use interactive spinners when in non-TT…
Browse files Browse the repository at this point in the history
…Y environments.
  • Loading branch information
phamann committed Sep 15, 2021
1 parent ebed164 commit 49d05be
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 52 deletions.
7 changes: 1 addition & 6 deletions pkg/commands/compute/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,7 @@ func NewBuildCommand(parent cmd.Registerer, client api.HTTPClient, globals *conf

// Exec implements the command interface.
func (c *BuildCommand) Exec(in io.Reader, out io.Writer) (err error) {
var progress text.Progress
if c.Globals.Verbose() {
progress = text.NewVerboseProgress(out)
} else {
progress = text.NewQuietProgress(out)
}
progress := text.NewProgress(out, c.Globals.Verbose())

defer func(errLog errors.LogInterface) {
if err != nil {
Expand Down
16 changes: 2 additions & 14 deletions pkg/commands/compute/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,7 @@ func (c *DeployCommand) Exec(in io.Reader, out io.Writer) (err error) {

// RESOURCE CREATION...

var progress text.Progress
if verbose {
progress = text.NewVerboseProgress(out)
} else {
progress = text.NewQuietProgress(out)
}

progress := text.NewProgress(out, c.Globals.Verbose())
undoStack := undo.NewStack()

defer func(errLog errors.LogInterface, progress text.Progress) {
Expand Down Expand Up @@ -418,13 +412,7 @@ func manageNoServiceIDFlow(
text.Break(out)
}

var progress text.Progress

if verbose {
progress = text.NewVerboseProgress(out)
} else {
progress = text.NewQuietProgress(out)
}
progress := text.NewProgress(out, verbose)

// There is no service and so we'll do a one time creation of the service
//
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/compute/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ func (c *InitCommand) Exec(in io.Reader, out io.Writer) (err error) {
text.Break(out)

if !c.Globals.Verbose() {
progress = text.NewQuietProgress(out)
progress = text.NewProgress(out, false)
}

_, err = exec.LookPath("git")
Expand Down
7 changes: 1 addition & 6 deletions pkg/commands/compute/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ func NewPackCommand(parent cmd.Registerer, globals *config.Data) *PackCommand {

// Exec implements the command interface.
func (c *PackCommand) Exec(in io.Reader, out io.Writer) (err error) {
var progress text.Progress
if c.Globals.Verbose() {
progress = text.NewVerboseProgress(out)
} else {
progress = text.NewQuietProgress(out)
}
progress := text.NewProgress(out, c.Globals.Verbose())

defer func(errLog errors.LogInterface) {
if err != nil {
Expand Down
7 changes: 1 addition & 6 deletions pkg/commands/compute/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,7 @@ func (c *ServeCommand) Exec(in io.Reader, out io.Writer) (err error) {
text.Break(out)
}

var progress text.Progress
if c.Globals.Verbose() {
progress = text.NewVerboseProgress(out)
} else {
progress = text.NewQuietProgress(out)
}
progress := text.NewProgress(out, c.Globals.Verbose())

bin, err := getViceroy(progress, out, c.viceroyVersioner)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/compute/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (c *UpdateCommand) Exec(in io.Reader, out io.Writer) (err error) {
return err
}

progress := text.NewQuietProgress(out)
progress := text.NewProgress(out, c.Globals.Verbose())
defer func() {
if err != nil {
c.Globals.ErrLog.AddWithContext(err, map[string]interface{}{
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/configure/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (c *RootCommand) Exec(in io.Reader, out io.Writer) (err error) {

text.Break(out)

progress := text.NewQuietProgress(out)
progress := text.NewProgress(out, c.Globals.Verbose())
defer func() {
if err != nil {
c.Globals.ErrLog.Add(err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/update/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (c *RootCommand) Exec(in io.Reader, out io.Writer) error {
text.Output(out, "Latest version: %s", latest)
text.Break(out)

progress := text.NewQuietProgress(out)
progress := text.NewProgress(out, c.Globals.Verbose())
progress.Step("Updating versioning information...")

err = c.Globals.File.Load(c.Globals.File.CLI.RemoteConfig, c.client, config.ConfigRequestTimeout, config.FilePath)
Expand Down
95 changes: 79 additions & 16 deletions pkg/text/progress.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import (
"encoding/hex"
"fmt"
"io"
"os"
"runtime"
"strings"
"sync"
"time"
"unicode/utf8"

"github.com/mattn/go-isatty"
)

// Progress is a producer contract, abstracting over the quiet and verbose
Expand All @@ -27,6 +30,20 @@ type Progress interface {
Fail()
}

// NewProgress returns a Progress based on the given verbosity level or whether
// the current process is running in a terminal environment.
func NewProgress(output io.Writer, verbose bool) Progress {
var progress Progress
if verbose {
progress = NewVerboseProgress(output)
} else if isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) {
progress = NewInteractiveProgress(output)
} else {
progress = NewQuietProgress(output)
}
return progress
}

// Ticker is a small consumer contract for the Spin function,
// capturing part of the Progress interface.
type Ticker interface {
Expand All @@ -52,10 +69,10 @@ func Spin(ctx context.Context, frames []rune, interval time.Duration, target Tic
}
}

// QuietProgress is an implementation of Progress that includes a spinner at the
// InteractiveProgress is an implementation of Progress that includes a spinner at the
// beginning of each Step, and where newline-delimited lines written via Write
// overwrite the current step line in the output.
type QuietProgress struct {
type InteractiveProgress struct {
mtx sync.Mutex
output io.Writer

Expand All @@ -68,9 +85,9 @@ type QuietProgress struct {
done <-chan struct{} // wait for Spin to stop
}

// NewQuietProgress returns a QuietProgress outputting to the writer.
func NewQuietProgress(output io.Writer) *QuietProgress {
p := &QuietProgress{
// NewInteractiveProgress returns a InteractiveProgress outputting to the writer.
func NewInteractiveProgress(output io.Writer) *InteractiveProgress {
p := &InteractiveProgress{
output: output,
stepHeader: "Initializing...",
}
Expand All @@ -89,7 +106,7 @@ func NewQuietProgress(output io.Writer) *QuietProgress {
return p
}

func (p *QuietProgress) replaceLine(format string, args ...interface{}) {
func (p *InteractiveProgress) replaceLine(format string, args ...interface{}) {
// Clear the current line.
n := utf8.RuneCountInString(p.currentOutput)
switch runtime.GOOS {
Expand All @@ -107,15 +124,15 @@ func (p *QuietProgress) replaceLine(format string, args ...interface{}) {
fmt.Fprint(p.output, p.currentOutput)
}

func (p *QuietProgress) getStatus() string {
func (p *InteractiveProgress) getStatus() string {
if p.lastBufferLine != "" {
return p.lastBufferLine // takes precedence
}
return p.stepHeader
}

// Tick implements the Progress interface.
func (p *QuietProgress) Tick(r rune) {
func (p *InteractiveProgress) Tick(r rune) {
p.mtx.Lock()
defer p.mtx.Unlock()

Expand All @@ -124,7 +141,7 @@ func (p *QuietProgress) Tick(r rune) {

// Write implements the Progress interface, emitting each incoming byte slice
// to the internal buffer to be written to the terminal on the next tick.
func (p *QuietProgress) Write(buf []byte) (int, error) {
func (p *InteractiveProgress) Write(buf []byte) (int, error) {
p.mtx.Lock()
defer p.mtx.Unlock()

Expand All @@ -135,7 +152,7 @@ func (p *QuietProgress) Write(buf []byte) (int, error) {
}

// Step implements the Progress interface.
func (p *QuietProgress) Step(msg string) {
func (p *InteractiveProgress) Step(msg string) {
msg = strings.TrimSpace(msg)

p.mtx.Lock()
Expand All @@ -156,7 +173,7 @@ func (p *QuietProgress) Step(msg string) {
}

// Done implements the Progress interface.
func (p *QuietProgress) Done() {
func (p *InteractiveProgress) Done() {
// It's important to cancel the Spin goroutine before taking the lock,
// because otherwise it's possible to generate a deadlock if the output
// io.Writer is also synchronized.
Expand All @@ -171,7 +188,7 @@ func (p *QuietProgress) Done() {
}

// Fail implements the Progress interface.
func (p *QuietProgress) Fail() {
func (p *InteractiveProgress) Fail() {
p.cancel()
<-p.done

Expand Down Expand Up @@ -202,6 +219,48 @@ func LastFullLine(s string) string {
//
//

// QuietProgress is an implementation of Progress that attempts to be quiet in
// it's output. I.e. it only prints each Step as it progresses and discards any
// intermediary writes between steps. No spinners are used, therefore it's
// useful for non-TTY environiments, such as CI.
type QuietProgress struct {
output io.Writer
nullWriter io.Writer
}

// NewQuietProgress returns a QuietProgress outputting to the writer.
func NewQuietProgress(output io.Writer) *QuietProgress {
qp := &QuietProgress{
output: output,
nullWriter: io.Discard,
}
qp.Step("Initializing...")
return qp
}

// Tick implements the Progress interface. It's a no-op.
func (p *QuietProgress) Tick(r rune) {}

// Tick implements the Progress interface.
func (p *QuietProgress) Write(buf []byte) (int, error) {
return p.nullWriter.Write(buf)
}

// Step implements the Progress interface.
func (p *QuietProgress) Step(msg string) {
fmt.Fprintln(p.output, strings.TrimSpace(msg))
}

// Done implements the Progress interface. It's a no-op.
func (p *QuietProgress) Done() {}

// Fail implements the Progress interface. It's a no-op.
func (p *QuietProgress) Fail() {}

//
//
//

// VerboseProgress is an implementation of Progress that treats Step and Write
// more or less the same: it simply pipes all output to the provided Writer. No
// spinners are used.
Expand Down Expand Up @@ -241,19 +300,23 @@ func (p *VerboseProgress) Fail() {}

// NullProgress is an implementation of Progress which discards everything
// written into it and produces no output.
type NullProgress struct{}
type NullProgress struct {
output io.Writer
}

// NewNullProgress returns a NullProgress.
func NewNullProgress() *NullProgress {
return &NullProgress{}
return &NullProgress{
output: io.Discard,
}
}

// Tick implements the Progress interface. It's a no-op.
// Tick implements the Progress interface. It's a no-opt
func (p *NullProgress) Tick(r rune) {}

// Tick implements the Progress interface.
func (p *NullProgress) Write(buf []byte) (int, error) {
return 0, nil
return p.output.Write(buf)
}

// Step implements the Progress interface.
Expand Down

0 comments on commit 49d05be

Please sign in to comment.