Giter Club home page Giter Club logo

survey's Introduction

Survey

GoDoc

A library for building interactive and accessible prompts on terminals supporting ANSI escape sequences.

⚠️ This project is no longer maintained. For an alternative, please check out: https://github.com/charmbracelet/bubbletea ⚠️

Hey everyone! I finally came to terms with the fact that I can no longer dedicate enough time to keep this library alive. This project outgrew my wildest expectations and was such a great experience. If someone else wants to take over maintainence, please reach out

package main

import (
    "fmt"
    "github.com/AlecAivazis/survey/v2"
)

// the questions to ask
var qs = []*survey.Question{
    {
        Name:     "name",
        Prompt:   &survey.Input{Message: "What is your name?"},
        Validate: survey.Required,
        Transform: survey.Title,
    },
    {
        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, survey will convert it
    }{}

    // 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)
}

Examples

Examples can be found in the examples/ directory. Run them to see basic behavior:

go run examples/simple.go
go run examples/validation.go

Running the Prompts

There are two primary ways to execute prompts and start collecting information from your users: Ask and AskOne. The primary difference is whether you are interested in collecting a single piece of information or if you have a list of questions to ask whose answers should be collected in a single struct. For most basic usecases, Ask should be enough. However, for surveys with complicated branching logic, we recommend that you break out your questions into multiple calls to both of these functions to fit your needs.

Configuring the Prompts

Most prompts take fine-grained configuration through fields on the structs you instantiate. It is also possible to change survey's default behaviors by passing AskOpts to either Ask or AskOne. Examples in this document will do both interchangeably:

prompt := &Select{
    Message: "Choose a color:",
    Options: []string{"red", "blue", "green"},
    // can pass a validator directly
    Validate: survey.Required,
}

// or define a default for the single call to `AskOne`
// the answer will get written to the color variable
survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))

// or define a default for every entry in a list of questions
// the answer will get copied into the matching field of the struct as shown above
survey.Ask(questions, &answers, survey.WithValidator(survey.Required))

Prompts

Input

name := ""
prompt := &survey.Input{
    Message: "ping",
}
survey.AskOne(prompt, &name)

Suggestion Options

file := ""
prompt := &survey.Input{
    Message: "inform a file to save:",
    Suggest: func (toComplete string) []string {
        files, _ := filepath.Glob(toComplete + "*")
        return files
    },
}
}
survey.AskOne(prompt, &file)

Multiline

text := ""
prompt := &survey.Multiline{
    Message: "ping",
}
survey.AskOne(prompt, &text)

Password

password := ""
prompt := &survey.Password{
    Message: "Please type your password",
}
survey.AskOne(prompt, &password)

Confirm

name := false
prompt := &survey.Confirm{
    Message: "Do you like pie?",
}
survey.AskOne(prompt, &name)

Select

color := ""
prompt := &survey.Select{
    Message: "Choose a color:",
    Options: []string{"red", "blue", "green"},
}
survey.AskOne(prompt, &color)

Fields and values that come from a Select prompt can be one of two different things. If you pass an int the field will have the value of the selected index. If you instead pass a string, the string value selected will be written to the field.

The user can also press esc to toggle the ability cycle through the options with the j and k keys to do down and up respectively.

By default, the select prompt is limited to showing 7 options at a time and will paginate lists of options longer than that. This can be changed a number of ways:

// as a field on a single select
prompt := &survey.MultiSelect{..., PageSize: 10}

// or as an option to Ask or AskOne
survey.AskOne(prompt, &days, survey.WithPageSize(10))

Select options description

The optional description text can be used to add extra information to each option listed in the select prompt:

color := ""
prompt := &survey.Select{
    Message: "Choose a color:",
    Options: []string{"red", "blue", "green"},
    Description: func(value string, index int) string {
        if value == "red" {
            return "My favorite color"
        }
        return ""
    },
}
survey.AskOne(prompt, &color)

// Assuming that the user chose "red - My favorite color":
fmt.Println(color) //=> "red"

MultiSelect

Example

days := []string{}
prompt := &survey.MultiSelect{
    Message: "What days do you prefer:",
    Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
}
survey.AskOne(prompt, &days)

Fields and values that come from a MultiSelect prompt can be one of two different things. If you pass an int the field will have a slice of the selected indices. If you instead pass a string, a slice of the string values selected will be written to the field.

The user can also press esc to toggle the ability cycle through the options with the j and k keys to do down and up respectively.

By default, the MultiSelect prompt is limited to showing 7 options at a time and will paginate lists of options longer than that. This can be changed a number of ways:

// as a field on a single select
prompt := &survey.MultiSelect{..., PageSize: 10}

// or as an option to Ask or AskOne
survey.AskOne(prompt, &days, survey.WithPageSize(10))

Editor

Launches the user's preferred editor (defined by the $VISUAL or $EDITOR environment variables) on a temporary file. Once the user exits their editor, the contents of the temporary file are read in as the result. If neither of those are present, notepad (on Windows) or vim (Linux or Mac) is used.

You can also specify a pattern for the name of the temporary file. This can be useful for ensuring syntax highlighting matches your usecase.

prompt := &survey.Editor{
    Message: "Shell code snippet",
    FileName: "*.sh",
}

survey.AskOne(prompt, &content)

Filtering Options

By default, the user can filter for options in Select and MultiSelects by typing while the prompt is active. This will filter out all options that don't contain the typed string anywhere in their name, ignoring case.

A custom filter function can also be provided to change this behavior:

func myFilter(filterValue string, optValue string, optIndex int) bool {
    // only include the option if it includes the filter and has length greater than 5
    return strings.Contains(optValue, filterValue) && len(optValue) >= 5
}

// configure it for a specific prompt
&Select{
    Message: "Choose a color:",
    Options: []string{"red", "blue", "green"},
    Filter: myFilter,
}

// or define a default for all of the questions
survey.AskOne(prompt, &color, survey.WithFilter(myFilter))

Keeping the filter active

By default the filter will disappear if the user selects one of the filtered elements. Once the user selects one element the filter setting is gone.

However the user can prevent this from happening and keep the filter active for multiple selections in a e.g. MultiSelect:

// configure it for a specific prompt
&Select{
    Message:    "Choose a color:",
    Options:    []string{"light-green", "green", "dark-green", "red"},
    KeepFilter: true,
}

// or define a default for all of the questions
survey.AskOne(prompt, &color, survey.WithKeepFilter(true))

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. Like usual, validators can be provided directly to the prompt or with survey.WithValidator:

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.")
        }
	return nil
    },
}

color := ""
prompt := &survey.Input{ Message: "Whats your name?" }

// you can pass multiple validators here and survey will make sure each one passes
survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))

Built-in Validators

survey comes prepackaged with a few validators to fit common situations. Currently these validators include:

name valid types description notes
Required any Rejects zero values of the response type Boolean values pass straight through since the zero value (false) is a valid response
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
MaxItems(n) []OptionAnswer Enforces that a response has no more selections of the indicated
MinItems(n) []OptionAnswer Enforces that a response has no less selections of the indicated

Help Text

All of the prompts have a Help field which can be defined to provide more information to your users:

&survey.Input{
    Message: "What is your phone number:",
    Help:    "Phone number should include the area code",
}

Removing the "Select All" and "Select None" options

By default, users can select all of the multi-select options using the right arrow key. To prevent users from being able to do this (and remove the <right> to all message from the prompt), use the option WithRemoveSelectAll:

import (
    "github.com/AlecAivazis/survey/v2"
)

number := ""
prompt := &survey.Input{
    Message: "This question has the select all option removed",
}

survey.AskOne(prompt, &number, survey.WithRemoveSelectAll())

Also by default, users can use the left arrow key to unselect all of the options. To prevent users from being able to do this (and remove the <left> to none message from the prompt), use the option WithRemoveSelectNone:

import (
    "github.com/AlecAivazis/survey/v2"
)

number := ""
prompt := &survey.Input{
    Message: "This question has the select all option removed",
}

survey.AskOne(prompt, &number, survey.WithRemoveSelectNone())

Changing the input rune

In some situations, ? is a perfectly valid response. To handle this, you can change the rune that survey looks for with WithHelpInput:

import (
    "github.com/AlecAivazis/survey/v2"
)

number := ""
prompt := &survey.Input{
    Message: "If you have this need, please give me a reasonable message.",
    Help:    "I couldn't come up with one.",
}

survey.AskOne(prompt, &number, survey.WithHelpInput('^'))

Changing the Icons

Changing the icons and their color/format can be done by passing the WithIcons option. The format follows the patterns outlined here. For example:

import (
    "github.com/AlecAivazis/survey/v2"
)

number := ""
prompt := &survey.Input{
    Message: "If you have this need, please give me a reasonable message.",
    Help:    "I couldn't come up with one.",
}

survey.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) {
    // you can set any icons
    icons.Question.Text = "⁇"
    // for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format
    icons.Question.Format = "yellow+hb"
}))

The icons and their default text and format are summarized below:

name text format description
Error X red Before an error
Help i cyan Before help text
Question ? green+hb Before the message of a prompt
SelectFocus > green Marks the current focus in Select and MultiSelect prompts
UnmarkedOption [ ] default+hb Marks an unselected option in a MultiSelect prompt
MarkedOption [x] cyan+b Marks a chosen selection in a MultiSelect prompt

Custom Types

survey will assign prompt answers to your custom types if they implement this interface:

type Settable interface {
    WriteAnswer(field string, value interface{}) error
}

Here is an example how to use them:

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
)

Testing

You can test your program's interactive prompts using go-expect. The library can be used to expect a match on stdout and respond on stdin. Since os.Stdout in a go test process is not a TTY, if you are manipulating the cursor or using survey, you will need a way to interpret terminal / ANSI escape sequences for things like CursorLocation. vt10x.NewVT10XConsole will create a go-expect console that also multiplexes stdio to an in-memory virtual terminal.

For some examples, you can see any of the tests in this repo.

FAQ

What kinds of IO are supported by survey?

survey aims to support most terminal emulators; it expects support for ANSI escape sequences. This means that reading from piped stdin or writing to piped stdout is not supported, and likely to break your application in these situations. See #337

Why isn't Ctrl-C working?

Ordinarily, when you type Ctrl-C, the terminal recognizes this as the QUIT button and delivers a SIGINT signal to the process, which terminates it. However, Survey temporarily configures the terminal to deliver control codes as ordinary input bytes. When Survey reads a ^C byte (ASCII \x03, "end of text"), it interrupts the current survey and returns a github.com/AlecAivazis/survey/v2/terminal.InterruptErr from Ask or AskOne. If you want to stop the process, handle the returned error in your code:

err := survey.AskOne(prompt, &myVar)
if err != nil {
	if err == terminal.InterruptErr {
		log.Fatal("interrupted")
	}
	...
}

survey's People

Contributors

alecaivazis avatar coryb avatar equim-chan avatar hinshun avatar infalmo avatar jdoklovic avatar kataras avatar lucassabreu avatar mad-py avatar markusfreitag avatar mehrunessky avatar mgabeler-lee-6rs avatar mholt avatar mislav avatar mostafahussein avatar noerw avatar patrickdillon avatar pierrebtz avatar roytangrb avatar sagikazarmark avatar seblegall avatar sirregion avatar stbenjam avatar stefanvassilev avatar suzuki-shunsuke avatar system-glitch avatar tklauser avatar twelvelabs avatar vcishere avatar zimeg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

survey's Issues

Support []int for writing indexes in addition to []string

The use case I have is type Product struct { name string, code int }.

So I create a var productNames []string from my []product slice, then pass it to survey.AskOne as MultiSelect options:

if err := survey.AskOne(&survey.MultiSelect{
	Message: "Which products do you want: ",
	Options: productNames,
}, &productAnswers, nil); err != nil {
	log.Fatal(errors.Wrap(err, "failed to prompt"))
}

At the end what I need is a list of product.codes.

In the code above, productAnswers has to be []string. To find the relevant product value that has the name displayed in the options list, I have to do either of the following:

  • create a name to product (map[string]product) mapping and after getting answers, look it up from there.
  • for each answer, iterate over products and find the product that has name==answer.

Both are fine as the list is probably small and these are cheap operations, they just cause extra code to be written.

If survey package supported writing indexes to []ints, it would be really easy to look the actual structs up from the products slice. For example, https://github.com/abiosoft/ishell package returns the answers to MultiSelect as slice of indexes chosen, and it works fine too. So if Write() function copying the answers into value supported both []int and []string for MultiSelect, it would be great.

Fix correct versioning of releases

Currently, some releases are incorrectly tagged in GitHub (missing the v prefix before e.g. 1.3.1):

skarmavbild 2017-09-29 kl 18 56 02

This causes gopkg to not pick up these versions:

skarmavbild 2017-09-29 kl 18 57 40

Which means that if you use github.com/AlecAivazis/survey you get build errors, and if you use gopkg.in/AlecAivazis/survey.v1/survey you get an older version without e.g. survey.Editor ¯_(ツ)_/¯

skarmavbild 2017-09-29 kl 18 53 57

ANSI codes not respected by SecureCRT

When i use exapmple code
`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",
},
},
}

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
}{}

// 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)

}`

the menu is

[root@localhost]/home/mygo/src/cloudcon# go run c.go ? What is your name? 12 ? What is your name? 12 ? Choose a color: ❯ red blue green ? Choose a color: red ❯ blue green ? Choose a color: red blue ❯ green ? Choose a color: red blue ❯ green ? Choose a color: red blue ❯ green

failed validation does not cleanup prompt on answer

I noticed while testing that if you fail a validation multiple times it will stack up the error messages. So this test code:

    answer := ""
    survey.AskOne(
        &survey.Input{Message: "Enter something:"},
        &answer,
        survey.Required,
    )
    fmt.Printf("response string: %s\n", answer)

Will produce this if you just type Enter a few times:

✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
✘ Sorry, your reply was invalid: Value is required
? Enter something: something
response string: something

I am not sure if this is desired behavior or not.

Since the renderer does know now about printing the error message it is unable to include those lines in the lineCount used when rewriting the prompt. If we wanted to fix this I think we need to somehow make the prompt renderer render the error message upon validation, but not sure what changes would be required to make this happen.

Provide more examples, for all question types

I was trying to use MultiSelect and tried to look for code examples, mostly because it's unclear to me how I'm supposed to collect the answers back (what interface{}'s actual type should be).

I tried []string, didn't work. So I had to dig into examples/longlist.go, where I saw a survey.Select example. Apparently it deserializes into a struct that has a field that has the same name as question.Name. This sounds fine.

But I have to guess whether that field should be []int or []string because it's not documented at https://godoc.org/github.com/AlecAivazis/survey#MultiSelect and I can't find any examples. It seems like there's a little bit of friction or docs/examples gap.

Answers can only be one word

Sorry for the flood of issues. Having a lot of fun with this lib though. 😄

Using the demo in the README, it seems that the survey.Input prompt truncates answers to just the first word. (Try using something like "hello dolly" as the answer.)

I'm on a roll with something, or I'd submit a patch right now -- maybe sometime in the future if you can't get around to it, however I'm not super familiar with terminal-fu.

Ctrl-C doesn't exit

I have yet to find any of my implementations that allow Ctrl-C to exit the application.

When it is going through and collecting from a Choice, all that Ctrl-C does is re-display the choices.

When I hit a required AskOne, it just skips that particular prompt without taking in a value. For example:

func createSpeakerPrompt(city, year string) (err error) {
	var exitCode = true

	for exitCode {
		if city == "" {
			prompt := &survey.Input{
				Message: "Enter the city name:",
			}
			survey.AskOne(prompt, &city, survey.Required)
		}

		if year == "" {
			prompt := &survey.Input{
				Message: "Enter the year:",
			}
			survey.AskOne(prompt, &year, survey.Required)
		}
		speaker.CreateSpeaker("", city, year)
		prompt := &survey.Confirm{
			Message: "Do you want to add another speaker?",
		}
		survey.AskOne(prompt, &exitCode, nil)
	}
	return
}

When you go through that, it will prompt you for the city...if you hit ctrl-c, it will then prompt for the year.

Input only returns string

So, although the write tests handle different answer types, when you go up a level to the ask / askOne functions, they will always fail if you do a simple Input{} prompt with anything but a string.

given this test code:

func TestInt(t *testing.T) {
	q := []*Question{
		{
			Name: "age",
			Prompt: &Input{
				Message: "What is your age?",
			},
			Validate: Required,
		},
	}

	in, _ := ioutil.TempFile("", "")
	defer in.Close()

	os.Stdin = in

	io.WriteString(in, "21\n")
	in.Seek(0, os.SEEK_SET)

	ans := int64(0)

	err := Ask(q, &ans)

	assert.Nil(t, err)
	assert.Equal(t, int64(21), ans)

}

you'll always get an error "reflect.Set: value of type string is not assignable to type int64"
I believe it's due to the fact that Input.Prompt always converts stuff to strings at the end:

// we're done
	return string(line), err

So essentially, if you use Input you always have to expect a string back, unless i'm missing something.

Choice questions with less than 3 options leave first line

Neat little package, thanks!

Unfortunately, when choice questions have just 1 or 2 options, the first line remains:

package main

import (
    "fmt"
    "github.com/alecaivazis/survey"
)

// the questions to ask
var qs = []*survey.Question{
    {
        Name: "color",
        Prompt: &survey.Choice{
            Message: "Choose a color:",
            Choices: []string{"red", "blue"},
            Default: "red",
        },
    },
}

func main() {
    answers, err := survey.Ask(qs)

    if err != nil {
        fmt.Println("\n", err.Error())
        return
    }

    fmt.Printf("%s chose %s.", answers["name"], answers["color"])
}

Prints, at the end:

? Choose a color:
? Choose a color: red
 chose red.

automated regression testing with expect

Hey @AlecAivazis, I continue to think about how to best use the files in your tests directory. I think expect is probably the way to go.

I think the easiest thing to do is use autoexpect which will automatically generate an expect script based on watching you interact with a program. For example I ran it on ask.go like:

$ autoexpect -f ask.exp go run ask.go
autoexpect started, file is ask.exp
Asking many.
? What is your name? Larry Bird
? Choose a color: blue
? What is your name? Larry Wall
Answered with Larry Wall.
Asking one with validation.
✘ Sorry, your reply was invalid: Value is required
? What is your name? Larry King
Answered with Larry King.
autoexpect done, file is ask.exp

It automatically captured all the input and output for that process and turned it into a script that can be directly executed to recreate that exact sequence. So if you run it the output will be the same:

$ ./ask.exp
spawn go run ask.go
Asking many.
? What is your name? Larry Bird
? Choose a color: blue
? What is your name? Larry Wall
Answered with Larry Wall.
Asking one with validation.
✘ Sorry, your reply was invalid: Value is required
? What is your name? Larry King
Answered with Larry King.

The ask.exp script that was generated will have all the output captured, even all the CSI codes.

Here is a useful snippet of ask.exp:

expect -exact "Asking many.\r
^[\[2K^[\[1;92m? ^[\[0m^[\[1;99mWhat is your name? ^[\[0m^[\[37m(Johnny Appleseed) ^[\[0m"
send -- "Larry Bird\r"
expect -exact "Larry Bird\r
^[\[1A^[\[2K^[\[1;92m? ^[\[0m^[\[1;99mWhat is your name? ^[\[0m^[\[36mLarry Bird^[\[0m\r
^[\[2K^[\[1;92m? ^[\[0m^[\[1;99mChoose a color:^[\[0m\r
^[\[1;99m  red^[\[0m\r
^[\[1;99m  blue^[\[0m\r
^[\[1;99m  green^[\[0m\r
^[\[1;36m> yellow^[\[0m\r
^[\[?25l^[\[2K^[\[1F^[\[2K^[\[1F^[\[2K^[\[1F^[\[2K^[\[1F^[\[2K^[\[1F^[\[2K^[\[1;92m? ^[\[0m^[\[1;99mChoose a color:^[\[0m\r
^[\[1;99m  red^[\[0m\r
^[\[1;99m  blue^[\[0m\r
^[\[1;99m  green^[\[0m\r
^[\[1;36m> yellow^[\[0m\r
"

Here we can see that I answered Larry Bird (the send) for the first name prompt, then the tty echo's that name back, then the prompt is rewritten with the name (Answer) colored in, followed by the "Choose a color" prompt (twice because Select.OnChange is called automatically with nil list on readline.SetConfig)

It might be useful if you want to just generate autoexpect scripts for each of these tests for the input you have been using to test with. Then we can just run them in a quick loop to make sure they all execute without error for a quick and easy regression test.

Not sure what platform, but if you use OSX you can easily install with brew install expect (which comes with autoexpect).

[bugs] validation.go run error

golang version 18
MacOS 10.12.4

$ go run examples/validation.go

command-line-arguments

examples/validation.go:13: cannot use "What is your name?" (type string) as type core.Renderer in field value
examples/validation.go:13: too few values in struct initializer
examples/validation.go:18: cannot use "Enter 'foo':" (type string) as type core.Renderer in field value
examples/validation.go:18: too few values in struct initializer

Allow users to set output stream

It users like this library is printing interactive dialogues to stdout. This is making it difficult to write programs that prompt questions, which then prints something to stdout users can redirect to a file and use.

e.g. A program like

keyutil generate --to-file=- | jq .

Would print to stdout, which then would be consumed by the next program in the pipeline. However currently all questions go to stdout, making this impossible.

gokpg.in version uses github/.../core instead of gopkg.in/.../core

Hi,

I'm hope I'm not holding it wrong, but I just ran into the following issue which took me some time to debug. Following https://github.com/AlecAivazis/survey#customizing-output, I tried the following:

package main

import (
	surveyCore "gopkg.in/AlecAivazis/survey.v1/core"
	"gopkg.in/AlecAivazis/survey.v1"
)

func main() {
	surveyCore.QuestionIcon = "???"

	options := []string{"Chicken", "Egg"}
	var ret string
	prompt := &survey.Select{
		Message: "Which came first, the chicken or the egg?",
		Options: options,
	}
	survey.AskOne(prompt, &ret, nil)
}

However, that does not change the question icon. After some debugging, I found that I need to reference the GitHub package to get it work:

diff --git a/icon.go b/icon_fixed.go
index b94e586..8e09228 100644
--- a/icon.go
+++ b/icon_fixed.go
@@ -1,8 +1,8 @@
 package main

 import (
+       surveyCore "github.com/AlecAivazis/survey/core"
        "gopkg.in/AlecAivazis/survey.v1"
-       surveyCore "gopkg.in/AlecAivazis/survey.v1/core"
 )

 func main() {

Without being a Go expert, this seems wrong as it effectively breaks semantic versioning?

Thanks for the fantastic project!

Help field for inputs does not seem to be working

Getting an error of 'unknown field 'Help' in struct literal of type survey.Input' when running something like this:

Prompt: &survey.Input{
			Message: "What is the site domain?",
			Help:    "The site name DOES contain the TLD (like '.com')",
		},

Add support for MultiValue prompts

This is slightly different than multi-select in that it can be used to gather things like "tags".
e.g.:
? enter a tag:
user enters: "golang"
? add another (y/n)
user enters "y"
? enter a tag:
user enters "github"
? add another (y/n)
user enters "n"
... move on to next question

A simple way to ask one question at a time

I have a situation where a fair amount of logic needs to happen between each question, and it would be good to be able to do something like this:

answer, err = survey.AskOne(&survey.Choice{
	Message: "...",
	Choices: []string{"..."},
})

What do you think?

usability issues for Select and MultiSelect with many options

As I run through my projects and replace prompts with survey I found a few minor usability issues with Select and MultiSelect. Specifically if they have many options it can be too much to present all at once. So here is a dumb example:

? Pick a number:
  1
  2
  3
  4
❯ 5
  6
  7
  8
  9
  10

I would like to add a property to limit the view, perhaps ViewSize so we can focus on just a few options. So setting ViewSize: 5 we would see something like:

? Pick a number:
  3
  4
❯ 5
  6
  7

But we would probably need some markers to indicate there is more Up or Down, so maybe something more like:

? Pick a number:
▲ 3
  4
❯ 5
  6
▼ 7

The next issue is that I would like to enable text focusing for long lists. If you have a Select list of all the US States, you currently would have to hit the Down arrow 49 times to get to Wyoming. Ideally we allow users to just type in W and jump the focus to the closest match starting with the letter.

Document where answers can be written to

I've been collecting answers to survey.MultiSelect etc. prompts into a []string. But from the source code it looks like it's possible to use structs/maps too?

func WriteAnswer(t interface{}, name string, v interface{}) (err error) {

If that's the case, Ask/AskOne methods that visibly accept t interface{} should probably document this behavior what t's type should/can be.

Add support for map[string]interface{} as a result

Currently you can pass a struct with fields to gather answers, however this is not very dynamic and requires the fields to be known up front.

It would be great to be able to pass map[string]interface{} as the thing to populate answers, however currently it fails due to type casting...
reflect.Set: value of type string is not assignable to type map[string]interface {}

Conditionally prevent signals trapping

It seems that a simple survey like this:

ans := false
prompt := &survey.Confirm{
	Message: prompt_message,
}
survey.AskOne(prompt, &ans, nil)

is overriding SIGINT action, preventing my own application to apply its own trapping procedure.
Actually don't know if it's the way it's supposed to work, but in this case I need a way to prevent it to move that way.

Survey forces me to use []survey.Question for every occasion

Is it possible to use Validate field from []survey.Question within any other function, except Ask? So it goes like every time we need to use Validate or Transform, we have to create a new question array and use it only with Ask, even if it isn't supposed to ask more, than one question. To make things less complicated, I suggest to leave only Ask function cause it carries all the stuff other functions do.

Support MultiSelect/Select for survey.Required validator

I think the survey.Required validator logically makes sense for Select/MultiSelect, too.

However using it as a validator in MultiSelect currently gives the following error:

panic: runtime error: comparing uncomparable type []string

goroutine 1 [running]:
github.com/AlecAivazis/survey.Required(0x145c0e0, 0xc420216620, 0xc420216620, 0x0)
	/Users/ahmetb/workspace/gopath-iamutil/src/github.com/AlecAivazis/survey/validate.go:12 +0xc7
github.com/AlecAivazis/survey.Ask(0xc4200b1b90, 0x1, 0x1, 0x1447700, 0xc4201fb1e0, 0x80, 0x1500de0)
	/Users/ahmetb/workspace/gopath-iamutil/src/github.com/AlecAivazis/survey/survey.go:61 +0x16e
github.com/AlecAivazis/survey.AskOne(0x1762aa0, 0xc4202c7300, 0x1447700, 0xc4201fb1e0, 0x15450a8, 0x0, 0x0)
	/Users/ahmetb/workspace/gopath-iamutil/src/github.com/AlecAivazis/survey/survey.go:32 +0xbe

It would be cool to use this validator (or a new one, like survey.HasAnswer) that works with slice semantics.

Tab-completion on file paths for input

I realize this might be super challenging, but one of the things I have in my cli app using survey is a place for users to enter the path to a local file. Tab-completion of the path would be super great. Has anyone taken a whack at this?

two selects in a row don't save correct value

If you have two select blocks in a row and on the second selection you choose the first option without moving your cursor at all, it will save the value from the first selection block instead.

var simpleQs = []*survey.Question{
	{
		Name: "color",
		Prompt: &survey.Select{
			Message: "Choose a color:",
			Options: []string{"red", "blue", "green"},
		},
		Validate: survey.Required,
	},
	{
		Name: "color2",
		Prompt: &survey.Select{
			Message: "Choose a color:",
			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)
}

In that example, if you select "blue" and then "red" the expected output is "blue and red" and the actual output is "blue and blue".

Adding further questions after a select is breaking the package.

Putting further questions after a select question does not operate properly.

I will include a slightly modified version of your example/simple.go, where all I did was switch the order of the questions, so you can run it and see:

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)
}

If the format of the questions is incorrect, please inform on the correct way as the ReadMe seems to have two different ways to do it. One in the large example near the beginning, and one in the smaller examples below.

Thank you in advance for your help!

Input prompt on Answer renders wrong if answer is ""

Creating issue for problem I saw working on #55. The template has:

{{- if .Answer}}
  {{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}

So if the default answer is "" then it evaluates to false and the template shows the prompt again. We probbaly need a .ShowAnswer boolean flag for the template to know when we need to show an answer for a value that will be evaluated to false by text/template.

MultiSelect fails to render a long list

Below you can see a picture of a long list passed to MultiSelect. Issues:

  • default view is the end of the list, even though the cursor is by default at item 0.
  • keeps showing only the end of list no matter where the cursor is moved to.
    • never shows the earlier items. I tried bringing cursor all the way to the end and tried to bring it above Item 102. It never showed Item 101 or things before it.

image

MultiSelect should:

  • adapt to the dynamic terminal height
  • slide the options window displayed along with the cursor

Is there a way to pass a different value on a Select prompt?

I have a Select prompt, and would like to display one thing to the user, but return a different value than what is being displayed.

That is to say, it will show "George Bluth", but would like it to instead return the value of "george-bluth", which is something i could have in a map, let's say.

In this particular case, I can transform the value myself (since it's simple toLower and string replacement), but in other cases it might be more arbitrary.

confirm and input prompts not working on windows

copying from discussion started in #51.

I ran go run tests\confirm.go and saw:

Enter 'yes'
? yes: (y/N) 
             ? yes: Yes
Answered true.
---------------------
Enter 'no'
? yes: (y/N)
             ? yes: No
Answered false.
---------------------

Select/Multiselect seems to work, but the recent changes to deal with the Help prompt rewriting seems to have broken the input and confirm prompts on windows.

Using cmd.exe on Window 10.

Default value not assigned into answers?

First, thank you for creating this great module!
I'm using this module like following, but the default value not assigned to the answer variable...

import "gopkg.in/AlecAivazis/survey.v1"

var questions = []*survey.Question{
	{
		Name: "project_name",
		Prompt: &survey.Input{"What is the project name?", ""},
		Validate: survey.Required,
	},
	{
		Name: "author",
		Prompt: &survey.Input{"Who are you?", ""},
	},
	{
		Name: "version",
		Prompt: &survey.Input{"What version is this app right now?", "0.0.0"},
	},
}

type Answers struct {
	ProjectName string `survey:"project_name"`
	Author string `survey:"author"`
	Version string `survey:"version"`
}

answers := Answers{}

err := survey.Ask(questions, &answers)
if err != nil {
	fmt.Println(err.Error())
}
fmt.Println("Answers", answers)

The answer always be like this

? What is the project name? foo
? Who are you? 
? What version is this app right now? (0.0.0)
Answers {foo  }

Did I make a mistake?

MultiSelect does not support cycling through the list

If I hit Up key at the beginning of the list, it should go to the item at the end of the list.

Similarly, if I hit Down at the end of the list, it should cycle back to the item at the beginning of the list.

This behavior is supported by Inquirer.js.

Modify API to allow for non-string result from prompts?

@AlecAivazis, creating an issue to track discussions started in [#28] and [#30] regarding potential API changes to encapsulate variable data returned from prompts (example: MultiChoice could return an []string and Confirm could return a bool).

Thinking about your last suggestion, if I understand what you are saying the usage would look roughly like:

    var simpleQs = []*survey.Question{{
        Name: "name",
    }, {
        Name: "color",
    }, {
        Name: "days",
    }, {
        Name: "happy",
    }}

    answers := struct {
        Name  string
        Color string
        Days  []string
        Happy bool
    }{}

    err := survey.Ask(simpleQs, &answers)

Or more with tags:

    answers := struct {
        UserName   string   `survey:"name"`
        FavColor   string   `survey:"color"`
        PreferDays []string `survey:"days"`
        IsHappy    bool     `survey:"happy"`
    }{}

    err := survey.Ask(simpleQs, &answers)

How would we extend AskOne to work on interface values as well? Perhaps something like:

days := []string{}
err := survey.AskOne(prompt, &days)

I think the usage of this is good from a user perspective. However I think the implementation will be reasonably tricky though (ie lots of code) and we will need to do lots of reflection (which will potentially move some compile-time errors to runtime-errors). So I think it is do-able but non-trivial, and I am not sure the implementation complexity cost would justify the relative ease of use in the API.

Use more compatible symbols by default

The default customization option values, while nice looking, are not extraordinarily compatible with the average user's font face. This is especially relevant for a library like survey. Which will probably be used in applications which interact with less technical users.

It would be nice if survey shipped with some more compatible defaults:

core.ErrorIcon = "X"
core.HelpIcon = "????"
core.QuestionIcon = "?"
core.SelectFocusIcon = ">"
core.MarkedOptionIcon = "[x]"
core.UnmarkedOptionIcon = "[ ]"

They still end up looking pretty nice looking:

2017-09-07-150747_1920x1080_scrot

MultiSelect scrolling not obvious

When I presented a MultiSelect with a long list it's not clear to the user whether there are more items beyond Item 6 (there is 100 more items).

image

Inquirer.js communicates this better as they print a gray (Move up and down to reveal more choices) line at the bottom:

image

I see there's an empty line at the end of MultiSelect in survey package currently. Did you mean to add this note and forget about it perhaps?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.