mirror of
https://github.com/Threnklyn/jira.git
synced 2026-05-18 20:23:28 +02:00
update for github.com/AlecAivazis/survey => gopkg.in/AlecAivazis/survey.v1 package
This commit is contained in:
-23
@@ -1,23 +0,0 @@
|
||||
autoplay-tests:
|
||||
summary: Replaying interactive tests
|
||||
command: |-
|
||||
cd tests
|
||||
set -e
|
||||
for test in autoplay/*.go; do
|
||||
echo "==> Running $test"
|
||||
go run $test
|
||||
done
|
||||
|
||||
install-deps:
|
||||
summary: Install all of package dependencies
|
||||
command: |-
|
||||
go get -t {{.files}}
|
||||
# for autoplay tests
|
||||
go get github.com/kr/pty
|
||||
|
||||
tests:
|
||||
summary: Run the test suite
|
||||
command: go test {{.files}}
|
||||
|
||||
variables:
|
||||
files: '$(go list -v ./... | grep -iEv "github.com/AlecAivazis/survey/(tests|examples)")'
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
language: go
|
||||
|
||||
before_install:
|
||||
- go get github.com/AlecAivazis/run
|
||||
|
||||
install:
|
||||
- run install-deps
|
||||
|
||||
script:
|
||||
- run tests
|
||||
# - run autoplay-tests
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Alec Aivazis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
-291
@@ -1,291 +0,0 @@
|
||||
# Survey
|
||||
[](https://travis-ci.org/AlecAivazis/survey)
|
||||
[](https://godoc.org/github.com/AlecAivazis/survey)
|
||||
|
||||
A library for building interactive prompts. Heavily inspired by the great [inquirer.js](https://github.com/SBoudrias/Inquirer.js/).
|
||||
|
||||

|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var qs = []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{Message: "What is your name?"},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
{
|
||||
Name: "color",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
Default: "red",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "age",
|
||||
Prompt: &survey.Input{Message: "How old are you?"},
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
// the answers will be written to this struct
|
||||
answers := struct {
|
||||
Name string // survey will match the question and field names
|
||||
FavoriteColor string `survey:"color"` // or you can tag fields to match a specific name
|
||||
Age int // if the types don't match exactly, survey will try to convert for you
|
||||
}{}
|
||||
|
||||
// perform the questions
|
||||
err := survey.Ask(qs, &answers)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s chose %s.", answers.Name, answers.FavoriteColor)
|
||||
}
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Examples](#examples)
|
||||
1. [Prompts](#prompts)
|
||||
1. [Input](#input)
|
||||
1. [Password](#password)
|
||||
1. [Confirm](#confirm)
|
||||
1. [Select](#select)
|
||||
1. [MultiSelect](#multiselect)
|
||||
1. [Validation](#validation)
|
||||
1. [Built-in Validators](#built-in-validators)
|
||||
1. [Help Text](#help-text)
|
||||
1. [Changing the input rune](#changing-the-input-run)
|
||||
1. [Custom Types](#custom-types)
|
||||
1. [Customizing Output](#customizing-output)
|
||||
1. [Versioning](#versioning)
|
||||
|
||||
## Examples
|
||||
|
||||
Examples can be found in the `examples/` directory. Run them
|
||||
to see basic behavior:
|
||||
|
||||
```bash
|
||||
go get github.com/AlecAivazis/survey
|
||||
|
||||
# ... navigate to the repo in your GOPATH
|
||||
|
||||
go run examples/simple.go
|
||||
go run examples/validation.go
|
||||
```
|
||||
|
||||
## Prompts
|
||||
|
||||
### Input
|
||||
|
||||
<img src="https://media.giphy.com/media/3og0IxS8JsuD9Z8syA/giphy.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
name := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "ping",
|
||||
}
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
```
|
||||
|
||||
|
||||
### Password
|
||||
|
||||
<img src="https://media.giphy.com/media/26FmQr6mUivkq71GE/giphy.gif" width="400px" />
|
||||
|
||||
```golang
|
||||
password := ""
|
||||
prompt := &survey.Password{
|
||||
Message: "Please type your password",
|
||||
}
|
||||
survey.AskOne(prompt, &password, nil)
|
||||
```
|
||||
|
||||
|
||||
### Confirm
|
||||
|
||||
<img src="https://media.giphy.com/media/3oKIPgsUmTp4m3eo4E/giphy.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
name := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: "Do you like pie?",
|
||||
}
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
```
|
||||
|
||||
|
||||
### Select
|
||||
|
||||
<img src="https://media.giphy.com/media/3oKIPxigmMu5YqpUPK/giphy.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
color := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
}
|
||||
survey.AskOne(prompt, &color, nil)
|
||||
```
|
||||
|
||||
By default, the select prompt is limited to showing 7 options at a time
|
||||
and will paginate lists of options longer than that. To increase, you can either
|
||||
change the global `survey.PageCount`, or set the `PageSize` field on the prompt:
|
||||
|
||||
```golang
|
||||
prompt := &survey.Select{..., PageSize: 10}
|
||||
```
|
||||
|
||||
### MultiSelect
|
||||
|
||||
<img src="https://media.giphy.com/media/3oKIP8lHYFtGeQDH0c/giphy.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
days := []string{}
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
}
|
||||
survey.AskOne(prompt, &days, nil)
|
||||
```
|
||||
|
||||
By default, the MultiSelect prompt is limited to showing 7 options at a time
|
||||
and will paginate lists of options longer than that. To increase, you can either
|
||||
change the global `survey.PageCount`, or set the `PageSize` field on the prompt:
|
||||
|
||||
```golang
|
||||
prompt := &survey.MultiSelect{..., PageSize: 10}
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
Validating individual responses for a particular question can be done by defining a
|
||||
`Validate` field on the `survey.Question` to be validated. This function takes an
|
||||
`interface{}` type and returns an error to show to the user, prompting them for another
|
||||
response:
|
||||
|
||||
```golang
|
||||
q := &survey.Question{
|
||||
Prompt: &survey.Input{Message: "Hello world validation"},
|
||||
Validate: func (val interface{}) error {
|
||||
// since we are validating an Input, the assertion will always succeed
|
||||
if str, ok := val.(string) ; ok && len(str) > 10 {
|
||||
return errors.New("This response cannot be longer than 10 characters.")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Built-in Validators
|
||||
|
||||
`survey` comes prepackaged with a few validators to fit common situations. Currently these
|
||||
validators include:
|
||||
|
||||
| name | valid types | description |
|
||||
|--------------|-----------------|---------------------------------------------------------------|
|
||||
| Required | any | Rejects zero values of the response type |
|
||||
| MinLength(n) | string | Enforces that a response is at least the given length |
|
||||
| MaxLength(n) | string | Enforces that a response is no longer than the given length |
|
||||
|
||||
## Help Text
|
||||
|
||||
All of the prompts have a `Help` field which can be defined to provide more information to your users:
|
||||
|
||||
<img src="https://media.giphy.com/media/l1KVbc5CehW6r7pss/giphy.gif" width="400px" style="margin-top: 8px"/>
|
||||
|
||||
```golang
|
||||
&survey.Input{
|
||||
Message: "What is your phone number:",
|
||||
Help: "Phone number should include the area code",
|
||||
}
|
||||
```
|
||||
|
||||
### Changing the input rune
|
||||
|
||||
In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
|
||||
looks for by setting the `HelpInputRune` variable in `survey/core`:
|
||||
|
||||
```golang
|
||||
|
||||
import (
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
surveyCore "gopkg.in/AlecAivazis/survey.v1/core"
|
||||
)
|
||||
|
||||
number := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "If you have this need, please give me a reasonable message.",
|
||||
Help: "I couldn't come up with one.",
|
||||
}
|
||||
|
||||
surveyCore.HelpInputRune = '^'
|
||||
|
||||
survey.AskOne(prompt, &number, nil)
|
||||
```
|
||||
|
||||
## Custom Types
|
||||
|
||||
survey will assign prompt answers to your custom types if they implement this interface:
|
||||
|
||||
```golang
|
||||
type settable interface {
|
||||
WriteAnswer(field string, value interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example how to use them:
|
||||
|
||||
```golang
|
||||
type MyValue struct {
|
||||
value string
|
||||
}
|
||||
func (my *MyValue) WriteAnswer(name string, value interface{}) error {
|
||||
my.value = value.(string)
|
||||
}
|
||||
|
||||
myval := MyValue{}
|
||||
survey.AskOne(
|
||||
&survey.Input{
|
||||
Message: "Enter something:",
|
||||
},
|
||||
&myval,
|
||||
nil,
|
||||
)
|
||||
```
|
||||
|
||||
## Customizing Output
|
||||
|
||||
Customizing the icons and various parts of survey can easily be done by setting the following variables
|
||||
in `survey/core`:
|
||||
|
||||
| name | default | description |
|
||||
|---------------------|----------------|-------------------------------------------------------------------|
|
||||
| ErrorIcon | ✘ | Before an error |
|
||||
| HelpIcon | ⓘ | Before help text |
|
||||
| QuestionIcon | ? | Before the message of a prompt |
|
||||
| SelectFocusIcon | ❯ | Marks the current focus in `Select` and `MultiSelect` prompts |
|
||||
| MarkedOptionIcon | ◉ | Marks a chosen selection in a `MultiSelect` prompt |
|
||||
| UnmarkedOptionIcon | ◯ | Marks an unselected option in a `MultiSelect` prompt |
|
||||
|
||||
## Versioning
|
||||
|
||||
This project tries to maintain semantic GitHub releases as closely as possible. As such, services
|
||||
like [gopkg.in](http://labix.org/gopkg.in) work very well to ensure non-breaking changes whenever
|
||||
you build your application. For example, importing v1 of survey could look something like
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import "gopkg.in/AlecAivazis/survey.v1"
|
||||
```
|
||||
-138
@@ -1,138 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
)
|
||||
|
||||
// Confirm is a regular text input that accept yes/no answers. Response type is a bool.
|
||||
type Confirm struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Default bool
|
||||
Help string
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type ConfirmTemplateData struct {
|
||||
Confirm
|
||||
Answer string
|
||||
ShowHelp bool
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var ConfirmQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .Answer}}
|
||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}
|
||||
{{- color "white"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}}
|
||||
{{- end}}`
|
||||
|
||||
// the regex for answers
|
||||
var (
|
||||
yesRx = regexp.MustCompile("^(?i:y(?:es)?)$")
|
||||
noRx = regexp.MustCompile("^(?i:n(?:o)?)$")
|
||||
)
|
||||
|
||||
func yesNo(t bool) string {
|
||||
if t {
|
||||
return "Yes"
|
||||
}
|
||||
return "No"
|
||||
}
|
||||
|
||||
func (c *Confirm) getBool(showHelp bool) (bool, error) {
|
||||
rr := terminal.NewRuneReader(os.Stdin)
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
// start waiting for input
|
||||
for {
|
||||
line, err := rr.ReadLine(0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// move back up a line to compensate for the \n echoed from terminal
|
||||
terminal.CursorPreviousLine(1)
|
||||
val := string(line)
|
||||
|
||||
// get the answer that matches the
|
||||
var answer bool
|
||||
switch {
|
||||
case yesRx.Match([]byte(val)):
|
||||
answer = true
|
||||
case noRx.Match([]byte(val)):
|
||||
answer = false
|
||||
case val == "":
|
||||
answer = c.Default
|
||||
case val == string(core.HelpInputRune) && c.Help != "":
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c, ShowHelp: true},
|
||||
)
|
||||
if err != nil {
|
||||
// use the default value and bubble up
|
||||
return c.Default, err
|
||||
}
|
||||
showHelp = true
|
||||
continue
|
||||
default:
|
||||
// we didnt get a valid answer, so print error and prompt again
|
||||
if err := c.Error(fmt.Errorf("%q is not a valid answer, please try again.", val)); err != nil {
|
||||
return c.Default, err
|
||||
}
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c, ShowHelp: showHelp},
|
||||
)
|
||||
if err != nil {
|
||||
// use the default value and bubble up
|
||||
return c.Default, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
// should not get here
|
||||
return c.Default, nil
|
||||
}
|
||||
|
||||
/*
|
||||
Prompt prompts the user with a simple text field and expects a reply followed
|
||||
by a carriage return.
|
||||
|
||||
likesPie := false
|
||||
prompt := &survey.Confirm{ Message: "What is your name?" }
|
||||
survey.AskOne(prompt, &likesPie, nil)
|
||||
*/
|
||||
func (c *Confirm) Prompt() (interface{}, error) {
|
||||
// render the question template
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// get input and return
|
||||
return c.getBool(false)
|
||||
}
|
||||
|
||||
// Cleanup overwrite the line with the finalized formatted version
|
||||
func (c *Confirm) Cleanup(val interface{}) error {
|
||||
// if the value was previously true
|
||||
ans := yesNo(val.(bool))
|
||||
// render the template
|
||||
return c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c, Answer: ans},
|
||||
)
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestConfirmRender(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
title string
|
||||
prompt Confirm
|
||||
data ConfirmTemplateData
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"Test Confirm question output with default true",
|
||||
Confirm{Message: "Is pizza your favorite food?", Default: true},
|
||||
ConfirmTemplateData{},
|
||||
`? Is pizza your favorite food? (Y/n) `,
|
||||
},
|
||||
{
|
||||
"Test Confirm question output with default false",
|
||||
Confirm{Message: "Is pizza your favorite food?", Default: false},
|
||||
ConfirmTemplateData{},
|
||||
`? Is pizza your favorite food? (y/N) `,
|
||||
},
|
||||
{
|
||||
"Test Confirm answer output",
|
||||
Confirm{Message: "Is pizza your favorite food?"},
|
||||
ConfirmTemplateData{Answer: "Yes"},
|
||||
"? Is pizza your favorite food? Yes\n",
|
||||
},
|
||||
{
|
||||
"Test Confirm with help but help message is hidden",
|
||||
Confirm{Message: "Is pizza your favorite food?", Help: "This is helpful"},
|
||||
ConfirmTemplateData{},
|
||||
"? Is pizza your favorite food? [? for help] (y/N) ",
|
||||
},
|
||||
{
|
||||
"Test Confirm help output with help message shown",
|
||||
Confirm{Message: "Is pizza your favorite food?", Help: "This is helpful"},
|
||||
ConfirmTemplateData{ShowHelp: true},
|
||||
`ⓘ This is helpful
|
||||
? Is pizza your favorite food? (y/N) `,
|
||||
},
|
||||
}
|
||||
|
||||
outputBuffer := bytes.NewBufferString("")
|
||||
terminal.Stdout = outputBuffer
|
||||
|
||||
for _, test := range tests {
|
||||
outputBuffer.Reset()
|
||||
test.data.Confirm = test.prompt
|
||||
err := test.prompt.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
test.data,
|
||||
)
|
||||
assert.Nil(t, err, test.title)
|
||||
assert.Equal(t, test.expected, outputBuffer.String(), test.title)
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
lineCount int
|
||||
errorLineCount int
|
||||
}
|
||||
|
||||
var ErrorTemplate = `{{color "red"}}{{ ErrorIcon }} Sorry, your reply was invalid: {{.Error}}{{color "reset"}}
|
||||
`
|
||||
|
||||
func (r *Renderer) Error(invalid error) error {
|
||||
// since errors are printed on top we need to reset the prompt
|
||||
// as well as any previous error print
|
||||
r.resetPrompt(r.lineCount + r.errorLineCount)
|
||||
// we just cleared the prompt lines
|
||||
r.lineCount = 0
|
||||
out, err := RunTemplate(ErrorTemplate, invalid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// keep track of how many lines are printed so we can clean up later
|
||||
r.errorLineCount = strings.Count(out, "\n")
|
||||
|
||||
// send the message to the user
|
||||
terminal.Print(out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) resetPrompt(lines int) {
|
||||
// clean out current line in case tmpl didnt end in newline
|
||||
terminal.CursorHorizontalAbsolute(0)
|
||||
terminal.EraseLine(terminal.ERASE_LINE_ALL)
|
||||
// clean up what we left behind last time
|
||||
for i := 0; i < lines; i++ {
|
||||
terminal.CursorPreviousLine(1)
|
||||
terminal.EraseLine(terminal.ERASE_LINE_ALL)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) Render(tmpl string, data interface{}) error {
|
||||
r.resetPrompt(r.lineCount)
|
||||
// render the template summarizing the current state
|
||||
out, err := RunTemplate(tmpl, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// keep track of how many lines are printed so we can clean up later
|
||||
r.lineCount = strings.Count(out, "\n")
|
||||
|
||||
// print the summary
|
||||
terminal.Print(out)
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"text/template"
|
||||
|
||||
"github.com/mgutz/ansi"
|
||||
)
|
||||
|
||||
var DisableColor = false
|
||||
|
||||
var (
|
||||
HelpInputRune = '?'
|
||||
|
||||
ErrorIcon = "✘"
|
||||
HelpIcon = "ⓘ"
|
||||
QuestionIcon = "?"
|
||||
|
||||
MarkedOptionIcon = "◉"
|
||||
UnmarkedOptionIcon = "◯"
|
||||
|
||||
SelectFocusIcon = "❯"
|
||||
)
|
||||
|
||||
var TemplateFuncs = map[string]interface{}{
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
"color": func(color string) string {
|
||||
if DisableColor {
|
||||
return ""
|
||||
}
|
||||
return ansi.ColorCode(color)
|
||||
},
|
||||
"HelpInputRune": func() string {
|
||||
return string(HelpInputRune)
|
||||
},
|
||||
"ErrorIcon": func() string {
|
||||
return ErrorIcon
|
||||
},
|
||||
"HelpIcon": func() string {
|
||||
return HelpIcon
|
||||
},
|
||||
"QuestionIcon": func() string {
|
||||
return QuestionIcon
|
||||
},
|
||||
"MarkedOptionIcon": func() string {
|
||||
return MarkedOptionIcon
|
||||
},
|
||||
"UnmarkedOptionIcon": func() string {
|
||||
return UnmarkedOptionIcon
|
||||
},
|
||||
"SelectFocusIcon": func() string {
|
||||
return SelectFocusIcon
|
||||
},
|
||||
}
|
||||
|
||||
var memoizedGetTemplate = map[string]*template.Template{}
|
||||
|
||||
func getTemplate(tmpl string) (*template.Template, error) {
|
||||
if t, ok := memoizedGetTemplate[tmpl]; ok {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
t, err := template.New("prompt").Funcs(TemplateFuncs).Parse(tmpl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memoizedGetTemplate[tmpl] = t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func RunTemplate(tmpl string, data interface{}) (string, error) {
|
||||
t, err := getTemplate(tmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := bytes.NewBufferString("")
|
||||
err = t.Execute(buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), err
|
||||
}
|
||||
-209
@@ -1,209 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// the tag used to denote the name of the question
|
||||
const tagName = "survey"
|
||||
|
||||
// add a few interfaces so users can configure how the prompt values are set
|
||||
type settable interface {
|
||||
WriteAnswer(field string, value interface{}) error
|
||||
}
|
||||
|
||||
func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
||||
// if the field is a custom type
|
||||
if s, ok := t.(settable); ok {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
|
||||
// the target to write to
|
||||
target := reflect.ValueOf(t)
|
||||
// the value to write from
|
||||
value := reflect.ValueOf(v)
|
||||
|
||||
// make sure we are writing to a pointer
|
||||
if target.Kind() != reflect.Ptr {
|
||||
return errors.New("you must pass a pointer as the target of a Write operation")
|
||||
}
|
||||
// the object "inside" of the target pointer
|
||||
elem := target.Elem()
|
||||
|
||||
// handle the special types
|
||||
switch elem.Kind() {
|
||||
// if we are writing to a struct
|
||||
case reflect.Struct:
|
||||
// get the name of the field that matches the string we were given
|
||||
fieldIndex, err := findFieldIndex(elem, name)
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// bubble up
|
||||
return err
|
||||
}
|
||||
field := elem.Field(fieldIndex)
|
||||
// handle references to the settable interface aswell
|
||||
if s, ok := field.Interface().(settable); ok {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
if field.CanAddr() {
|
||||
if s, ok := field.Addr().Interface().(settable); ok {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
}
|
||||
|
||||
// copy the value over to the normal struct
|
||||
return copy(field, value)
|
||||
case reflect.Map:
|
||||
mapType := reflect.TypeOf(t).Elem()
|
||||
if mapType.Key().Kind() != reflect.String || mapType.Elem().Kind() != reflect.Interface {
|
||||
return errors.New("answer maps must be of type map[string]interface")
|
||||
}
|
||||
mt := *t.(*map[string]interface{})
|
||||
mt[name] = value.Interface()
|
||||
return nil
|
||||
}
|
||||
// otherwise just copy the value to the target
|
||||
return copy(elem, value)
|
||||
}
|
||||
|
||||
// BUG(AlecAivazis): the current implementation might cause weird conflicts if there are
|
||||
// two fields with same name that only differ by casing.
|
||||
func findFieldIndex(s reflect.Value, name string) (int, error) {
|
||||
// the type of the value
|
||||
sType := s.Type()
|
||||
|
||||
// first look for matching tags so we can overwrite matching field names
|
||||
for i := 0; i < sType.NumField(); i++ {
|
||||
// the field we are current scanning
|
||||
field := sType.Field(i)
|
||||
|
||||
// the value of the survey tag
|
||||
tag := field.Tag.Get(tagName)
|
||||
// if the tag matches the name we are looking for
|
||||
if tag != "" && tag == name {
|
||||
// then we found our index
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
// then look for matching names
|
||||
for i := 0; i < sType.NumField(); i++ {
|
||||
// the field we are current scanning
|
||||
field := sType.Field(i)
|
||||
|
||||
// if the name of the field matches what we're looking for
|
||||
if strings.ToLower(field.Name) == strings.ToLower(name) {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
|
||||
// we didn't find the field
|
||||
return -1, fmt.Errorf("could not find field matching %v", name)
|
||||
}
|
||||
|
||||
// Write takes a value and copies it to the target
|
||||
func copy(t reflect.Value, v reflect.Value) (err error) {
|
||||
// if something ends up panicing we need to catch it in a deferred func
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// if we paniced with an error
|
||||
if _, ok := r.(error); ok {
|
||||
// cast the result to an error object
|
||||
err = r.(error)
|
||||
} else if _, ok := r.(string); ok {
|
||||
// otherwise we could have paniced with a string so wrap it in an error
|
||||
err = errors.New(r.(string))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// attempt to copy the underlying value to the target
|
||||
if v.Kind() == reflect.String && v.Type() != t.Type() {
|
||||
var castVal interface{}
|
||||
var casterr error
|
||||
vString := v.Interface().(string)
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
castVal, casterr = strconv.ParseBool(vString)
|
||||
case reflect.Int:
|
||||
castVal, casterr = strconv.Atoi(vString)
|
||||
case reflect.Int8:
|
||||
var val64 int64
|
||||
val64, casterr = strconv.ParseInt(vString, 10, 8)
|
||||
if casterr == nil {
|
||||
castVal = int8(val64)
|
||||
}
|
||||
case reflect.Int16:
|
||||
var val64 int64
|
||||
val64, casterr = strconv.ParseInt(vString, 10, 16)
|
||||
if casterr == nil {
|
||||
castVal = int16(val64)
|
||||
}
|
||||
case reflect.Int32:
|
||||
var val64 int64
|
||||
val64, casterr = strconv.ParseInt(vString, 10, 32)
|
||||
if casterr == nil {
|
||||
castVal = int32(val64)
|
||||
}
|
||||
case reflect.Int64:
|
||||
castVal, casterr = strconv.ParseInt(vString, 10, 64)
|
||||
case reflect.Uint:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
||||
if casterr == nil {
|
||||
castVal = uint(val64)
|
||||
}
|
||||
case reflect.Uint8:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
||||
if casterr == nil {
|
||||
castVal = uint8(val64)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 16)
|
||||
if casterr == nil {
|
||||
castVal = uint16(val64)
|
||||
}
|
||||
case reflect.Uint32:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 32)
|
||||
if casterr == nil {
|
||||
castVal = uint32(val64)
|
||||
}
|
||||
case reflect.Uint64:
|
||||
castVal, casterr = strconv.ParseUint(vString, 10, 64)
|
||||
case reflect.Float32:
|
||||
var val64 float64
|
||||
val64, casterr = strconv.ParseFloat(vString, 32)
|
||||
if casterr == nil {
|
||||
castVal = float32(val64)
|
||||
}
|
||||
case reflect.Float64:
|
||||
castVal, casterr = strconv.ParseFloat(vString, 64)
|
||||
default:
|
||||
return fmt.Errorf("Unable to convert from string to type %s", t.Kind())
|
||||
}
|
||||
|
||||
if casterr != nil {
|
||||
return casterr
|
||||
}
|
||||
|
||||
t.Set(reflect.ValueOf(castVal))
|
||||
return
|
||||
}
|
||||
|
||||
t.Set(v)
|
||||
|
||||
// we're done
|
||||
return
|
||||
}
|
||||
-534
@@ -1,534 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWrite_returnsErrorIfTargetNotPtr(t *testing.T) {
|
||||
// try to copy a value to a non-pointer
|
||||
err := WriteAnswer(true, "hello", true)
|
||||
// make sure there was an error
|
||||
if err == nil {
|
||||
t.Error("Did not encounter error when writing to non-pointer.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canWriteToBool(t *testing.T) {
|
||||
// a pointer to hold the boolean value
|
||||
ptr := true
|
||||
|
||||
// try to copy a false value to the pointer
|
||||
WriteAnswer(&ptr, "", false)
|
||||
|
||||
// if the value is true
|
||||
if ptr {
|
||||
// the test failed
|
||||
t.Error("Could not write a false bool to a pointer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canWriteString(t *testing.T) {
|
||||
// a pointer to hold the boolean value
|
||||
ptr := ""
|
||||
|
||||
// try to copy a false value to the pointer
|
||||
err := WriteAnswer(&ptr, "", "hello")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// if the value is not what we wrote
|
||||
if ptr != "hello" {
|
||||
t.Error("Could not write a string value to a pointer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canWriteSlice(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
ptr := []string{}
|
||||
|
||||
// copy in a value
|
||||
WriteAnswer(&ptr, "", []string{"hello", "world"})
|
||||
|
||||
// make sure there are two entries
|
||||
if len(ptr) != 2 {
|
||||
// the test failed
|
||||
t.Errorf("Incorrect number of entries in written list. Expected 2, found %v.", len(ptr))
|
||||
// dont move on
|
||||
return
|
||||
}
|
||||
|
||||
// make sure the first entry is hello
|
||||
if ptr[0] != "hello" {
|
||||
// the test failed
|
||||
t.Errorf("incorrect first value in written pointer. expected hello found %v.", ptr[0])
|
||||
}
|
||||
|
||||
// make sure the second entry is world
|
||||
if ptr[1] != "world" {
|
||||
// the test failed
|
||||
t.Errorf("incorrect second value in written pointer. expected world found %v.", ptr[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_recoversInvalidReflection(t *testing.T) {
|
||||
// a variable to mutate
|
||||
ptr := false
|
||||
|
||||
// write a boolean value to the string
|
||||
err := WriteAnswer(&ptr, "", "hello")
|
||||
|
||||
// if there was no error
|
||||
if err == nil {
|
||||
// the test failed
|
||||
t.Error("Did not encounter error when forced invalid write.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteAnswer_handlesNonStructValues(t *testing.T) {
|
||||
// the value to write to
|
||||
ptr := ""
|
||||
|
||||
// write a value to the pointer
|
||||
WriteAnswer(&ptr, "", "world")
|
||||
|
||||
// if we didn't change the value appropriate
|
||||
if ptr != "world" {
|
||||
// the test failed
|
||||
t.Error("Did not write value to primitive pointer")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteAnswer_canMutateStruct(t *testing.T) {
|
||||
// the struct to hold the answer
|
||||
ptr := struct{ Name string }{}
|
||||
|
||||
// write a value to an existing field
|
||||
err := WriteAnswer(&ptr, "name", "world")
|
||||
if err != nil {
|
||||
// the test failed
|
||||
t.Errorf("Encountered error while writing answer: %v", err.Error())
|
||||
// we're done here
|
||||
return
|
||||
}
|
||||
|
||||
// make sure we changed the field
|
||||
if ptr.Name != "world" {
|
||||
// the test failed
|
||||
t.Error("Did not mutate struct field when writing answer.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteAnswer_canMutateMap(t *testing.T) {
|
||||
// the map to hold the answer
|
||||
ptr := make(map[string]interface{})
|
||||
|
||||
// write a value to an existing field
|
||||
err := WriteAnswer(&ptr, "name", "world")
|
||||
if err != nil {
|
||||
// the test failed
|
||||
t.Errorf("Encountered error while writing answer: %v", err.Error())
|
||||
// we're done here
|
||||
return
|
||||
}
|
||||
|
||||
// make sure we changed the field
|
||||
if ptr["name"] != "world" {
|
||||
// the test failed
|
||||
t.Error("Did not mutate map when writing answer.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_returnsErrorIfInvalidMapType(t *testing.T) {
|
||||
// try to copy a value to a non map[string]interface{}
|
||||
ptr := make(map[int]string)
|
||||
|
||||
err := WriteAnswer(&ptr, "name", "world")
|
||||
// make sure there was an error
|
||||
if err == nil {
|
||||
t.Error("Did not encounter error when writing to invalid map.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteAnswer_returnsErrWhenFieldNotFound(t *testing.T) {
|
||||
// the struct to hold the answer
|
||||
ptr := struct{ Name string }{}
|
||||
|
||||
// write a value to an existing field
|
||||
err := WriteAnswer(&ptr, "", "world")
|
||||
|
||||
if err == nil {
|
||||
// the test failed
|
||||
t.Error("Did not encountered error while writing answer to non-existing field.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindFieldIndex_canFindExportedField(t *testing.T) {
|
||||
// create a reflective wrapper over the struct to look through
|
||||
val := reflect.ValueOf(struct{ Name string }{})
|
||||
|
||||
// find the field matching "name"
|
||||
fieldIndex, err := findFieldIndex(val, "name")
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// the test failed
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// make sure we got the right value
|
||||
if val.Type().Field(fieldIndex).Name != "Name" {
|
||||
// the test failed
|
||||
t.Errorf("Did not find the correct field name. Expected 'Name' found %v.", val.Type().Field(fieldIndex).Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindFieldIndex_canFindTaggedField(t *testing.T) {
|
||||
// the struct to look through
|
||||
val := reflect.ValueOf(struct {
|
||||
Username string `survey:"name"`
|
||||
}{})
|
||||
|
||||
// find the field matching "name"
|
||||
fieldIndex, err := findFieldIndex(val, "name")
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// the test failed
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// make sure we got the right value
|
||||
if val.Type().Field(fieldIndex).Name != "Username" {
|
||||
// the test failed
|
||||
t.Errorf("Did not find the correct field name. Expected 'Username' found %v.", val.Type().Field(fieldIndex).Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindFieldIndex_canHandleCapitalAnswerNames(t *testing.T) {
|
||||
// create a reflective wrapper over the struct to look through
|
||||
val := reflect.ValueOf(struct{ Name string }{})
|
||||
|
||||
// find the field matching "name"
|
||||
fieldIndex, err := findFieldIndex(val, "Name")
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// the test failed
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// make sure we got the right value
|
||||
if val.Type().Field(fieldIndex).Name != "Name" {
|
||||
// the test failed
|
||||
t.Errorf("Did not find the correct field name. Expected 'Name' found %v.", val.Type().Field(fieldIndex).Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindFieldIndex_tagOverwriteFieldName(t *testing.T) {
|
||||
// the struct to look through
|
||||
val := reflect.ValueOf(struct {
|
||||
Name string
|
||||
Username string `survey:"name"`
|
||||
}{})
|
||||
|
||||
// find the field matching "name"
|
||||
fieldIndex, err := findFieldIndex(val, "name")
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// the test failed
|
||||
t.Error(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// make sure we got the right value
|
||||
if val.Type().Field(fieldIndex).Name != "Username" {
|
||||
// the test failed
|
||||
t.Errorf("Did not find the correct field name. Expected 'Username' found %v.", val.Type().Field(fieldIndex).Name)
|
||||
}
|
||||
}
|
||||
|
||||
type testFieldSettable struct {
|
||||
Values map[string]string
|
||||
}
|
||||
|
||||
type testStringSettable struct {
|
||||
Value string `survey:"string"`
|
||||
}
|
||||
|
||||
type testTaggedStruct struct {
|
||||
TaggedValue testStringSettable `survey:"tagged"`
|
||||
}
|
||||
|
||||
type testPtrTaggedStruct struct {
|
||||
TaggedValue *testStringSettable `survey:"tagged"`
|
||||
}
|
||||
|
||||
func (t *testFieldSettable) WriteAnswer(name string, value interface{}) error {
|
||||
if t.Values == nil {
|
||||
t.Values = map[string]string{}
|
||||
}
|
||||
if v, ok := value.(string); ok {
|
||||
t.Values[name] = v
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Incompatible type %T", value)
|
||||
}
|
||||
|
||||
func (t *testStringSettable) WriteAnswer(_ string, value interface{}) error {
|
||||
t.Value = value.(string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWriteWithFieldSettable(t *testing.T) {
|
||||
testSet1 := testFieldSettable{}
|
||||
err := WriteAnswer(&testSet1, "values", "stringVal")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, map[string]string{"values": "stringVal"}, testSet1.Values)
|
||||
|
||||
testSet2 := testFieldSettable{}
|
||||
err = WriteAnswer(&testSet2, "values", 123)
|
||||
assert.Error(t, fmt.Errorf("Incompatible type int64"), err)
|
||||
assert.Equal(t, map[string]string{}, testSet2.Values)
|
||||
|
||||
testString1 := testStringSettable{}
|
||||
err = WriteAnswer(&testString1, "", "value1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testStringSettable{"value1"}, testString1)
|
||||
|
||||
testSetStruct := testTaggedStruct{}
|
||||
err = WriteAnswer(&testSetStruct, "tagged", "stringVal1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testTaggedStruct{TaggedValue: testStringSettable{"stringVal1"}}, testSetStruct)
|
||||
|
||||
testPtrSetStruct := testPtrTaggedStruct{&testStringSettable{}}
|
||||
err = WriteAnswer(&testPtrSetStruct, "tagged", "stringVal1")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, testPtrTaggedStruct{TaggedValue: &testStringSettable{"stringVal1"}}, testPtrSetStruct)
|
||||
}
|
||||
|
||||
// CONVERSION TESTS
|
||||
func TestWrite_canStringToBool(t *testing.T) {
|
||||
// a pointer to hold the boolean value
|
||||
ptr := true
|
||||
|
||||
// try to copy a false value to the pointer
|
||||
WriteAnswer(&ptr, "", "false")
|
||||
|
||||
// if the value is true
|
||||
if ptr {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToInt(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr int = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToInt8(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr int8 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToInt16(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr int16 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToInt32(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr int32 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToInt64(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr int64 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToUint(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr uint = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToUint8(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr uint8 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToUint16(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr uint16 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToUint32(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr uint32 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToUint64(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr uint64 = 1
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToFloat32(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr float32 = 1.0
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2.5")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2.5 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canStringToFloat64(t *testing.T) {
|
||||
// a pointer to hold the value
|
||||
var ptr float64 = 1.0
|
||||
|
||||
// try to copy a value to the pointer
|
||||
WriteAnswer(&ptr, "", "2.5")
|
||||
|
||||
// if the value is true
|
||||
if ptr != 2.5 {
|
||||
// the test failed
|
||||
t.Error("Could not convert string to pointer type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWrite_canConvertStructFieldTypes(t *testing.T) {
|
||||
// the struct to hold the answer
|
||||
ptr := struct {
|
||||
Name string
|
||||
Age uint
|
||||
Male bool
|
||||
Height float64
|
||||
}{}
|
||||
|
||||
// write the values as strings
|
||||
check(t, WriteAnswer(&ptr, "name", "Bob"))
|
||||
check(t, WriteAnswer(&ptr, "age", "22"))
|
||||
check(t, WriteAnswer(&ptr, "male", "true"))
|
||||
check(t, WriteAnswer(&ptr, "height", "6.2"))
|
||||
|
||||
// make sure we changed the fields
|
||||
if ptr.Name != "Bob" {
|
||||
t.Error("Did not mutate Name when writing answer.")
|
||||
}
|
||||
|
||||
if ptr.Age != 22 {
|
||||
t.Error("Did not mutate Age when writing answer.")
|
||||
}
|
||||
|
||||
if !ptr.Male {
|
||||
t.Error("Did not mutate Male when writing answer.")
|
||||
}
|
||||
|
||||
if ptr.Height != 6.2 {
|
||||
t.Error("Did not mutate Height when writing answer.")
|
||||
}
|
||||
}
|
||||
|
||||
func check(t *testing.T, err error) {
|
||||
if err != nil {
|
||||
t.Fatalf("Encountered error while writing answer: %v", err.Error())
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var simpleQs = []*survey.Question{
|
||||
{
|
||||
Name: "letter",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose a letter:",
|
||||
Options: []string{
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
},
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
answers := struct {
|
||||
Letter string
|
||||
}{}
|
||||
|
||||
// ask the question
|
||||
err := survey.Ask(simpleQs, &answers)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
// print the answers
|
||||
fmt.Printf("you chose %s.\n", answers.Letter)
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var simpleQs = []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{
|
||||
Message: "What is your name?",
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
{
|
||||
Name: "color",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
ansmap := make(map[string]string)
|
||||
|
||||
// ask the question
|
||||
err := survey.Ask(simpleQs, &ansmap)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
// print the answers
|
||||
fmt.Printf("%s chose %s.\n", ansmap["name"], ansmap["color"])
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var simpleQs = []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{
|
||||
Message: "What is your name?",
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
{
|
||||
Name: "color",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
answers := struct {
|
||||
Name string
|
||||
Color string
|
||||
}{}
|
||||
|
||||
// ask the question
|
||||
err := survey.Ask(simpleQs, &answers)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
// print the answers
|
||||
fmt.Printf("%s chose %s.\n", answers.Name, answers.Color)
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var validationQs = []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{Message: "What is your name?"},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
{
|
||||
Name: "valid",
|
||||
Prompt: &survey.Input{Message: "Enter 'foo':", Default: "not foo"},
|
||||
Validate: func(val interface{}) error {
|
||||
// if the input matches the expectation
|
||||
if str := val.(string); str != "foo" {
|
||||
return fmt.Errorf("You entered %s, not 'foo'.", str)
|
||||
}
|
||||
// nothing was wrong
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
// the place to hold the answers
|
||||
answers := struct {
|
||||
Name string
|
||||
Valid string
|
||||
}{}
|
||||
err := survey.Ask(validationQs, &answers)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("\n", err.Error())
|
||||
}
|
||||
}
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
Input is a regular text input that prints each character the user types on the screen
|
||||
and accepts the input with the enter key. Response type is a string.
|
||||
|
||||
name := ""
|
||||
prompt := &survey.Input{ Message: "What is your name?" }
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
*/
|
||||
type Input struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Default string
|
||||
Help string
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type InputTemplateData struct {
|
||||
Input
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var InputQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .ShowAnswer}}
|
||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}
|
||||
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||
{{- end}}`
|
||||
|
||||
func (i *Input) Prompt() (interface{}, error) {
|
||||
// render the template
|
||||
err := i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{Input: *i},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// start reading runes from the standard in
|
||||
rr := terminal.NewRuneReader(os.Stdin)
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
line := []rune{}
|
||||
// get the next line
|
||||
for {
|
||||
line, err = rr.ReadLine(0)
|
||||
if err != nil {
|
||||
return string(line), err
|
||||
}
|
||||
// terminal will echo the \n so we need to jump back up one row
|
||||
terminal.CursorPreviousLine(1)
|
||||
|
||||
if string(line) == string(core.HelpInputRune) && i.Help != "" {
|
||||
err = i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{Input: *i, ShowHelp: true},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// if the line is empty
|
||||
if line == nil || len(line) == 0 {
|
||||
// use the default value
|
||||
return i.Default, err
|
||||
}
|
||||
|
||||
// we're done
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
func (i *Input) Cleanup(val interface{}) error {
|
||||
return i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{Input: *i, Answer: val.(string), ShowAnswer: true},
|
||||
)
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestInputRender(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
title string
|
||||
prompt Input
|
||||
data InputTemplateData
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"Test Input question output without default",
|
||||
Input{Message: "What is your favorite month:"},
|
||||
InputTemplateData{},
|
||||
"? What is your favorite month: ",
|
||||
},
|
||||
{
|
||||
"Test Input question output with default",
|
||||
Input{Message: "What is your favorite month:", Default: "April"},
|
||||
InputTemplateData{},
|
||||
"? What is your favorite month: (April) ",
|
||||
},
|
||||
{
|
||||
"Test Input answer output",
|
||||
Input{Message: "What is your favorite month:"},
|
||||
InputTemplateData{Answer: "October", ShowAnswer: true},
|
||||
"? What is your favorite month: October\n",
|
||||
},
|
||||
{
|
||||
"Test Input question output without default but with help hidden",
|
||||
Input{Message: "What is your favorite month:", Help: "This is helpful"},
|
||||
InputTemplateData{},
|
||||
"? What is your favorite month: [? for help] ",
|
||||
},
|
||||
{
|
||||
"Test Input question output with default and with help hidden",
|
||||
Input{Message: "What is your favorite month:", Default: "April", Help: "This is helpful"},
|
||||
InputTemplateData{},
|
||||
"? What is your favorite month: [? for help] (April) ",
|
||||
},
|
||||
{
|
||||
"Test Input question output without default but with help shown",
|
||||
Input{Message: "What is your favorite month:", Help: "This is helpful"},
|
||||
InputTemplateData{ShowHelp: true},
|
||||
`ⓘ This is helpful
|
||||
? What is your favorite month: `,
|
||||
},
|
||||
{
|
||||
"Test Input question output with default and with help shown",
|
||||
Input{Message: "What is your favorite month:", Default: "April", Help: "This is helpful"},
|
||||
InputTemplateData{ShowHelp: true},
|
||||
`ⓘ This is helpful
|
||||
? What is your favorite month: (April) `,
|
||||
},
|
||||
}
|
||||
|
||||
outputBuffer := bytes.NewBufferString("")
|
||||
terminal.Stdout = outputBuffer
|
||||
|
||||
for _, test := range tests {
|
||||
outputBuffer.Reset()
|
||||
test.data.Input = test.prompt
|
||||
err := test.prompt.Render(
|
||||
InputQuestionTemplate,
|
||||
test.data,
|
||||
)
|
||||
assert.Nil(t, err, test.title)
|
||||
assert.Equal(t, test.expected, outputBuffer.String(), test.title)
|
||||
}
|
||||
}
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
MultiSelect is a prompt that presents a list of various options to the user
|
||||
for them to select using the arrow keys and enter. Response type is a slice of strings.
|
||||
|
||||
days := []string{}
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
}
|
||||
survey.AskOne(prompt, &days, nil)
|
||||
*/
|
||||
type MultiSelect struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Options []string
|
||||
Default []string
|
||||
Help string
|
||||
PageSize int
|
||||
selectedIndex int
|
||||
checked map[string]bool
|
||||
showingHelp bool
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type MultiSelectTemplateData struct {
|
||||
MultiSelect
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
Checked map[string]bool
|
||||
SelectedIndex int
|
||||
ShowHelp bool
|
||||
PageEntries []string
|
||||
}
|
||||
|
||||
var MultiSelectQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }}{{color "reset"}}
|
||||
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}} {{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}}{{end}}
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $option := .PageEntries}}
|
||||
{{- if eq $ix $.SelectedIndex}}{{color "cyan"}}{{ SelectFocusIcon }}{{color "reset"}}{{else}} {{end}}
|
||||
{{- if index $.Checked $option}}{{color "green"}} {{ MarkedOptionIcon }} {{else}}{{color "default+hb"}} {{ UnmarkedOptionIcon }} {{end}}
|
||||
{{- color "reset"}}
|
||||
{{- " "}}{{$option}}{{"\n"}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
// OnChange is called on every keypress.
|
||||
func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
if key == terminal.KeyArrowUp {
|
||||
// if we are at the top of the list
|
||||
if m.selectedIndex == 0 {
|
||||
// go to the bottom
|
||||
m.selectedIndex = len(m.Options) - 1
|
||||
} else {
|
||||
// decrement the selected index
|
||||
m.selectedIndex--
|
||||
}
|
||||
} else if key == terminal.KeyArrowDown {
|
||||
// if we are at the bottom of the list
|
||||
if m.selectedIndex == len(m.Options)-1 {
|
||||
// start at the top
|
||||
m.selectedIndex = 0
|
||||
} else {
|
||||
// increment the selected index
|
||||
m.selectedIndex++
|
||||
}
|
||||
// if the user pressed down and there is room to move
|
||||
} else if key == terminal.KeySpace {
|
||||
if old, ok := m.checked[m.Options[m.selectedIndex]]; !ok {
|
||||
// otherwise just invert the current value
|
||||
m.checked[m.Options[m.selectedIndex]] = true
|
||||
} else {
|
||||
// otherwise just invert the current value
|
||||
m.checked[m.Options[m.selectedIndex]] = !old
|
||||
}
|
||||
// only show the help message if we have one to show
|
||||
} else if key == core.HelpInputRune && m.Help != "" {
|
||||
m.showingHelp = true
|
||||
}
|
||||
|
||||
// paginate the options
|
||||
opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex)
|
||||
|
||||
// render the options
|
||||
m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Checked: m.checked,
|
||||
ShowHelp: m.showingHelp,
|
||||
PageEntries: opts,
|
||||
},
|
||||
)
|
||||
|
||||
// if we are not pressing ent
|
||||
return line, 0, true
|
||||
}
|
||||
|
||||
func (m *MultiSelect) Prompt() (interface{}, error) {
|
||||
// compute the default state
|
||||
m.checked = make(map[string]bool)
|
||||
// if there is a default
|
||||
if len(m.Default) > 0 {
|
||||
for _, dflt := range m.Default {
|
||||
for _, opt := range m.Options {
|
||||
// if the option correponds to the default
|
||||
if opt == dflt {
|
||||
// we found our initial value
|
||||
m.checked[opt] = true
|
||||
// stop looking
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no options to render
|
||||
if len(m.Options) == 0 {
|
||||
// we failed
|
||||
return "", errors.New("please provide options to select from")
|
||||
}
|
||||
|
||||
// hide the cursor
|
||||
terminal.CursorHide()
|
||||
// show the cursor when we're done
|
||||
defer terminal.CursorShow()
|
||||
|
||||
// paginate the options
|
||||
opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex)
|
||||
|
||||
// ask the question
|
||||
err := m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Checked: m.checked,
|
||||
PageEntries: opts,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := terminal.NewRuneReader(os.Stdin)
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
// start waiting for input
|
||||
for {
|
||||
r, _, _ := rr.ReadRune()
|
||||
if r == '\r' || r == '\n' {
|
||||
break
|
||||
}
|
||||
if r == terminal.KeyInterrupt {
|
||||
return "", fmt.Errorf("interrupt")
|
||||
}
|
||||
if r == terminal.KeyEndTransmission {
|
||||
break
|
||||
}
|
||||
m.OnChange(nil, 0, r)
|
||||
}
|
||||
|
||||
answers := []string{}
|
||||
for _, option := range m.Options {
|
||||
if val, ok := m.checked[option]; ok && val {
|
||||
answers = append(answers, option)
|
||||
}
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
// Cleanup removes the options section, and renders the ask like a normal question.
|
||||
func (m *MultiSelect) Cleanup(val interface{}) error {
|
||||
// execute the output summary template with the answer
|
||||
return m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: m.selectedIndex,
|
||||
Checked: m.checked,
|
||||
Answer: strings.Join(val.([]string), ", "),
|
||||
ShowAnswer: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestMultiSelectRender(t *testing.T) {
|
||||
|
||||
prompt := MultiSelect{
|
||||
Message: "Pick your words:",
|
||||
Options: []string{"foo", "bar", "baz", "buz"},
|
||||
Default: []string{"bar", "buz"},
|
||||
}
|
||||
|
||||
helpfulPrompt := prompt
|
||||
helpfulPrompt.Help = "This is helpful"
|
||||
|
||||
tests := []struct {
|
||||
title string
|
||||
prompt MultiSelect
|
||||
data MultiSelectTemplateData
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"Test MultiSelect question output",
|
||||
prompt,
|
||||
MultiSelectTemplateData{
|
||||
SelectedIndex: 2,
|
||||
PageEntries: prompt.Options,
|
||||
Checked: map[string]bool{"bar": true, "buz": true},
|
||||
},
|
||||
`? Pick your words:
|
||||
◯ foo
|
||||
◉ bar
|
||||
❯ ◯ baz
|
||||
◉ buz
|
||||
`,
|
||||
},
|
||||
{
|
||||
"Test MultiSelect answer output",
|
||||
prompt,
|
||||
MultiSelectTemplateData{
|
||||
Answer: "foo, buz",
|
||||
ShowAnswer: true,
|
||||
},
|
||||
"? Pick your words: foo, buz\n",
|
||||
},
|
||||
{
|
||||
"Test MultiSelect question output with help hidden",
|
||||
helpfulPrompt,
|
||||
MultiSelectTemplateData{
|
||||
SelectedIndex: 2,
|
||||
PageEntries: prompt.Options,
|
||||
Checked: map[string]bool{"bar": true, "buz": true},
|
||||
},
|
||||
`? Pick your words: [? for help]
|
||||
◯ foo
|
||||
◉ bar
|
||||
❯ ◯ baz
|
||||
◉ buz
|
||||
`,
|
||||
},
|
||||
{
|
||||
"Test MultiSelect question output with help shown",
|
||||
helpfulPrompt,
|
||||
MultiSelectTemplateData{
|
||||
SelectedIndex: 2,
|
||||
PageEntries: prompt.Options,
|
||||
Checked: map[string]bool{"bar": true, "buz": true},
|
||||
ShowHelp: true,
|
||||
},
|
||||
`ⓘ This is helpful
|
||||
? Pick your words:
|
||||
◯ foo
|
||||
◉ bar
|
||||
❯ ◯ baz
|
||||
◉ buz
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
outputBuffer := bytes.NewBufferString("")
|
||||
terminal.Stdout = outputBuffer
|
||||
|
||||
for _, test := range tests {
|
||||
outputBuffer.Reset()
|
||||
test.data.MultiSelect = test.prompt
|
||||
err := test.prompt.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
test.data,
|
||||
)
|
||||
assert.Nil(t, err, test.title)
|
||||
assert.Equal(t, test.expected, outputBuffer.String(), test.title)
|
||||
}
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
Password is like a normal Input but the text shows up as *'s and there is no default. Response
|
||||
type is a string.
|
||||
|
||||
password := ""
|
||||
prompt := &survey.Password{ Message: "Please type your password" }
|
||||
survey.AskOne(prompt, &password, nil)
|
||||
*/
|
||||
type Password struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Help string
|
||||
}
|
||||
|
||||
type PasswordTemplateData struct {
|
||||
Password
|
||||
ShowHelp bool
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var PasswordQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}`
|
||||
|
||||
func (p *Password) Prompt() (line interface{}, err error) {
|
||||
// render the question template
|
||||
out, err := core.RunTemplate(
|
||||
PasswordQuestionTemplate,
|
||||
PasswordTemplateData{Password: *p},
|
||||
)
|
||||
terminal.Print(out)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := terminal.NewRuneReader(os.Stdin)
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
// no help msg? Just return any response
|
||||
if p.Help == "" {
|
||||
line, err := rr.ReadLine('*')
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
// process answers looking for help prompt answer
|
||||
for {
|
||||
line, err := rr.ReadLine('*')
|
||||
if err != nil {
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
if string(line) == string(core.HelpInputRune) {
|
||||
// terminal will echo the \n so we need to jump back up one row
|
||||
terminal.CursorPreviousLine(1)
|
||||
|
||||
err = p.Render(
|
||||
PasswordQuestionTemplate,
|
||||
PasswordTemplateData{Password: *p, ShowHelp: true},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return string(line), err
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup hides the string with a fixed number of characters.
|
||||
func (prompt *Password) Cleanup(val interface{}) error {
|
||||
return nil
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestPasswordRender(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
title string
|
||||
prompt Password
|
||||
data PasswordTemplateData
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"Test Password question output",
|
||||
Password{Message: "Tell me your secret:"},
|
||||
PasswordTemplateData{},
|
||||
"? Tell me your secret: ",
|
||||
},
|
||||
{
|
||||
"Test Password question output with help hidden",
|
||||
Password{Message: "Tell me your secret:", Help: "This is helpful"},
|
||||
PasswordTemplateData{},
|
||||
"? Tell me your secret: [? for help] ",
|
||||
},
|
||||
{
|
||||
"Test Password question output with help shown",
|
||||
Password{Message: "Tell me your secret:", Help: "This is helpful"},
|
||||
PasswordTemplateData{ShowHelp: true},
|
||||
`ⓘ This is helpful
|
||||
? Tell me your secret: `,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test.data.Password = test.prompt
|
||||
actual, err := core.RunTemplate(
|
||||
PasswordQuestionTemplate,
|
||||
&test.data,
|
||||
)
|
||||
assert.Nil(t, err, test.title)
|
||||
assert.Equal(t, test.expected, actual, test.title)
|
||||
}
|
||||
}
|
||||
-210
@@ -1,210 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
Select is a prompt that presents a list of various options to the user
|
||||
for them to select using the arrow keys and enter. Response type is a string.
|
||||
|
||||
color := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
}
|
||||
survey.AskOne(prompt, &color, nil)
|
||||
*/
|
||||
type Select struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Options []string
|
||||
Default string
|
||||
Help string
|
||||
PageSize int
|
||||
selectedIndex int
|
||||
useDefault bool
|
||||
showingHelp bool
|
||||
}
|
||||
|
||||
// the data available to the templates when processing
|
||||
type SelectTemplateData struct {
|
||||
Select
|
||||
PageEntries []string
|
||||
SelectedIndex int
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
}
|
||||
|
||||
var SelectQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }}{{color "reset"}}
|
||||
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else}}
|
||||
{{- if and .Help (not .ShowHelp)}} {{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}}{{end}}
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $choice := .PageEntries}}
|
||||
{{- if eq $ix $.SelectedIndex}}{{color "cyan+b"}}{{ SelectFocusIcon }} {{else}}{{color "default+hb"}} {{end}}
|
||||
{{- $choice}}
|
||||
{{- color "reset"}}{{"\n"}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
// OnChange is called on every keypress.
|
||||
func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
// if the user pressed the enter key
|
||||
if key == terminal.KeyEnter {
|
||||
return []rune(s.Options[s.selectedIndex]), 0, true
|
||||
// if the user pressed the up arrow
|
||||
} else if key == terminal.KeyArrowUp {
|
||||
s.useDefault = false
|
||||
|
||||
// if we are at the top of the list
|
||||
if s.selectedIndex == 0 {
|
||||
// start from the button
|
||||
s.selectedIndex = len(s.Options) - 1
|
||||
} else {
|
||||
// otherwise we are not at the top of the list so decrement the selected index
|
||||
s.selectedIndex--
|
||||
}
|
||||
// if the user pressed down and there is room to move
|
||||
} else if key == terminal.KeyArrowDown {
|
||||
s.useDefault = false
|
||||
// if we are at the bottom of the list
|
||||
if s.selectedIndex == len(s.Options)-1 {
|
||||
// start from the top
|
||||
s.selectedIndex = 0
|
||||
} else {
|
||||
// increment the selected index
|
||||
s.selectedIndex++
|
||||
}
|
||||
// only show the help message if we have one
|
||||
} else if key == core.HelpInputRune && s.Help != "" {
|
||||
s.showingHelp = true
|
||||
}
|
||||
|
||||
// figure out the options and index to render
|
||||
opts, idx := paginate(s.PageSize, s.Options, s.selectedIndex)
|
||||
|
||||
// render the options
|
||||
s.Render(
|
||||
SelectQuestionTemplate,
|
||||
SelectTemplateData{
|
||||
Select: *s,
|
||||
SelectedIndex: idx,
|
||||
ShowHelp: s.showingHelp,
|
||||
PageEntries: opts,
|
||||
},
|
||||
)
|
||||
|
||||
// if we are not pressing ent
|
||||
return []rune(s.Options[s.selectedIndex]), 0, true
|
||||
}
|
||||
|
||||
func (s *Select) Prompt() (interface{}, error) {
|
||||
// if there are no options to render
|
||||
if len(s.Options) == 0 {
|
||||
// we failed
|
||||
return "", errors.New("please provide options to select from")
|
||||
}
|
||||
|
||||
// start off with the first option selected
|
||||
sel := 0
|
||||
// if there is a default
|
||||
if s.Default != "" {
|
||||
// find the choice
|
||||
for i, opt := range s.Options {
|
||||
// if the option correponds to the default
|
||||
if opt == s.Default {
|
||||
// we found our initial value
|
||||
sel = i
|
||||
// stop looking
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// save the selected index
|
||||
s.selectedIndex = sel
|
||||
|
||||
// figure out the options and index to render
|
||||
opts, idx := paginate(s.PageSize, s.Options, sel)
|
||||
|
||||
// ask the question
|
||||
err := s.Render(
|
||||
SelectQuestionTemplate,
|
||||
SelectTemplateData{
|
||||
Select: *s,
|
||||
PageEntries: opts,
|
||||
SelectedIndex: idx,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// hide the cursor
|
||||
terminal.CursorHide()
|
||||
// show the cursor when we're done
|
||||
defer terminal.CursorShow()
|
||||
|
||||
// by default, use the default value
|
||||
s.useDefault = true
|
||||
|
||||
rr := terminal.NewRuneReader(os.Stdin)
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
// start waiting for input
|
||||
for {
|
||||
r, _, err := rr.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r == '\r' || r == '\n' {
|
||||
break
|
||||
}
|
||||
if r == terminal.KeyInterrupt {
|
||||
return "", fmt.Errorf("interrupt")
|
||||
}
|
||||
if r == terminal.KeyEndTransmission {
|
||||
break
|
||||
}
|
||||
s.OnChange(nil, 0, r)
|
||||
}
|
||||
|
||||
var val string
|
||||
// if we are supposed to use the default value
|
||||
if s.useDefault {
|
||||
// if there is a default value
|
||||
if s.Default != "" {
|
||||
// use the default value
|
||||
val = s.Default
|
||||
} else {
|
||||
// there is no default value so use the first
|
||||
val = s.Options[0]
|
||||
}
|
||||
// otherwise the selected index points to the value
|
||||
} else {
|
||||
// the
|
||||
val = s.Options[s.selectedIndex]
|
||||
}
|
||||
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (s *Select) Cleanup(val interface{}) error {
|
||||
return s.Render(
|
||||
SelectQuestionTemplate,
|
||||
SelectTemplateData{
|
||||
Select: *s,
|
||||
Answer: val.(string),
|
||||
ShowAnswer: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/AlecAivazis/survey/terminal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestSelectRender(t *testing.T) {
|
||||
|
||||
prompt := Select{
|
||||
Message: "Pick your word:",
|
||||
Options: []string{"foo", "bar", "baz", "buz"},
|
||||
Default: "baz",
|
||||
}
|
||||
|
||||
helpfulPrompt := prompt
|
||||
helpfulPrompt.Help = "This is helpful"
|
||||
|
||||
tests := []struct {
|
||||
title string
|
||||
prompt Select
|
||||
data SelectTemplateData
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"Test Select question output",
|
||||
prompt,
|
||||
SelectTemplateData{SelectedIndex: 2, PageEntries: prompt.Options},
|
||||
`? Pick your word:
|
||||
foo
|
||||
bar
|
||||
❯ baz
|
||||
buz
|
||||
`,
|
||||
},
|
||||
{
|
||||
"Test Select answer output",
|
||||
prompt,
|
||||
SelectTemplateData{Answer: "buz", ShowAnswer: true, PageEntries: prompt.Options},
|
||||
"? Pick your word: buz\n",
|
||||
},
|
||||
{
|
||||
"Test Select question output with help hidden",
|
||||
helpfulPrompt,
|
||||
SelectTemplateData{SelectedIndex: 2, PageEntries: prompt.Options},
|
||||
`? Pick your word: [? for help]
|
||||
foo
|
||||
bar
|
||||
❯ baz
|
||||
buz
|
||||
`,
|
||||
},
|
||||
{
|
||||
"Test Select question output with help shown",
|
||||
helpfulPrompt,
|
||||
SelectTemplateData{SelectedIndex: 2, ShowHelp: true, PageEntries: prompt.Options},
|
||||
`ⓘ This is helpful
|
||||
? Pick your word:
|
||||
foo
|
||||
bar
|
||||
❯ baz
|
||||
buz
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
outputBuffer := bytes.NewBufferString("")
|
||||
terminal.Stdout = outputBuffer
|
||||
|
||||
for _, test := range tests {
|
||||
outputBuffer.Reset()
|
||||
test.data.Select = test.prompt
|
||||
err := test.prompt.Render(
|
||||
SelectQuestionTemplate,
|
||||
test.data,
|
||||
)
|
||||
assert.Nil(t, err, test.title)
|
||||
assert.Equal(t, test.expected, outputBuffer.String(), test.title)
|
||||
}
|
||||
}
|
||||
-180
@@ -1,180 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
)
|
||||
|
||||
// PageSize is the default maximum number of items to show in select/multiselect prompts
|
||||
var PageSize = 7
|
||||
|
||||
// Validator is a function passed to a Question after a user has provided a response.
|
||||
// If the function returns an error, then the user will be prompted again for another
|
||||
// response.
|
||||
type Validator func(interface{}) error
|
||||
|
||||
// Question is the core data structure for a survey questionnaire.
|
||||
type Question struct {
|
||||
Name string
|
||||
Prompt Prompt
|
||||
Validate Validator
|
||||
}
|
||||
|
||||
// Prompt is the primary interface for the objects that can take user input
|
||||
// and return a response.
|
||||
type Prompt interface {
|
||||
Prompt() (interface{}, error)
|
||||
Cleanup(interface{}) error
|
||||
Error(error) error
|
||||
}
|
||||
|
||||
/*
|
||||
AskOne performs the prompt for a single prompt and asks for validation if required.
|
||||
Response types should be something that can be casted from the response type designated
|
||||
in the documentation. For example:
|
||||
|
||||
name := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "name",
|
||||
}
|
||||
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
|
||||
*/
|
||||
func AskOne(p Prompt, response interface{}, v Validator) error {
|
||||
err := Ask([]*Question{{Prompt: p, Validate: v}}, response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Ask performs the prompt loop, asking for validation when appropriate. The response
|
||||
type can be one of two options. If a struct is passed, the answer will be written to
|
||||
the field whose name matches the Name field on the corresponding question. Field types
|
||||
should be something that can be casted from the response type designated in the
|
||||
documentation. Note, a survey tag can also be used to identify a Otherwise, a
|
||||
map[string]interface{} can be passed, responses will be written to the key with the
|
||||
matching name. For example:
|
||||
|
||||
qs := []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{Message: "What is your name?"},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
answers := struct{ Name string }{}
|
||||
|
||||
|
||||
err := survey.Ask(qs, &answers)
|
||||
*/
|
||||
func Ask(qs []*Question, response interface{}) error {
|
||||
|
||||
// if we weren't passed a place to record the answers
|
||||
if response == nil {
|
||||
// we can't go any further
|
||||
return errors.New("cannot call Ask() with a nil reference to record the answers")
|
||||
}
|
||||
|
||||
// go over every question
|
||||
for _, q := range qs {
|
||||
// grab the user input and save it
|
||||
ans, err := q.Prompt.Prompt()
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if there is a validate handler for this question
|
||||
if q.Validate != nil {
|
||||
// wait for a valid response
|
||||
for invalid := q.Validate(ans); invalid != nil; invalid = q.Validate(ans) {
|
||||
err := q.Prompt.Error(invalid)
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ask for more input
|
||||
ans, err = q.Prompt.Prompt()
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tell the prompt to cleanup with the validated value
|
||||
q.Prompt.Cleanup(ans)
|
||||
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// stop listening
|
||||
return err
|
||||
}
|
||||
|
||||
// add it to the map
|
||||
err = core.WriteAnswer(response, q.Name, ans)
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
// return the response
|
||||
return nil
|
||||
}
|
||||
|
||||
// paginate returns a single page of choices given the page size, the total list of
|
||||
// possible choices, and the current selected index in the total list.
|
||||
func paginate(page int, choices []string, sel int) ([]string, int) {
|
||||
// the number of elements to show in a single page
|
||||
var pageSize int
|
||||
// if the select has a specific page size
|
||||
if page != 0 {
|
||||
// use the specified one
|
||||
pageSize = page
|
||||
// otherwise the select does not have a page size
|
||||
} else {
|
||||
// use the package default
|
||||
pageSize = PageSize
|
||||
}
|
||||
|
||||
var start, end, cursor int
|
||||
|
||||
if len(choices) < pageSize {
|
||||
// if we dont have enough options to fill a page
|
||||
start = 0
|
||||
end = len(choices)
|
||||
cursor = sel
|
||||
|
||||
} else if sel < pageSize/2 {
|
||||
// if we are in the first half page
|
||||
start = 0
|
||||
end = pageSize
|
||||
cursor = sel
|
||||
|
||||
} else if len(choices)-sel-1 < pageSize/2 {
|
||||
// if we are in the last half page
|
||||
start = len(choices) - pageSize
|
||||
end = len(choices)
|
||||
cursor = sel - start
|
||||
|
||||
} else {
|
||||
// somewhere in the middle
|
||||
above := pageSize / 2
|
||||
below := pageSize - above
|
||||
|
||||
cursor = pageSize / 2
|
||||
start = sel - above
|
||||
end = sel + below
|
||||
}
|
||||
|
||||
// return the subset we care about and the index
|
||||
return choices[start:end], cursor
|
||||
}
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/AlecAivazis/survey/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// disable color output for all prompts to simplify testing
|
||||
core.DisableColor = true
|
||||
}
|
||||
|
||||
func TestValidationError(t *testing.T) {
|
||||
|
||||
err := fmt.Errorf("Football is not a valid month")
|
||||
|
||||
actual, err := core.RunTemplate(
|
||||
core.ErrorTemplate,
|
||||
err,
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to run template to format error: %s", err)
|
||||
}
|
||||
|
||||
expected := `✘ Sorry, your reply was invalid: Football is not a valid month
|
||||
`
|
||||
|
||||
if actual != expected {
|
||||
t.Errorf("Formatted error was not formatted correctly. Found:\n%s\nExpected:\n%s", actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsk_returnsErrorIfTargetIsNil(t *testing.T) {
|
||||
// pass an empty place to leave the answers
|
||||
err := Ask([]*Question{}, nil)
|
||||
|
||||
// if we didn't get an error
|
||||
if err == nil {
|
||||
// the test failed
|
||||
t.Error("Did not encounter error when asking with no where to record.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPagination_tooFew(t *testing.T) {
|
||||
// a small list of options
|
||||
choices := []string{"choice1", "choice2", "choice3"}
|
||||
|
||||
// a page bigger than the total number
|
||||
pageSize := 4
|
||||
// the current selection
|
||||
sel := 3
|
||||
|
||||
// compute the page info
|
||||
page, idx := paginate(pageSize, choices, sel)
|
||||
|
||||
// make sure we see the full list of options
|
||||
assert.Equal(t, choices, page)
|
||||
// with the second index highlighted (no change)
|
||||
assert.Equal(t, 3, idx)
|
||||
}
|
||||
|
||||
func TestPagination_firstHalf(t *testing.T) {
|
||||
// the choices for the test
|
||||
choices := []string{"choice1", "choice2", "choice3", "choice4", "choice5", "choice6"}
|
||||
|
||||
// section the choices into groups of 4 so the choice is somewhere in the middle
|
||||
// to verify there is no displacement of the page
|
||||
pageSize := 4
|
||||
// test the second item
|
||||
sel := 2
|
||||
|
||||
// compute the page info
|
||||
page, idx := paginate(pageSize, choices, sel)
|
||||
|
||||
// we should see the first three options
|
||||
assert.Equal(t, choices[0:4], page)
|
||||
// with the second index highlighted
|
||||
assert.Equal(t, 2, idx)
|
||||
}
|
||||
|
||||
func TestPagination_middle(t *testing.T) {
|
||||
// the choices for the test
|
||||
choices := []string{"choice0", "choice1", "choice2", "choice3", "choice4", "choice5"}
|
||||
|
||||
// section the choices into groups of 3
|
||||
pageSize := 2
|
||||
// test the second item so that we can verify we are in the middle of the list
|
||||
sel := 3
|
||||
|
||||
// compute the page info
|
||||
page, idx := paginate(pageSize, choices, sel)
|
||||
|
||||
// we should see the first three options
|
||||
assert.Equal(t, choices[2:4], page)
|
||||
// with the second index highlighted
|
||||
assert.Equal(t, 1, idx)
|
||||
}
|
||||
|
||||
func TestPagination_lastHalf(t *testing.T) {
|
||||
// the choices for the test
|
||||
choices := []string{"choice0", "choice1", "choice2", "choice3", "choice4", "choice5"}
|
||||
|
||||
// section the choices into groups of 3
|
||||
pageSize := 3
|
||||
// test the last item to verify we're not in the middle
|
||||
sel := 5
|
||||
|
||||
// compute the page info
|
||||
page, idx := paginate(pageSize, choices, sel)
|
||||
|
||||
// we should see the first three options
|
||||
assert.Equal(t, choices[3:6], page)
|
||||
// we should be at the bottom of the list
|
||||
assert.Equal(t, 2, idx)
|
||||
}
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CursorUp moves the cursor n cells to up.
|
||||
func CursorUp(n int) {
|
||||
fmt.Printf("\x1b[%dA", n)
|
||||
}
|
||||
|
||||
// CursorDown moves the cursor n cells to down.
|
||||
func CursorDown(n int) {
|
||||
fmt.Printf("\x1b[%dB", n)
|
||||
}
|
||||
|
||||
// CursorForward moves the cursor n cells to right.
|
||||
func CursorForward(n int) {
|
||||
fmt.Printf("\x1b[%dC", n)
|
||||
}
|
||||
|
||||
// CursorBack moves the cursor n cells to left.
|
||||
func CursorBack(n int) {
|
||||
fmt.Printf("\x1b[%dD", n)
|
||||
}
|
||||
|
||||
// CursorNextLine moves cursor to beginning of the line n lines down.
|
||||
func CursorNextLine(n int) {
|
||||
fmt.Printf("\x1b[%dE", n)
|
||||
}
|
||||
|
||||
// CursorPreviousLine moves cursor to beginning of the line n lines up.
|
||||
func CursorPreviousLine(n int) {
|
||||
fmt.Printf("\x1b[%dF", n)
|
||||
}
|
||||
|
||||
// CursorHorizontalAbsolute moves cursor horizontally to x.
|
||||
func CursorHorizontalAbsolute(x int) {
|
||||
fmt.Printf("\x1b[%dG", x)
|
||||
}
|
||||
|
||||
// CursorShow shows the cursor.
|
||||
func CursorShow() {
|
||||
fmt.Print("\x1b[?25h")
|
||||
}
|
||||
|
||||
// CursorHide hide the cursor.
|
||||
func CursorHide() {
|
||||
fmt.Print("\x1b[?25l")
|
||||
}
|
||||
|
||||
// CursorMove moves the cursor to a specific x,y location.
|
||||
func CursorMove(x int, y int) {
|
||||
fmt.Printf("\x1b[%d;%df", x, y)
|
||||
}
|
||||
|
||||
// CursorLocation returns the current location of the cursor in the terminal
|
||||
func CursorLocation() (*Coord, error) {
|
||||
// print the escape sequence to recieve the position in our stdin
|
||||
fmt.Print("\x1b[6n")
|
||||
|
||||
// read from stdin to get the response
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
// spec says we read 'til R, so do that
|
||||
text, err := reader.ReadSlice('R')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// spec also says they're split by ;, so do that too
|
||||
if strings.Contains(string(text), ";") {
|
||||
// a regex to parse the output of the ansi code
|
||||
re := regexp.MustCompile(`\d+;\d+`)
|
||||
line := re.FindString(string(text))
|
||||
|
||||
// find the column and rows embedded in the string
|
||||
coords := strings.Split(line, ";")
|
||||
|
||||
// try to cast the col number to an int
|
||||
col, err := strconv.Atoi(coords[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to cast the row number to an int
|
||||
row, err := strconv.Atoi(coords[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// return the coordinate object with the col and row we calculated
|
||||
return &Coord{Short(col), Short(row)}, nil
|
||||
}
|
||||
|
||||
// it didn't work so return an error
|
||||
return nil, fmt.Errorf("could not compute the cursor position using ascii escape sequences")
|
||||
}
|
||||
|
||||
// Size returns the height and width of the terminal.
|
||||
func Size() (*Coord, error) {
|
||||
// the general approach here is to move the cursor to the very bottom
|
||||
// of the terminal, ask for the current location and then move the
|
||||
// cursor back where we started
|
||||
|
||||
// save the current location of the cursor
|
||||
origin, err := CursorLocation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// move the cursor to the very bottom of the terminal
|
||||
CursorMove(999, 999)
|
||||
|
||||
// ask for the current location
|
||||
bottom, err := CursorLocation()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// move back where we began
|
||||
CursorUp(int(bottom.Y - origin.Y))
|
||||
CursorHorizontalAbsolute(int(origin.X))
|
||||
|
||||
// sice the bottom was calcuated in the lower right corner, it
|
||||
// is the dimensions we are looking for
|
||||
return bottom, nil
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func CursorUp(n int) {
|
||||
cursorMove(0, n)
|
||||
}
|
||||
|
||||
func CursorDown(n int) {
|
||||
cursorMove(0, -1*n)
|
||||
}
|
||||
|
||||
func CursorForward(n int) {
|
||||
cursorMove(n, 0)
|
||||
}
|
||||
|
||||
func CursorBack(n int) {
|
||||
cursorMove(-1*n, 0)
|
||||
}
|
||||
|
||||
func cursorMove(x int, y int) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
var cursor Coord
|
||||
cursor.X = csbi.cursorPosition.X + Short(x)
|
||||
cursor.Y = csbi.cursorPosition.Y + Short(y)
|
||||
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||
}
|
||||
|
||||
func CursorNextLine(n int) {
|
||||
CursorUp(n)
|
||||
CursorHorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
func CursorPreviousLine(n int) {
|
||||
CursorDown(n)
|
||||
CursorHorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
func CursorHorizontalAbsolute(x int) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
var cursor Coord
|
||||
cursor.X = Short(x)
|
||||
cursor.Y = csbi.cursorPosition.Y
|
||||
|
||||
if csbi.size.X < cursor.X {
|
||||
cursor.X = csbi.size.X
|
||||
}
|
||||
|
||||
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||
}
|
||||
|
||||
func CursorShow() {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
cci.visible = 1
|
||||
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
}
|
||||
|
||||
func CursorHide() {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
cci.visible = 0
|
||||
|
||||
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
}
|
||||
|
||||
func CursorLocation() (Coord, error) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
return csbi.cursorPosition, nil
|
||||
}
|
||||
|
||||
func Size() (Coord, error) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
return csbi.size, nil
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package terminal
|
||||
|
||||
type EraseLineMode int
|
||||
|
||||
const (
|
||||
ERASE_LINE_END EraseLineMode = iota
|
||||
ERASE_LINE_START
|
||||
ERASE_LINE_ALL
|
||||
)
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func EraseLine(mode EraseLineMode) {
|
||||
fmt.Printf("\x1b[%dK", mode)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func EraseLine(mode EraseLineMode) {
|
||||
handle := syscall.Handle(os.Stdout.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
|
||||
var w uint32
|
||||
var x Short
|
||||
cursor := csbi.cursorPosition
|
||||
switch mode {
|
||||
case ERASE_LINE_END:
|
||||
x = csbi.size.X
|
||||
case ERASE_LINE_START:
|
||||
x = 0
|
||||
case ERASE_LINE_ALL:
|
||||
cursor.X = 0
|
||||
x = csbi.size.X
|
||||
}
|
||||
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Returns special stdout, which converts escape sequences to Windows API calls
|
||||
// on Windows environment.
|
||||
func NewAnsiStdout() io.Writer {
|
||||
return os.Stdout
|
||||
}
|
||||
|
||||
// Returns special stderr, which converts escape sequences to Windows API calls
|
||||
// on Windows environment.
|
||||
func NewAnsiStderr() io.Writer {
|
||||
return os.Stderr
|
||||
}
|
||||
-204
@@ -1,204 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
var (
|
||||
singleArgFunctions = map[rune]func(int){
|
||||
'A': CursorUp,
|
||||
'B': CursorDown,
|
||||
'C': CursorForward,
|
||||
'D': CursorBack,
|
||||
'E': CursorNextLine,
|
||||
'F': CursorPreviousLine,
|
||||
'G': CursorHorizontalAbsolute,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
foregroundBlue = 0x1
|
||||
foregroundGreen = 0x2
|
||||
foregroundRed = 0x4
|
||||
foregroundIntensity = 0x8
|
||||
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
|
||||
backgroundBlue = 0x10
|
||||
backgroundGreen = 0x20
|
||||
backgroundRed = 0x40
|
||||
backgroundIntensity = 0x80
|
||||
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
out io.Writer
|
||||
handle syscall.Handle
|
||||
orgAttr word
|
||||
}
|
||||
|
||||
func NewAnsiStdout() io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
out := os.Stdout
|
||||
if !isatty.IsTerminal(out.Fd()) {
|
||||
return out
|
||||
}
|
||||
handle := syscall.Handle(out.Fd())
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
|
||||
}
|
||||
|
||||
func NewAnsiStderr() io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
out := os.Stderr
|
||||
if !isatty.IsTerminal(out.Fd()) {
|
||||
return out
|
||||
}
|
||||
handle := syscall.Handle(out.Fd())
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
|
||||
}
|
||||
|
||||
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||
r := bytes.NewReader(data)
|
||||
|
||||
for {
|
||||
ch, size, err := r.ReadRune()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
n += size
|
||||
|
||||
switch ch {
|
||||
case '\x1b':
|
||||
size, err = w.handleEscape(r)
|
||||
n += size
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
default:
|
||||
fmt.Fprint(w.out, string(ch))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
|
||||
buf := make([]byte, 0, 10)
|
||||
buf = append(buf, "\x1b"...)
|
||||
|
||||
// Check '[' continues after \x1b
|
||||
ch, size, err := r.ReadRune()
|
||||
if err != nil {
|
||||
fmt.Fprint(w.out, string(buf))
|
||||
return
|
||||
}
|
||||
n += size
|
||||
if ch != '[' {
|
||||
fmt.Fprint(w.out, string(buf))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse escape code
|
||||
var code rune
|
||||
argBuf := make([]byte, 0, 10)
|
||||
for {
|
||||
ch, size, err = r.ReadRune()
|
||||
if err != nil {
|
||||
fmt.Fprint(w.out, string(buf))
|
||||
return
|
||||
}
|
||||
n += size
|
||||
if ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') {
|
||||
code = ch
|
||||
break
|
||||
}
|
||||
argBuf = append(argBuf, string(ch)...)
|
||||
}
|
||||
|
||||
w.applyEscapeCode(buf, string(argBuf), code)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) {
|
||||
switch arg + string(code) {
|
||||
case "?25h":
|
||||
CursorShow()
|
||||
return
|
||||
case "?25l":
|
||||
CursorHide()
|
||||
return
|
||||
}
|
||||
|
||||
if f, ok := singleArgFunctions[code]; ok {
|
||||
if n, err := strconv.Atoi(arg); err == nil {
|
||||
f(n)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch code {
|
||||
case 'm':
|
||||
w.applySelectGraphicRendition(arg)
|
||||
default:
|
||||
buf = append(buf, string(code)...)
|
||||
fmt.Fprint(w.out, string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
// Original implementation: https://github.com/mattn/go-colorable
|
||||
func (w *Writer) applySelectGraphicRendition(arg string) {
|
||||
if arg == "" {
|
||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr))
|
||||
return
|
||||
}
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
attr := csbi.attributes
|
||||
|
||||
for _, param := range strings.Split(arg, ";") {
|
||||
n, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case n == 0 || n == 100:
|
||||
attr = w.orgAttr
|
||||
case 1 <= n && n <= 5:
|
||||
attr |= foregroundIntensity
|
||||
case 30 <= n && n <= 37:
|
||||
attr = (attr & backgroundMask)
|
||||
if (n-30)&1 != 0 {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if (n-30)&2 != 0 {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if (n-30)&4 != 0 {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
case 40 <= n && n <= 47:
|
||||
attr = (attr & foregroundMask)
|
||||
if (n-40)&1 != 0 {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if (n-40)&2 != 0 {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if (n-40)&4 != 0 {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
Stdout = NewAnsiStdout()
|
||||
)
|
||||
|
||||
// Print prints given arguments with escape sequence conversion for windows.
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(Stdout, a...)
|
||||
}
|
||||
|
||||
// Printf prints a given format with escape sequence conversion for windows.
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(Stdout, format, a...)
|
||||
}
|
||||
|
||||
// Println prints given arguments with newline and escape sequence conversion
|
||||
// for windows.
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(Stdout, a...)
|
||||
}
|
||||
-184
@@ -1,184 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type RuneReader struct {
|
||||
Input *os.File
|
||||
|
||||
state runeReaderState
|
||||
}
|
||||
|
||||
func NewRuneReader(input *os.File) *RuneReader {
|
||||
return &RuneReader{
|
||||
Input: input,
|
||||
state: newRuneReaderState(input),
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
line := []rune{}
|
||||
|
||||
// we only care about horizontal displacements from the origin so start counting at 0
|
||||
index := 0
|
||||
|
||||
for {
|
||||
// wait for some input
|
||||
r, _, err := rr.ReadRune()
|
||||
if err != nil {
|
||||
return line, err
|
||||
}
|
||||
|
||||
// if the user pressed enter or some other newline/termination like ctrl+d
|
||||
if r == '\r' || r == '\n' || r == KeyEndTransmission {
|
||||
// go to the beginning of the next line
|
||||
Print("\r\n")
|
||||
|
||||
// we're done processing the input
|
||||
return line, nil
|
||||
}
|
||||
|
||||
// if the user interrupts (ie with ctrl+c)
|
||||
if r == KeyInterrupt {
|
||||
// go to the beginning of the next line
|
||||
Print("\r\n")
|
||||
|
||||
// we're done processing the input, and treat interrupt like an error
|
||||
return line, fmt.Errorf("interrupt")
|
||||
}
|
||||
|
||||
// allow for backspace/delete editing of inputs
|
||||
if r == KeyBackspace || r == KeyDelete {
|
||||
// and we're not at the beginning of the line
|
||||
if index > 0 && len(line) > 0 {
|
||||
// if we are at the end of the word
|
||||
if index == len(line) {
|
||||
// just remove the last letter from the internal representation
|
||||
line = line[:len(line)-1]
|
||||
|
||||
// go back one
|
||||
CursorBack(1)
|
||||
|
||||
// clear the rest of the line
|
||||
EraseLine(ERASE_LINE_END)
|
||||
} else {
|
||||
// we need to remove a character from the middle of the word
|
||||
|
||||
// remove the current index from the list
|
||||
line = append(line[:index-1], line[index:]...)
|
||||
|
||||
// go back one space so we can clear the rest
|
||||
CursorBack(1)
|
||||
|
||||
// clear the rest of the line
|
||||
EraseLine(ERASE_LINE_END)
|
||||
|
||||
// print what comes after
|
||||
Print(string(line[index-1:]))
|
||||
|
||||
// leave the cursor where the user left it
|
||||
CursorBack(len(line) - index + 1)
|
||||
}
|
||||
|
||||
// decrement the index
|
||||
index--
|
||||
} else {
|
||||
// otherwise the user pressed backspace while at the beginning of the line
|
||||
soundBell()
|
||||
}
|
||||
|
||||
// we're done processing this key
|
||||
continue
|
||||
}
|
||||
|
||||
// if the left arrow is pressed
|
||||
if r == KeyArrowLeft {
|
||||
// and we have space to the left
|
||||
if index > 0 {
|
||||
// move the cursor to the left
|
||||
CursorBack(1)
|
||||
// decrement the index
|
||||
index--
|
||||
|
||||
} else {
|
||||
// otherwise we are at the beginning of where we started reading lines
|
||||
// sound the bell
|
||||
soundBell()
|
||||
}
|
||||
|
||||
// we're done processing this key press
|
||||
continue
|
||||
}
|
||||
|
||||
// if the right arrow is pressed
|
||||
if r == KeyArrowRight {
|
||||
// and we have space to the right of the word
|
||||
if index < len(line) {
|
||||
// move the cursor to the right
|
||||
CursorForward(1)
|
||||
// increment the index
|
||||
index++
|
||||
|
||||
} else {
|
||||
// otherwise we are at the end of the word and can't go past
|
||||
// sound the bell
|
||||
soundBell()
|
||||
}
|
||||
|
||||
// we're done processing this key press
|
||||
continue
|
||||
}
|
||||
|
||||
// if the letter is another escape sequence
|
||||
if unicode.IsControl(r) {
|
||||
// ignore it
|
||||
continue
|
||||
}
|
||||
|
||||
// the user pressed a regular key
|
||||
|
||||
// if we are at the end of the line
|
||||
if index == len(line) {
|
||||
// just append the character at the end of the line
|
||||
line = append(line, r)
|
||||
// increment the location counter
|
||||
index++
|
||||
|
||||
// if we don't need to mask the input
|
||||
if mask == 0 {
|
||||
// just print the character the user pressed
|
||||
Printf("%c", r)
|
||||
} else {
|
||||
// otherwise print the mask we were given
|
||||
Printf("%c", mask)
|
||||
}
|
||||
} else {
|
||||
// we are in the middle of the word so we need to insert the character the user pressed
|
||||
line = append(line[:index], append([]rune{r}, line[index:]...)...)
|
||||
|
||||
// visually insert the character by deleting the rest of the line
|
||||
EraseLine(ERASE_LINE_END)
|
||||
|
||||
// print the rest of the word after
|
||||
for _, char := range line[index:] {
|
||||
// if we don't need to mask the input
|
||||
if mask == 0 {
|
||||
// just print the character the user pressed
|
||||
Printf("%c", char)
|
||||
} else {
|
||||
// otherwise print the mask we were given
|
||||
Printf("%c", mask)
|
||||
}
|
||||
}
|
||||
|
||||
// leave the cursor where the user left it
|
||||
CursorBack(len(line) - index - 1)
|
||||
|
||||
// accomodate the new letter in our counter
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
// copied from: https://github.com/golang/crypto/blob/master/ssh/terminal/util_bsd.go
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package terminal
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
const ioctlWriteTermios = syscall.TIOCSETA
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
// copied from https://github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package terminal
|
||||
|
||||
// These constants are declared here, rather than importing
|
||||
// them from the syscall package as some syscall packages, even
|
||||
// on linux, for example gccgo, do not declare them.
|
||||
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
// The terminal mode manipluation code is derived heavily from:
|
||||
// https://github.com/golang/crypto/blob/master/ssh/terminal/util.go:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type runeReaderState struct {
|
||||
term syscall.Termios
|
||||
buf *bufio.Reader
|
||||
}
|
||||
|
||||
func newRuneReaderState(input *os.File) runeReaderState {
|
||||
return runeReaderState{
|
||||
buf: bufio.NewReader(input),
|
||||
}
|
||||
}
|
||||
|
||||
// For reading runes we just want to disable echo.
|
||||
func (rr *RuneReader) SetTermMode() error {
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
newState := rr.state.term
|
||||
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG
|
||||
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) RestoreTermMode() error {
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.Input.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) ReadRune() (rune, int, error) {
|
||||
r, size, err := rr.state.buf.ReadRune()
|
||||
if err != nil {
|
||||
return r, size, err
|
||||
}
|
||||
// parse ^[ sequences to look for arrow keys
|
||||
if r == '\033' {
|
||||
r, size, err = rr.state.buf.ReadRune()
|
||||
if err != nil {
|
||||
return r, size, err
|
||||
}
|
||||
if r != '[' {
|
||||
return r, size, fmt.Errorf("Unexpected Escape Sequence: %q", []rune{'\033', r})
|
||||
}
|
||||
r, size, err = rr.state.buf.ReadRune()
|
||||
if err != nil {
|
||||
return r, size, err
|
||||
}
|
||||
switch r {
|
||||
case 'D':
|
||||
return KeyArrowLeft, 1, nil
|
||||
case 'C':
|
||||
return KeyArrowRight, 1, nil
|
||||
case 'A':
|
||||
return KeyArrowUp, 1, nil
|
||||
case 'B':
|
||||
return KeyArrowDown, 1, nil
|
||||
}
|
||||
return r, size, fmt.Errorf("Unknown Escape Sequence: %q", []rune{'\033', '[', r})
|
||||
}
|
||||
return r, size, err
|
||||
}
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
dll = syscall.NewLazyDLL("kernel32.dll")
|
||||
setConsoleMode = dll.NewProc("SetConsoleMode")
|
||||
getConsoleMode = dll.NewProc("GetConsoleMode")
|
||||
readConsoleInput = dll.NewProc("ReadConsoleInputW")
|
||||
)
|
||||
|
||||
const (
|
||||
EVENT_KEY = 0x0001
|
||||
|
||||
// key codes for arrow keys
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
VK_LEFT = 0x25
|
||||
VK_UP = 0x26
|
||||
VK_RIGHT = 0x27
|
||||
VK_DOWN = 0x28
|
||||
|
||||
RIGHT_CTRL_PRESSED = 0x0004
|
||||
LEFT_CTRL_PRESSED = 0x0008
|
||||
|
||||
ENABLE_ECHO_INPUT uint32 = 0x0004
|
||||
ENABLE_LINE_INPUT uint32 = 0x0002
|
||||
ENABLE_PROCESSED_INPUT uint32 = 0x0001
|
||||
)
|
||||
|
||||
type inputRecord struct {
|
||||
eventType uint16
|
||||
padding uint16
|
||||
event [16]byte
|
||||
}
|
||||
|
||||
type keyEventRecord struct {
|
||||
bKeyDown int32
|
||||
wRepeatCount uint16
|
||||
wVirtualKeyCode uint16
|
||||
wVirtualScanCode uint16
|
||||
unicodeChar uint16
|
||||
wdControlKeyState uint32
|
||||
}
|
||||
|
||||
type runeReaderState struct {
|
||||
term uint32
|
||||
}
|
||||
|
||||
func newRuneReaderState(input *os.File) runeReaderState {
|
||||
return runeReaderState{}
|
||||
}
|
||||
|
||||
func (rr *RuneReader) SetTermMode() error {
|
||||
r, _, err := getConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(unsafe.Pointer(&rr.state.term)))
|
||||
// windows return 0 on error
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
newState := rr.state.term
|
||||
newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT
|
||||
r, _, err = setConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(newState))
|
||||
// windows return 0 on error
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) RestoreTermMode() error {
|
||||
r, _, err := setConsoleMode.Call(uintptr(rr.Input.Fd()), uintptr(rr.state.term))
|
||||
// windows return 0 on error
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) ReadRune() (rune, int, error) {
|
||||
ir := &inputRecord{}
|
||||
bytesRead := 0
|
||||
for {
|
||||
rv, _, e := readConsoleInput.Call(rr.Input.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead)))
|
||||
// windows returns non-zero to indicate success
|
||||
if rv == 0 && e != nil {
|
||||
return 0, 0, e
|
||||
}
|
||||
|
||||
if ir.eventType != EVENT_KEY {
|
||||
continue
|
||||
}
|
||||
|
||||
// the event data is really a c struct union, so here we have to do an usafe
|
||||
// cast to put the data into the keyEventRecord (since we have already verified
|
||||
// above that this event does correspond to a key event
|
||||
key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0]))
|
||||
// we only care about key down events
|
||||
if key.bKeyDown == 0 {
|
||||
continue
|
||||
}
|
||||
if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' {
|
||||
return KeyInterrupt, bytesRead, nil
|
||||
}
|
||||
|
||||
// not a normal character so look up the input sequence from the
|
||||
// virtual key code mappings (VK_*)
|
||||
if key.unicodeChar == 0 {
|
||||
switch key.wVirtualKeyCode {
|
||||
case VK_DOWN:
|
||||
return KeyArrowDown, bytesRead, nil
|
||||
case VK_LEFT:
|
||||
return KeyArrowLeft, bytesRead, nil
|
||||
case VK_RIGHT:
|
||||
return KeyArrowRight, bytesRead, nil
|
||||
case VK_UP:
|
||||
return KeyArrowUp, bytesRead, nil
|
||||
default:
|
||||
// not a virtual key that we care about so just continue on to
|
||||
// the next input key
|
||||
continue
|
||||
}
|
||||
}
|
||||
r := rune(key.unicodeChar)
|
||||
return r, bytesRead, nil
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
package terminal
|
||||
|
||||
const (
|
||||
KeyArrowLeft = '\x02'
|
||||
KeyArrowRight = '\x06'
|
||||
KeyArrowUp = '\x10'
|
||||
KeyArrowDown = '\x0e'
|
||||
KeySpace = ' '
|
||||
KeyEnter = '\r'
|
||||
KeyBackspace = '\b'
|
||||
KeyDelete = '\x7f'
|
||||
KeyInterrupt = '\x03'
|
||||
KeyEndTransmission = '\x04'
|
||||
)
|
||||
|
||||
func soundBell() {
|
||||
Print("\a")
|
||||
}
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||
)
|
||||
|
||||
type wchar uint16
|
||||
type dword uint32
|
||||
type word uint16
|
||||
|
||||
type smallRect struct {
|
||||
left Short
|
||||
top Short
|
||||
right Short
|
||||
bottom Short
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
size Coord
|
||||
cursorPosition Coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize Coord
|
||||
}
|
||||
|
||||
type consoleCursorInfo struct {
|
||||
size dword
|
||||
visible int32
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
package terminal
|
||||
|
||||
type Short int16
|
||||
|
||||
type Coord struct {
|
||||
X Short
|
||||
Y Short
|
||||
}
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
# survey/tests
|
||||
|
||||
Because of the nature of this library, I was having a hard time finding a reliable
|
||||
way to run unit tests, therefore I decided to try to create a suite
|
||||
of integration tests which must be run successfully before a PR can be merged.
|
||||
I will try to add to this suite as new edge cases are known.
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var simpleQs = []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{
|
||||
Message: "What is your name?",
|
||||
Default: "Johnny Appleseed",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "color",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green", "yellow"},
|
||||
Default: "yellow",
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
fmt.Println("Asking many.")
|
||||
// a place to store the answers
|
||||
ans := struct {
|
||||
Name string
|
||||
Color string
|
||||
}{}
|
||||
err := survey.Ask(simpleQs, &ans)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Asking one.")
|
||||
answer := ""
|
||||
err = survey.AskOne(simpleQs[0].Prompt, &answer, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Printf("Answered with %v.\n", answer)
|
||||
|
||||
fmt.Println("Asking one with validation.")
|
||||
vAns := ""
|
||||
err = survey.AskOne(&survey.Input{Message: "What is your name?"}, &vAns, survey.Required)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
fmt.Printf("Answered with %v.\n", vAns)
|
||||
}
|
||||
-180
@@ -1,180 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/ask.go go run ask.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "ask.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("Asking many.\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m\x1b[37m(Johnny Appleseed) \x1b[0m", buf)
|
||||
fh.Write([]byte("L"))
|
||||
expect("L", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("a", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("y"))
|
||||
expect("y", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect(" ", buf)
|
||||
fh.Write([]byte("B"))
|
||||
expect("B", buf)
|
||||
fh.Write([]byte("i"))
|
||||
expect("i", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("d"))
|
||||
expect("d", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m\x1b[36mLarry Bird\x1b[0m\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ yellow\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("A"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m yellow\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("A"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m yellow\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\x1b[36m blue\x1b[0m\r\n", buf)
|
||||
expect("Asking one.\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m\x1b[37m(Johnny Appleseed) \x1b[0m", buf)
|
||||
fh.Write([]byte("L"))
|
||||
expect("L", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("a", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("y"))
|
||||
expect("y", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect(" ", buf)
|
||||
fh.Write([]byte("K"))
|
||||
expect("K", buf)
|
||||
fh.Write([]byte("i"))
|
||||
expect("i", buf)
|
||||
fh.Write([]byte("n"))
|
||||
expect("n", buf)
|
||||
fh.Write([]byte("g"))
|
||||
expect("g", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m\x1b[36mLarry King\x1b[0m\r\n", buf)
|
||||
expect("Answered with Larry King.\r\n", buf)
|
||||
expect("Asking one with validation.\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[31m✘ Sorry, your reply was invalid: Value is required\x1b[0m\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m", buf)
|
||||
fh.Write([]byte("L"))
|
||||
expect("L", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("a", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("y"))
|
||||
expect("y", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect(" ", buf)
|
||||
fh.Write([]byte("W"))
|
||||
expect("W", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("a", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m\x1b[36mLarry Wall\x1b[0m\r\n", buf)
|
||||
expect("Answered with Larry Wall.\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-132
@@ -1,132 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/confirm.go go run confirm.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "confirm.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("Enter 'yes'\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[37m(y/N) \x1b[0m", buf)
|
||||
fh.Write([]byte("y"))
|
||||
expect("y", buf)
|
||||
fh.Write([]byte("e"))
|
||||
expect("e", buf)
|
||||
fh.Write([]byte("s"))
|
||||
expect("s", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[36mYes\x1b[0m\r\n", buf)
|
||||
expect("Answered true.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("Enter 'no'\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[37m(y/N) \x1b[0m", buf)
|
||||
fh.Write([]byte("n"))
|
||||
expect("n", buf)
|
||||
fh.Write([]byte("o"))
|
||||
expect("o", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[36mNo\x1b[0m\r\n", buf)
|
||||
expect("Answered false.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("default\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[37m(Y/n) \x1b[0m", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[36mYes\x1b[0m\r\n", buf)
|
||||
expect("Answered true.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("not recognized (enter random letter)\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[37m(Y/n) \x1b[0m", buf)
|
||||
fh.Write([]byte("x"))
|
||||
expect("x", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[31m✘ Sorry, your reply was invalid: \"x\" is not a valid answer, please try again.\x1b[0m\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[37m(Y/n) \x1b[0m", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[36mYes\x1b[0m\r\n", buf)
|
||||
expect("Answered true.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("no help - type '?'\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[37m(Y/n) \x1b[0m", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("?", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[31m✘ Sorry, your reply was invalid: \"?\" is not a valid answer, please try again.\x1b[0m\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[37m(Y/n) \x1b[0m", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99myes: \x1b[0m\x1b[36mYes\x1b[0m\r\n", buf)
|
||||
expect("Answered true.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-104
@@ -1,104 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/doubleSelect.go go run doubleSelect.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "doubleSelect.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mselect1:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mselect1:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mselect1:\x1b[0m\x1b[36m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mselect2:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mselect2:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mselect2:\x1b[0m\x1b[36m blue\x1b[0m\r\n", buf)
|
||||
expect("blue and blue.\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-236
@@ -1,236 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/help.go go run help.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "help.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("confirm\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mIs it raining? \x1b[0m\x1b[36m[? for help]\x1b[0m \x1b[37m(y/N) \x1b[0m", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("?", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[36mⓘ Go outside, if your head becomes wet the answer is probably 'yes'\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mIs it raining? \x1b[0m\x1b[37m(y/N) \x1b[0m", buf)
|
||||
fh.Write([]byte("y"))
|
||||
expect("y", buf)
|
||||
fh.Write([]byte("e"))
|
||||
expect("e", buf)
|
||||
fh.Write([]byte("s"))
|
||||
expect("s", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mIs it raining? \x1b[0m\x1b[36mYes\x1b[0m\r\n", buf)
|
||||
expect("Answered true.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("input\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your phone number: \x1b[0m\x1b[36m[? for help]\x1b[0m ", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("?", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[36mⓘ Phone number should include the area code, parentheses optional\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your phone number: \x1b[0m", buf)
|
||||
fh.Write([]byte("1"))
|
||||
expect("1", buf)
|
||||
fh.Write([]byte("2"))
|
||||
expect("2", buf)
|
||||
fh.Write([]byte("3"))
|
||||
expect("3", buf)
|
||||
fh.Write([]byte("-"))
|
||||
expect("-", buf)
|
||||
fh.Write([]byte("1"))
|
||||
expect("1", buf)
|
||||
fh.Write([]byte("2"))
|
||||
expect("2", buf)
|
||||
fh.Write([]byte("3"))
|
||||
expect("3", buf)
|
||||
fh.Write([]byte("-"))
|
||||
expect("-", buf)
|
||||
fh.Write([]byte("1"))
|
||||
expect("1", buf)
|
||||
fh.Write([]byte("2"))
|
||||
expect("2", buf)
|
||||
fh.Write([]byte("3"))
|
||||
expect("3", buf)
|
||||
fh.Write([]byte("4"))
|
||||
expect("4", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your phone number: \x1b[0m\x1b[36m123-123-1234\x1b[0m\r\n", buf)
|
||||
expect("Answered 123-123-1234.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("select\r\n", buf)
|
||||
expect("\x1b[?25l\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m \x1b[36m[? for help]\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[36mⓘ We are closed weekends and avaibility is limited on Wednesday\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[36mⓘ We are closed weekends and avaibility is limited on Wednesday\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[36mⓘ We are closed weekends and avaibility is limited on Wednesday\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[36mⓘ We are closed weekends and avaibility is limited on Wednesday\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[36mⓘ We are closed weekends and avaibility is limited on Wednesday\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[36mⓘ We are closed weekends and avaibility is limited on Wednesday\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days are you available:\x1b[0m\x1b[36m Monday, Friday\x1b[0m\r\n", buf)
|
||||
expect("Answered [Monday Friday].\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("select\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m \x1b[36m[? for help]\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[36mⓘ Blue is the best color, but it is your choice\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\x1b[36m blue\x1b[0m\r\n", buf)
|
||||
expect("Answered blue.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("password\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mEnter a secret: \x1b[0m\x1b[36m[? for help]\x1b[0m ", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[36mⓘ Don't really enter a secret, this is just for testing\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mEnter a secret: \x1b[0m", buf)
|
||||
fh.Write([]byte("f"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("o"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("o"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("b"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("Answered foobar.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-176
@@ -1,176 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/input.go go run input.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "input.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("no default\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello world \x1b[0m", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("a", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("e"))
|
||||
expect("e", buf)
|
||||
fh.Write([]byte("c"))
|
||||
expect("c", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello world \x1b[0m\x1b[36malec\x1b[0m\r\n", buf)
|
||||
expect("Answered alec.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("default\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello world \x1b[0m\x1b[37m(default) \x1b[0m", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello world \x1b[0m\x1b[36mdefault\x1b[0m\r\n", buf)
|
||||
expect("Answered default.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("no help, send '?'\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello world \x1b[0m", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("?", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello world \x1b[0m\x1b[36m?\x1b[0m\r\n", buf)
|
||||
expect("Answered ?.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("input text in random location\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello \x1b[0m", buf)
|
||||
fh.Write([]byte("h"))
|
||||
expect("h", buf)
|
||||
fh.Write([]byte("e"))
|
||||
expect("e", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("o"))
|
||||
expect("o", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect(" ", buf)
|
||||
fh.Write([]byte("w"))
|
||||
expect("w", buf)
|
||||
fh.Write([]byte("o"))
|
||||
expect("o", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("d"))
|
||||
expect("d", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("D"))
|
||||
expect("\x1b[1D", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
fh.Write([]byte("a"))
|
||||
expect("\x1b[0Kad\x1b[1D", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("D"))
|
||||
expect("\x1b[1D", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("D"))
|
||||
expect("\x1b[1D", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("\x1b[0Kalad\x1b[3D", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("D"))
|
||||
expect("\x1b[1D", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("D"))
|
||||
expect("\x1b[1D", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("D"))
|
||||
expect("\x1b[1D", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("D"))
|
||||
expect("\x1b[1D", buf)
|
||||
fh.Write([]byte("\u007f"))
|
||||
expect("\x1b[1D\x1b[0Kworalad\x1b[7D", buf)
|
||||
fh.Write([]byte("\u007f"))
|
||||
expect("\x1b[1D\x1b[0Kworalad\x1b[7D", buf)
|
||||
fh.Write([]byte("\u007f"))
|
||||
expect("\x1b[1D\x1b[0Kworalad\x1b[7D", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mHello \x1b[0m\x1b[36mhelworalad\x1b[0m\r\n", buf)
|
||||
expect("Answered helworalad.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-446
@@ -1,446 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/multiselect.go go run multiselect.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "multiselect.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("standard\r\n", buf)
|
||||
expect("\x1b[?25l\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\x1b[36m Monday, Friday\x1b[0m\r\n", buf)
|
||||
expect("Answered [Monday Friday].\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("default (sunday, tuesday)\r\n", buf)
|
||||
expect("\x1b[?25l\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\x1b[36m Monday, Friday\x1b[0m\r\n", buf)
|
||||
expect("Answered [Monday Friday].\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("default not found\r\n", buf)
|
||||
expect("\x1b[?25l\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\x1b[36m Monday, Friday\x1b[0m\r\n", buf)
|
||||
expect("Answered [Monday Friday].\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("no help - type ?\r\n", buf)
|
||||
expect("\x1b[?25l\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[1;99m ◯ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Sunday\r\n", buf)
|
||||
expect(" \x1b[32m ◉ \x1b[0m Monday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Tuesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Wednesday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Thursday\r\n", buf)
|
||||
expect("\x1b[36m❯\x1b[0m\x1b[32m ◉ \x1b[0m Friday\r\n", buf)
|
||||
expect(" \x1b[1;99m ◯ \x1b[0m Saturday\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat days do you prefer:\x1b[0m\x1b[36m Monday, Friday\x1b[0m\r\n", buf)
|
||||
expect("Answered [Monday Friday].\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/password.go go run password.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "password.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("standard\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mPlease type your password: \x1b[0m", buf)
|
||||
fh.Write([]byte("f"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("o"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("o"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("b"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("Answered foobar.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("please make sure paste works\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mPlease paste your password: \x1b[0m", buf)
|
||||
fh.Write([]byte("f"))
|
||||
fh.Write([]byte("o"))
|
||||
fh.Write([]byte("o"))
|
||||
fh.Write([]byte("b"))
|
||||
fh.Write([]byte("a"))
|
||||
fh.Write([]byte("r"))
|
||||
expect("******", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("Answered foobar.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("no help, send '?'\r\n", buf)
|
||||
expect("\x1b[1;92m? \x1b[0m\x1b[1;99mPlease type your password: \x1b[0m", buf)
|
||||
fh.Write([]byte("?"))
|
||||
expect("*", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("Answered ?.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-158
@@ -1,158 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/select.go go run select.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "select.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("standard\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\x1b[36m red\x1b[0m\r\n", buf)
|
||||
expect("Answered red.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("short\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\x1b[36m red\x1b[0m\r\n", buf)
|
||||
expect("Answered red.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("default\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color (should default blue):\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color (should default blue):\x1b[0m\x1b[36m blue\x1b[0m\r\n", buf)
|
||||
expect("Answered blue.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("one\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ hello\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\x1b[36m hello\x1b[0m\r\n", buf)
|
||||
expect("Answered hello.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("no help, type ?\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\x1b[36m red\x1b[0m\r\n", buf)
|
||||
expect("Answered red.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("passes through bottom\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\x1b[36m red\x1b[0m\r\n", buf)
|
||||
expect("Answered red.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("passes through top\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("A"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose one:\x1b[0m\x1b[36m blue\x1b[0m\r\n", buf)
|
||||
expect("Answered blue.\r\n", buf)
|
||||
expect("---------------------\r\n", buf)
|
||||
expect("no options\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// DO NOT MODIFY THIS FILE!
|
||||
//
|
||||
// This file was automatically generated via the commands:
|
||||
//
|
||||
// go get github.com/coryb/autoplay
|
||||
// autoplay -n autoplay/selectThenInput.go go run selectThenInput.go
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/kr/pty"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
RED = "\033[31m"
|
||||
RESET = "\033[0m"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fh, tty, _ := pty.Open()
|
||||
defer tty.Close()
|
||||
defer fh.Close()
|
||||
c := exec.Command("go", "run", "selectThenInput.go")
|
||||
c.Stdin = tty
|
||||
c.Stdout = tty
|
||||
c.Stderr = tty
|
||||
c.Start()
|
||||
buf := bufio.NewReaderSize(fh, 1024)
|
||||
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
expect("\x1b[?25l", buf)
|
||||
fh.Write([]byte("\x1b"))
|
||||
fh.Write([]byte("["))
|
||||
fh.Write([]byte("B"))
|
||||
expect("\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m red\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;36m❯ blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[1;99m green\x1b[0m\r\n", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\x1b[?25h\x1b[0G\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1F\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mChoose a color:\x1b[0m\x1b[36m blue\x1b[0m\r\n", buf)
|
||||
expect("\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m", buf)
|
||||
fh.Write([]byte("L"))
|
||||
expect("L", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("a", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("r"))
|
||||
expect("r", buf)
|
||||
fh.Write([]byte("y"))
|
||||
expect("y", buf)
|
||||
fh.Write([]byte(" "))
|
||||
expect(" ", buf)
|
||||
fh.Write([]byte("W"))
|
||||
expect("W", buf)
|
||||
fh.Write([]byte("a"))
|
||||
expect("a", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("l"))
|
||||
expect("l", buf)
|
||||
fh.Write([]byte("\r"))
|
||||
expect("\r\r\n", buf)
|
||||
expect("\x1b[1F\x1b[0G\x1b[2K\x1b[1;92m? \x1b[0m\x1b[1;99mWhat is your name? \x1b[0m\x1b[36mLarry Wall\x1b[0m\r\n", buf)
|
||||
expect("Larry Wall chose blue.\r\n", buf)
|
||||
|
||||
c.Wait()
|
||||
tty.Close()
|
||||
fh.Close()
|
||||
}
|
||||
|
||||
func expect(expected string, buf *bufio.Reader) {
|
||||
sofar := []rune{}
|
||||
for _, r := range expected {
|
||||
got, _, _ := buf.ReadRune()
|
||||
sofar = append(sofar, got)
|
||||
if got != r {
|
||||
fmt.Fprintln(os.Stderr, RESET)
|
||||
|
||||
// we want to quote the string but we also want to make the unexpected character RED
|
||||
// so we use the strconv.Quote function but trim off the quoted characters so we can
|
||||
// merge multiple quoted strings into one.
|
||||
expStart := strings.TrimSuffix(strconv.Quote(expected[:len(sofar)-1]), "\"")
|
||||
expMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(expected[len(sofar)-1])), "\""), "\"")
|
||||
expEnd := strings.TrimPrefix(strconv.Quote(expected[len(sofar):]), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Expected: %s%s%s%s%s\n", expStart, RED, expMiss, RESET, expEnd)
|
||||
|
||||
// read the rest of the buffer
|
||||
p := make([]byte, buf.Buffered())
|
||||
buf.Read(p)
|
||||
|
||||
gotStart := strings.TrimSuffix(strconv.Quote(string(sofar[:len(sofar)-1])), "\"")
|
||||
gotMiss := strings.TrimSuffix(strings.TrimPrefix(strconv.Quote(string(sofar[len(sofar)-1])), "\""), "\"")
|
||||
gotEnd := strings.TrimPrefix(strconv.Quote(string(p)), "\"")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Got: %s%s%s%s%s\n", gotStart, RED, gotMiss, RESET, gotEnd)
|
||||
panic(fmt.Errorf("Unexpected Rune %q, Expected %q\n", got, r))
|
||||
} else {
|
||||
fmt.Printf("%c", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AlecAivazis/survey"
|
||||
"github.com/AlecAivazis/survey/tests/util"
|
||||
)
|
||||
|
||||
var answer = false
|
||||
|
||||
var goodTable = []TestUtil.TestTableEntry{
|
||||
{
|
||||
"Enter 'yes'", &survey.Confirm{
|
||||
Message: "yes:",
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"Enter 'no'", &survey.Confirm{
|
||||
Message: "yes:",
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"default", &survey.Confirm{
|
||||
Message: "yes:",
|
||||
Default: true,
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"not recognized (enter random letter)", &survey.Confirm{
|
||||
Message: "yes:",
|
||||
Default: true,
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"no help - type '?'", &survey.Confirm{
|
||||
Message: "yes:",
|
||||
Default: true,
|
||||
}, &answer,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
TestUtil.RunTable(goodTable)
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
var simpleQs = []*survey.Question{
|
||||
{
|
||||
Name: "color",
|
||||
Prompt: &survey.Select{
|
||||
Message: "select1:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
{
|
||||
Name: "color2",
|
||||
Prompt: &survey.Select{
|
||||
Message: "select2:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
answers := struct {
|
||||
Color string
|
||||
Color2 string
|
||||
}{}
|
||||
// ask the question
|
||||
err := survey.Ask(simpleQs, &answers)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
// print the answers
|
||||
fmt.Printf("%s and %s.\n", answers.Color, answers.Color2)
|
||||
}
|
||||
-55
@@ -1,55 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AlecAivazis/survey"
|
||||
"github.com/AlecAivazis/survey/tests/util"
|
||||
)
|
||||
|
||||
var (
|
||||
confirmAns = false
|
||||
inputAns = ""
|
||||
multiselectAns = []string{}
|
||||
selectAns = ""
|
||||
passwordAns = ""
|
||||
)
|
||||
|
||||
var goodTable = []TestUtil.TestTableEntry{
|
||||
{
|
||||
"confirm", &survey.Confirm{
|
||||
Message: "Is it raining?",
|
||||
Help: "Go outside, if your head becomes wet the answer is probably 'yes'",
|
||||
}, &confirmAns,
|
||||
},
|
||||
{
|
||||
"input", &survey.Input{
|
||||
Message: "What is your phone number:",
|
||||
Help: "Phone number should include the area code, parentheses optional",
|
||||
}, &inputAns,
|
||||
},
|
||||
{
|
||||
"select", &survey.MultiSelect{
|
||||
Message: "What days are you available:",
|
||||
Help: "We are closed weekends and avaibility is limited on Wednesday",
|
||||
Options: []string{"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"},
|
||||
Default: []string{"Monday", "Tuesday", "Thursday", "Friday"},
|
||||
}, &multiselectAns,
|
||||
},
|
||||
{
|
||||
"select", &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Help: "Blue is the best color, but it is your choice",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
Default: "blue",
|
||||
}, &selectAns,
|
||||
},
|
||||
{
|
||||
"password", &survey.Password{
|
||||
Message: "Enter a secret:",
|
||||
Help: "Don't really enter a secret, this is just for testing",
|
||||
}, &passwordAns,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
TestUtil.RunTable(goodTable)
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AlecAivazis/survey"
|
||||
"github.com/AlecAivazis/survey/tests/util"
|
||||
)
|
||||
|
||||
var val = ""
|
||||
|
||||
var table = []TestUtil.TestTableEntry{
|
||||
{
|
||||
"no default", &survey.Input{Message: "Hello world"}, &val,
|
||||
},
|
||||
{
|
||||
"default", &survey.Input{Message: "Hello world", Default: "default"}, &val,
|
||||
},
|
||||
{
|
||||
"no help, send '?'", &survey.Input{Message: "Hello world"}, &val,
|
||||
},
|
||||
{
|
||||
"input text in random location", &survey.Input{Message: "Hello"}, &val,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
TestUtil.RunTable(table)
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package main
|
||||
|
||||
import "github.com/AlecAivazis/survey"
|
||||
|
||||
func main() {
|
||||
color := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{
|
||||
"a",
|
||||
"b",
|
||||
"c",
|
||||
"d",
|
||||
"e",
|
||||
"f",
|
||||
"g",
|
||||
"h",
|
||||
"i",
|
||||
"j",
|
||||
},
|
||||
}
|
||||
survey.AskOne(prompt, &color, nil)
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AlecAivazis/survey"
|
||||
"github.com/AlecAivazis/survey/tests/util"
|
||||
)
|
||||
|
||||
var answer = []string{}
|
||||
|
||||
var table = []TestUtil.TestTableEntry{
|
||||
{
|
||||
"standard", &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"default (sunday, tuesday)", &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
Default: []string{"Sunday", "Tuesday"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"default not found", &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
Default: []string{"Sundayaa"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"no help - type ?", &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
Default: []string{"Sundayaa"},
|
||||
}, &answer,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
TestUtil.RunTable(table)
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AlecAivazis/survey"
|
||||
"github.com/AlecAivazis/survey/tests/util"
|
||||
)
|
||||
|
||||
var value = ""
|
||||
|
||||
var table = []TestUtil.TestTableEntry{
|
||||
{
|
||||
"standard", &survey.Password{Message: "Please type your password:"}, &value,
|
||||
},
|
||||
{
|
||||
"please make sure paste works", &survey.Password{Message: "Please paste your password:"}, &value,
|
||||
},
|
||||
{
|
||||
"no help, send '?'", &survey.Password{Message: "Please type your password:"}, &value,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
TestUtil.RunTable(table)
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/AlecAivazis/survey"
|
||||
"github.com/AlecAivazis/survey/tests/util"
|
||||
)
|
||||
|
||||
var answer = ""
|
||||
|
||||
var goodTable = []TestUtil.TestTableEntry{
|
||||
{
|
||||
"standard", &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"short", &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"default", &survey.Select{
|
||||
Message: "Choose a color (should default blue):",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
Default: "blue",
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"one", &survey.Select{
|
||||
Message: "Choose one:",
|
||||
Options: []string{"hello"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"no help, type ?", &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"passes through bottom", &survey.Select{
|
||||
Message: "Choose one:",
|
||||
Options: []string{"red", "blue"},
|
||||
}, &answer,
|
||||
},
|
||||
{
|
||||
"passes through top", &survey.Select{
|
||||
Message: "Choose one:",
|
||||
Options: []string{"red", "blue"},
|
||||
}, &answer,
|
||||
},
|
||||
}
|
||||
|
||||
var badTable = []TestUtil.TestTableEntry{
|
||||
{
|
||||
"no options", &survey.Select{
|
||||
Message: "Choose one:",
|
||||
}, &answer,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
TestUtil.RunTable(goodTable)
|
||||
TestUtil.RunErrorTable(badTable)
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var simpleQs = []*survey.Question{
|
||||
{
|
||||
Name: "color",
|
||||
Prompt: &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{
|
||||
Message: "What is your name?",
|
||||
},
|
||||
Validate: survey.Required,
|
||||
},
|
||||
}
|
||||
|
||||
func main() {
|
||||
answers := struct {
|
||||
Color string
|
||||
Name string
|
||||
}{}
|
||||
// ask the question
|
||||
err := survey.Ask(simpleQs, &answers)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
// print the answers
|
||||
fmt.Printf("%s chose %s.\n", answers.Name, answers.Color)
|
||||
}
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
package TestUtil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/AlecAivazis/survey"
|
||||
)
|
||||
|
||||
type TestTableEntry struct {
|
||||
Name string
|
||||
Prompt survey.Prompt
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func formatAnswer(ans interface{}) {
|
||||
// show the answer to the user
|
||||
fmt.Printf("Answered %v.\n", reflect.ValueOf(ans).Elem())
|
||||
fmt.Println("---------------------")
|
||||
}
|
||||
|
||||
func RunTable(table []TestTableEntry) {
|
||||
// go over every entry in the table
|
||||
for _, entry := range table {
|
||||
// tell the user what we are going to ask them
|
||||
fmt.Println(entry.Name)
|
||||
// perform the ask
|
||||
err := survey.AskOne(entry.Prompt, entry.Value, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("AskOne on %v's prompt failed: %v.", entry.Name, err.Error())
|
||||
break
|
||||
}
|
||||
// show the answer to the user
|
||||
formatAnswer(entry.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func RunErrorTable(table []TestTableEntry) {
|
||||
// go over every entry in the table
|
||||
for _, entry := range table {
|
||||
// tell the user what we are going to ask them
|
||||
fmt.Println(entry.Name)
|
||||
// perform the ask
|
||||
err := survey.AskOne(entry.Prompt, entry.Value, nil)
|
||||
if err == nil {
|
||||
fmt.Printf("AskOne on %v's prompt didn't fail.", entry.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Required does not allow an empty value
|
||||
func Required(val interface{}) error {
|
||||
// if the value passed in is the zero value of the appropriate type
|
||||
if isZero(reflect.ValueOf(val)) {
|
||||
return errors.New("Value is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxLength requires that the string is no longer than the specified value
|
||||
func MaxLength(length int) Validator {
|
||||
// return a validator that checks the length of the string
|
||||
return func(val interface{}) error {
|
||||
if str, ok := val.(string); ok {
|
||||
// if the string is longer than the given value
|
||||
if len(str) > length {
|
||||
// yell loudly
|
||||
return fmt.Errorf("value is too long. Max length is %v", length)
|
||||
}
|
||||
} else {
|
||||
// otherwise we cannot convert the value into a string and cannot enforce length
|
||||
return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name())
|
||||
}
|
||||
|
||||
// the input is fine
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MinLength requires that the string is longer or equal in length to the specified value
|
||||
func MinLength(length int) Validator {
|
||||
// return a validator that checks the length of the string
|
||||
return func(val interface{}) error {
|
||||
if str, ok := val.(string); ok {
|
||||
// if the string is shorter than the given value
|
||||
if len(str) < length {
|
||||
// yell loudly
|
||||
return fmt.Errorf("value is too short. Min length is %v", length)
|
||||
}
|
||||
} else {
|
||||
// otherwise we cannot convert the value into a string and cannot enforce length
|
||||
return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name())
|
||||
}
|
||||
|
||||
// the input is fine
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ComposeValidators is a variadic function used to create one validator from many.
|
||||
func ComposeValidators(validators ...Validator) Validator {
|
||||
// return a validator that calls each one sequentially
|
||||
return func(val interface{}) error {
|
||||
// execute each validator
|
||||
for _, validator := range validators {
|
||||
// if the string is not valid
|
||||
if err := validator(val); err != nil {
|
||||
// return the error
|
||||
return err
|
||||
}
|
||||
}
|
||||
// we passed all validators, the string is valid
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// isZero returns true if the passed value is the zero object
|
||||
func isZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
return v.Len() == 0
|
||||
}
|
||||
|
||||
// compare the types directly with more general coverage
|
||||
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
|
||||
}
|
||||
-151
@@ -1,151 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRequired_canSucceedOnPrimitiveTypes(t *testing.T) {
|
||||
// a string to test
|
||||
str := "hello"
|
||||
// if the string is not valid
|
||||
if valid := Required(str); valid != nil {
|
||||
//
|
||||
t.Error("Non null returned an error when one wasn't expected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequired_canFailOnPrimitiveTypes(t *testing.T) {
|
||||
// a string to test
|
||||
str := ""
|
||||
// if the string is valid
|
||||
if notValid := Required(str); notValid == nil {
|
||||
//
|
||||
t.Error("Non null did not return an error when one was expected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequired_canSucceedOnMap(t *testing.T) {
|
||||
// an non-empty map to test
|
||||
val := map[string]int{"hello": 1}
|
||||
// if the string is not valid
|
||||
if valid := Required(val); valid != nil {
|
||||
//
|
||||
t.Error("Non null returned an error when one wasn't expected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequired_canFailOnMap(t *testing.T) {
|
||||
// an non-empty map to test
|
||||
val := map[string]int{}
|
||||
// if the string is valid
|
||||
if notValid := Required(val); notValid == nil {
|
||||
//
|
||||
t.Error("Non null did not return an error when one was expected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequired_canSucceedOnLists(t *testing.T) {
|
||||
// a string to test
|
||||
str := []string{"hello"}
|
||||
// if the string is not valid
|
||||
if valid := Required(str); valid != nil {
|
||||
//
|
||||
t.Error("Non null returned an error when one wasn't expected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequired_canFailOnLists(t *testing.T) {
|
||||
// a string to test
|
||||
str := []string{}
|
||||
// if the string is not valid
|
||||
if notValid := Required(str); notValid == nil {
|
||||
//
|
||||
t.Error("Non null did not return an error when one was expected.")
|
||||
}
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func randString(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Int63()%int64(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func TestMaxLength(t *testing.T) {
|
||||
// the string to test
|
||||
testStr := randString(150)
|
||||
// validate the string
|
||||
if err := MaxLength(140)(testStr); err == nil {
|
||||
t.Error("No error returned with input greater than 150 characters.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinLength(t *testing.T) {
|
||||
// validate the string
|
||||
if err := MinLength(12)(randString(10)); err == nil {
|
||||
t.Error("No error returned with input less than 12 characters.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinLength_onInt(t *testing.T) {
|
||||
// validate the string
|
||||
if err := MinLength(12)(1); err == nil {
|
||||
t.Error("No error returned when enforcing length on int.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaxLength_onInt(t *testing.T) {
|
||||
// validate the string
|
||||
if err := MaxLength(12)(1); err == nil {
|
||||
t.Error("No error returned when enforcing length on int.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeValidators_passes(t *testing.T) {
|
||||
// create a validator that requires a string of no more than 10 characters
|
||||
valid := ComposeValidators(
|
||||
Required,
|
||||
MaxLength(10),
|
||||
)
|
||||
|
||||
str := randString(12)
|
||||
// if a valid string fails
|
||||
if err := valid(str); err == nil {
|
||||
// the test failed
|
||||
t.Error("Composed validator did not pass. Wanted string less than 10 chars, passed in", str)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestComposeValidators_failsOnFirstError(t *testing.T) {
|
||||
// create a validator that requires a string of no more than 10 characters
|
||||
valid := ComposeValidators(
|
||||
Required,
|
||||
MaxLength(10),
|
||||
)
|
||||
|
||||
// if an empty string passes
|
||||
if err := valid(""); err == nil {
|
||||
// the test failed
|
||||
t.Error("Composed validator did not fail on first test like expected.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeValidators_failsOnSubsequentValidators(t *testing.T) {
|
||||
// create a validator that requires a string of no more than 10 characters
|
||||
valid := ComposeValidators(
|
||||
Required,
|
||||
MaxLength(10),
|
||||
)
|
||||
|
||||
str := randString(12)
|
||||
// if a string longer than 10 passes
|
||||
if err := valid(str); err == nil {
|
||||
// the test failed
|
||||
t.Error("Composed validator did not fail on second first test like expected. Should fail max length > 10 :", str)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user