update for github.com/AlecAivazis/survey => gopkg.in/AlecAivazis/survey.v1 package

This commit is contained in:
Cory Bennett
2017-09-06 08:40:34 -07:00
parent 4d79af4f5e
commit 9453179251
67 changed files with 4 additions and 4 deletions
-23
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,291 +0,0 @@
# Survey
[![Build Status](https://travis-ci.org/AlecAivazis/survey.svg?branch=feature%2Fpretty)](https://travis-ci.org/AlecAivazis/survey)
[![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg)](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/).
![](https://zippy.gfycat.com/AmusingBossyArrowworm.gif)
```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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,9 +0,0 @@
package terminal
type EraseLineMode int
const (
ERASE_LINE_END EraseLineMode = iota
ERASE_LINE_START
ERASE_LINE_ALL
)
-11
View File
@@ -1,11 +0,0 @@
// +build !windows
package terminal
import (
"fmt"
)
func EraseLine(mode EraseLineMode) {
fmt.Printf("\x1b[%dK", mode)
}
-28
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -1,8 +0,0 @@
package terminal
type Short int16
type Coord struct {
X Short
Y Short
}
-6
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
}