Skip to content

Commit

Permalink
use functional option for cui (#174)
Browse files Browse the repository at this point in the history
  • Loading branch information
micnncim authored and ktr0731 committed May 27, 2019
1 parent 5ff074c commit 461f928
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 39 deletions.
12 changes: 4 additions & 8 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (

// App is the root component for running the application.
type App struct {
cui *cui.UI
cui cui.UI

flagSet *pflag.FlagSet

Expand All @@ -27,7 +27,7 @@ type App struct {

// New instantiates a new App instance. If cui is nil, the default UI will be used.
// Note that cui is also used for the REPL UI if the mode is REPL mode.
func New(cui *cui.UI) *App {
func New(cui cui.UI) *App {
return &App{
cui: cui,
}
Expand All @@ -51,10 +51,6 @@ func (a *App) Run(args []string) int {
}

func (a *App) run(args []string) error {
if a.cui == nil {
a.cui = cui.DefaultUI()
}

flags, err := a.parseFlags(args)
if err != nil {
return err
Expand Down Expand Up @@ -107,9 +103,9 @@ func (a *App) run(args []string) error {

if a.cfg.Config.Meta.AutoUpdate {
eg.Go(func() error {
return processUpdate(ctx, a.cfg.Config, a.cui.Writer)
return processUpdate(ctx, a.cfg.Config, a.cui.Writer())
})
} else if err := processUpdate(ctx, a.cfg.Config, a.cui.Writer); err != nil {
} else if err := processUpdate(ctx, a.cfg.Config, a.cui.Writer()); err != nil {
return errors.Wrap(err, "failed to update Evans")
}

Expand Down
4 changes: 2 additions & 2 deletions app/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (f *flags) validate() error {
func (a *App) parseFlags(args []string) (*flags, error) {
f := pflag.NewFlagSet("main", pflag.ContinueOnError)
f.SortFlags = false
f.SetOutput(a.cui.Writer)
f.SetOutput(a.cui.Writer())

var flags flags

Expand Down Expand Up @@ -144,7 +144,7 @@ func (a *App) parseFlags(args []string) (*flags, error) {
fmt.Fprintf(w, " %s\t%s\n", cmd, usage)
})
w.Flush()
fmt.Fprintf(a.cui.Writer, usageFormat, meta.AppName, buf.String())
fmt.Fprintf(a.cui.Writer(), usageFormat, meta.AppName, buf.String())
}

// ignore error because flag set mode is ExitOnError
Expand Down
20 changes: 20 additions & 0 deletions cui/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cui

import (
"io"
)

// Option for basicUI
type Option func(*basicUI)

func Writer(w io.Writer) Option {
return func(u *basicUI) {
u.writer = w
}
}

func ErrWriter(ew io.Writer) Option {
return func(u *basicUI) {
u.errWriter = ew
}
}
69 changes: 69 additions & 0 deletions cui/option_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package cui

import (
"io"
"os"
"testing"

"github.com/stretchr/testify/assert"
)

func TestReader(t *testing.T) {
cases := map[string]struct {
r io.Reader
expected io.Reader
}{
"normal": {
r: os.Stdin,
expected: os.Stdin,
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
ui := &basicUI{}
Reader(c.r)(ui)
assert.Equal(t, c.expected, ui.reader)
})
}
}

func TestWriter(t *testing.T) {
cases := map[string]struct {
w io.Writer
expected io.Writer
}{
"normal": {
w: os.Stdout,
expected: os.Stdout,
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
ui := &basicUI{}
Writer(c.w)(ui)
assert.Equal(t, c.expected, ui.writer)
})
}
}

func TestErrWriter(t *testing.T) {
cases := map[string]struct {
ew io.Writer
expected io.Writer
}{
"normal": {
ew: os.Stderr,
expected: os.Stderr,
},
}

for name, c := range cases {
t.Run(name, func(t *testing.T) {
ui := &basicUI{}
ErrWriter(c.ew)(ui)
assert.Equal(t, c.expected, ui.errWriter)
})
}
}
78 changes: 58 additions & 20 deletions cui/ui.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,80 @@
// Package cui defines charcter user interfaces for I/O.
package cui

import (
"fmt"
"io"
"os"

"github.com/fatih/color"
colorable "github.com/mattn/go-colorable"
)

var defaultUI = UI{
Prefix: "evans: ",
Writer: os.Stdout,
ErrWriter: os.Stderr,
// UI provides formatted I/O interfaces.
// It is used from Evans's standard I/O and CLI mode I/O.
type UI interface {
Output(s string)
Info(s string)
Error(s string)

Writer() io.Writer
}

// New creates a new UI with passed options.
func New(opts ...Option) UI {
// Creates a new UI with stdin, stdout, stderr.
ui := &basicUI{
writer: colorable.NewColorableStdout(),
errWriter: colorable.NewColorableStderr(),
}
for _, opt := range opts {
opt(ui)
}
return ui
}

// UI provides formatted output for the application. Almost users can use
// DefaultUI() as it is.
type UI struct {
Prefix string
Writer, ErrWriter io.Writer
type basicUI struct {
writer, errWriter io.Writer
}

// Output writes out the passed argument s to Writer with a line break.
func (u *UI) Output(s string) {
fmt.Fprintln(u.Writer, s)
func (u *basicUI) Output(s string) {
fmt.Fprintln(u.writer, s)
}

// Info is the same as Output, but distinguish these for composition.
func (u *UI) Info(s string) {
func (u *basicUI) Info(s string) {
u.Output(s)
}

// Error writes out the passed argument s to ErrWriter with a line break.
func (u *UI) Error(s string) {
fmt.Fprintln(u.ErrWriter, s)
func (u *basicUI) Error(s string) {
fmt.Fprintln(u.errWriter, s)
}

// Writer returns an io.Writer which is used in u.
func (u *basicUI) Writer() io.Writer {
return u.writer
}

type coloredUI struct {
UI
}

// NewColored wraps provided `ui` with coloredUI.
// If `ui` is *coloredUI, NewColored returns it as it is.
// Colored output works fine in Windows environment.
func NewColored(ui UI) UI {
if ui, ok := ui.(*coloredUI); ok {
return ui
}
return &coloredUI{ui}
}

// Info is the same as New, but colored.
func (u *coloredUI) Info(s string) {
u.UI.Info(color.BlueString(s))
}

// DefaultUI returns the default UI.
func DefaultUI() *UI {
ui := defaultUI
return &ui
// Error is the same as New, but colored.
func (u *coloredUI) Error(s string) {
u.UI.Error(color.RedString(s))
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/pflag v1.0.3
github.com/spf13/viper v1.3.1
github.com/stretchr/testify v1.3.0
github.com/tj/go-spin v1.1.0
github.com/zchee/go-xdgbasedir v1.0.3
go.uber.org/goleak v0.10.0
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import (
"os"

"github.com/ktr0731/evans/app"
"github.com/ktr0731/evans/cui"
)

func main() {
os.Exit(app.New(nil).Run(os.Args[1:]))
os.Exit(app.New(cui.New()).Run(os.Args[1:]))
}
4 changes: 2 additions & 2 deletions mode/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
var DefaultCLIReader io.Reader = os.Stdin

// RunAsCLIMode starts Evans as CLI mode.
func RunAsCLIMode(cfg *config.Config, call, file string, ui *cui.UI) error {
func RunAsCLIMode(cfg *config.Config, call, file string, ui cui.UI) error {
if call == "" {
return errors.New("flag --call must not be empty")
}
Expand Down Expand Up @@ -89,7 +89,7 @@ func RunAsCLIMode(cfg *config.Config, call, file string, ui *cui.UI) error {
}
}

err = usecase.CallRPC(ctx, ui.Writer, call)
err = usecase.CallRPC(ctx, ui.Writer(), call)
if err != nil {
return errors.Wrapf(err, "failed to call RPC '%s'", call)
}
Expand Down
2 changes: 1 addition & 1 deletion mode/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/pkg/errors"
)

func RunAsREPLMode(cfg *config.Config, ui *cui.UI) error {
func RunAsREPLMode(cfg *config.Config, ui cui.UI) error {
var result error
gRPCClient, err := newGRPCClient(cfg)
if err != nil {
Expand Down
10 changes: 5 additions & 5 deletions repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type REPL struct {
cfg *config.REPL
serverCfg *config.Server
prompt prompt.Prompt
ui *cui.UI
ui cui.UI

cmds map[string]commander
aliases map[string]string
Expand All @@ -47,7 +47,7 @@ var commands = map[string]commander{

// New instantiates a new REPL instance. New always calls p.SetPrefix for display the server addr.
// New may return an error if some of passed arguments are invalid.
func New(cfg *config.Config, p prompt.Prompt, ui *cui.UI, pkgName, svcName string) (*REPL, error) {
func New(cfg *config.Config, p prompt.Prompt, ui cui.UI, pkgName, svcName string) (*REPL, error) {
cmds := commands
// Each value must be a key of cmds.
aliases := map[string]string{
Expand Down Expand Up @@ -126,7 +126,7 @@ func (r *REPL) cleanup(ctx context.Context) {

func (r *REPL) runCommand(cmdName string, args []string) error {
if cmdName == "help" {
fmt.Fprintln(r.ui.Writer, r.helpText())
r.ui.Output(r.helpText())
return nil
}

Expand All @@ -142,15 +142,15 @@ func (r *REPL) runCommand(cmdName string, args []string) error {

if len(args) != 0 {
if args[0] == "-h" || args[0] == "--help" {
fmt.Fprintln(r.ui.Writer, cmd.Help())
r.ui.Output(cmd.Help())
return nil
}
}

if err := cmd.Validate(args); err != nil {
return err
}
if err := cmd.Run(r.ui.Writer, args); err != nil {
if err := cmd.Run(r.ui.Writer(), args); err != nil {
return err
}

Expand Down

0 comments on commit 461f928

Please sign in to comment.