-
-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
215 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,67 @@ | ||
package ui | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/AlecAivazis/survey/v2" | ||
) | ||
|
||
func FileSelector(msg, descr string) (string, error) { | ||
var q = &survey.Input{ | ||
Message: msg, | ||
Suggest: func(partname string) []string { | ||
// thanks to AlecAivazis the for great example of this. | ||
files, _ := filepath.Glob(partname + "*") | ||
return files | ||
type fileSelectorOpt struct { | ||
emptyFilename string // if set, the empty filename will be replaced to this value | ||
} | ||
|
||
func WithEmptyFilename(s string) Option { | ||
return func(so *inputOptions) { | ||
so.fileSelectorOpt.emptyFilename = s | ||
} | ||
} | ||
|
||
func FileSelector(msg, descr string, opt ...Option) (string, error) { | ||
var opts = defaultOpts().apply(opt...) | ||
|
||
var q = []*survey.Question{ | ||
{ | ||
Name: "filename", | ||
Prompt: &survey.Input{ | ||
Message: msg, | ||
Suggest: func(partname string) []string { | ||
files, _ := filepath.Glob(partname + "*") | ||
return files | ||
}, | ||
Help: descr, | ||
}, | ||
Validate: func(ans interface{}) error { | ||
if ans.(string) != "" || opts.emptyFilename != "" { | ||
return nil | ||
} | ||
return errors.New("empty filename") | ||
}, | ||
}, | ||
Help: descr, | ||
} | ||
|
||
var ( | ||
output string | ||
) | ||
var resp struct { | ||
Filename string | ||
} | ||
for { | ||
if err := survey.AskOne(q, &output); err != nil { | ||
if err := survey.Ask(q, &resp, opts.surveyOpts()...); err != nil { | ||
return "", err | ||
} | ||
if _, err := os.Stat(output); err != nil { | ||
if resp.Filename == "" && opts.emptyFilename != "" { | ||
resp.Filename = opts.emptyFilename | ||
} | ||
if _, err := os.Stat(resp.Filename); err != nil { | ||
break | ||
} | ||
overwrite, err := Confirm(fmt.Sprintf("File %q exists. Overwrite?", output), false) | ||
overwrite, err := Confirm(fmt.Sprintf("File %q exists. Overwrite?", resp.Filename), false, opt...) | ||
if err != nil { | ||
return "", err | ||
} | ||
if overwrite { | ||
break | ||
} | ||
} | ||
if output == "" { | ||
output = "-" | ||
} | ||
return output, nil | ||
return resp.Filename, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package ui | ||
|
||
import ( | ||
"testing" | ||
"time" | ||
|
||
"github.com/AlecAivazis/survey/v2/core" | ||
"github.com/AlecAivazis/survey/v2/terminal" | ||
"github.com/Netflix/go-expect" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func init() { | ||
// disable color output for all prompts to simplify testing | ||
core.DisableColor = true | ||
} | ||
|
||
func TestFileselector(t *testing.T) { | ||
t.Run("default", func(t *testing.T) { | ||
var filename string | ||
testFn := func(stdio terminal.Stdio) error { | ||
var err error | ||
filename, err = FileSelector("xxx", "help", WithOutput(stdio)) | ||
return err | ||
} | ||
procedure := func(t *testing.T, console *expect.Console) { | ||
console.ExpectString("xxx") | ||
console.SendLine("test.txt") | ||
console.ExpectEOF() | ||
} | ||
RunTest(t, procedure, testFn) | ||
assert.Equal(t, "test.txt", filename) | ||
}) | ||
t.Run("empty with no override", func(t *testing.T) { | ||
var filename string | ||
testFn := func(stdio terminal.Stdio) error { | ||
var err error | ||
filename, err = FileSelector("xxx", "help", WithOutput(stdio)) | ||
return err | ||
} | ||
procedure := func(t *testing.T, console *expect.Console) { | ||
console.ExpectString("xxx") | ||
console.SendLine("") | ||
time.Sleep(10 * time.Millisecond) | ||
console.ExpectString("xxx") | ||
console.SendLine(":wq!") | ||
console.ExpectEOF() | ||
} | ||
RunTest(t, procedure, testFn) | ||
assert.Equal(t, ":wq!", filename) | ||
}) | ||
t.Run("empty with the override", func(t *testing.T) { | ||
var filename string | ||
testFn := func(stdio terminal.Stdio) error { | ||
var err error | ||
filename, err = FileSelector("xxx", "help", WithOutput(stdio), WithEmptyFilename("override")) | ||
return err | ||
} | ||
procedure := func(t *testing.T, console *expect.Console) { | ||
console.ExpectString("xxx") | ||
console.SendLine("") | ||
console.ExpectEOF() | ||
} | ||
RunTest(t, procedure, testFn) | ||
assert.Equal(t, "override", filename) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,46 @@ | ||
// Package ui contains some common UI elements, that use Survey library. | ||
package ui | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/AlecAivazis/survey/v2" | ||
"github.com/AlecAivazis/survey/v2/terminal" | ||
) | ||
|
||
type inputOptions struct { | ||
stdio terminal.Stdio | ||
fileSelectorOpt | ||
} | ||
|
||
// surveyOpts returns the survey options. | ||
func (io *inputOptions) surveyOpts() []survey.AskOpt { | ||
return []survey.AskOpt{ | ||
survey.WithStdio(io.stdio.In, io.stdio.Out, io.stdio.Err), | ||
} | ||
} | ||
|
||
func (io *inputOptions) apply(opt ...Option) *inputOptions { | ||
for _, fn := range opt { | ||
fn(io) | ||
} | ||
return io | ||
} | ||
|
||
type Option func(*inputOptions) | ||
|
||
func defaultOpts() *inputOptions { | ||
return &inputOptions{ | ||
stdio: terminal.Stdio{ | ||
In: os.Stdin, | ||
Out: os.Stdout, | ||
Err: os.Stderr, | ||
}, | ||
} | ||
} | ||
|
||
func WithOutput(stdio terminal.Stdio) Option { | ||
return func(io *inputOptions) { | ||
io.stdio = stdio | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package ui | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/AlecAivazis/survey/v2/terminal" | ||
"github.com/Netflix/go-expect" | ||
"github.com/hinshun/vt10x" | ||
"github.com/kr/pty" | ||
) | ||
|
||
type ( | ||
procedureFunc func(*testing.T, *expect.Console) | ||
testFunc func(terminal.Stdio) error | ||
) | ||
|
||
// RunTest is the helper function to execute the UI tests. | ||
// | ||
// it's a simplified copy/paste from the survey lib: | ||
// | ||
// https://github.com/go-survey/survey/blob/master/survey_posix_test.go | ||
func RunTest(t *testing.T, procedure func(*testing.T, *expect.Console), test func(terminal.Stdio) error) { | ||
t.Helper() | ||
|
||
pty, tty, err := pty.Open() | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
term := vt10x.New(vt10x.WithWriter(tty)) | ||
console, err := expect.NewConsole(expect.WithStdin(pty), expect.WithStdout(term), expect.WithCloser(pty, tty)) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer console.Close() | ||
|
||
done := make(chan struct{}) | ||
go func() { | ||
defer close(done) | ||
procedure(t, console) | ||
}() | ||
stdio := terminal.Stdio{In: console.Tty(), Out: console.Tty(), Err: console.Tty()} | ||
if err := test(stdio); err != nil { | ||
t.Error(err) | ||
} | ||
if err := console.Tty().Close(); err != nil { | ||
t.Errorf("error closing tty: %s", err) | ||
} | ||
<-done | ||
} |