alecaivazis / survey Goto Github PK
View Code? Open in Web Editor NEWA golang library for building interactive and accessible prompts with full support for windows and posix terminals.
License: MIT License
A golang library for building interactive and accessible prompts with full support for windows and posix terminals.
License: MIT License
Would be great to see hierarchical prompt support as in inquirer with when
object -- inquirer/objects.
This was really nicely implemented in yeoman yeoman/generator#278
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.
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?
Line 19 in 6ee49f5
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.
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
.
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
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:
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.
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')",
},
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).
Inquirer.js communicates this better as they print a gray (Move up and down to reveal more choices)
line at the bottom:
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?
Currently, some releases are incorrectly tagged in GitHub (missing the v
prefix before e.g. 1.3.1
):
This causes gopkg
to not pick up these versions:
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
¯_(ツ)_/¯
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.
Inquirer solve this with a when
property but there are probably other options too
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!
When a question uses survey.Required to validate the input, it forbids quitting with Ctrl+C. That doesn't seem useful to me. 😉 Is that intentional?
Currently, the Choice prompt's selected option is not displayed as bold which causes a slight jump as the text flips between selected and non-selected state.
cc @coryb
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.
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!
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?
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.
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.
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.
This is to better reflect Inquirer and it makes sense since it's not always a question (the readme for example)
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".
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
).
golang version 18
MacOS 10.12.4
$ go run examples/validation.go
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
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.
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.
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?
Answer area to the survey.Input question cannot be deleted with the Ctrl+W key (which is supported by most terminal emulators, such as iTerm).
Something tells me the logic around cleanup when an input is on the last line is incorrect. I need to look into that
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
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.
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.
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 {}
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?
The code panics if you try to use a MultiSelect question with the built in Required validator.
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.
@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.
This attribute is a function that mutates the choice value into a different internal value
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.
Inquirer styles this in grey surrounded by ()
after the message
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.
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.code
s.
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:
map[string]product
) mapping and after getting answers, look it up from there.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 []int
s, 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.
The idea is to be able to switch between the two experiences in inquirer and npm
Testing the same shells, prompt themes, etc, I get clean output on OSX but extra color codes on linux (ubuntu/debian).
This might just be an issue with buger/goterm itself. This issue suggests color code issues could possibly be mitigated with https://github.com/fatih/color.
Below you can see a picture of a long list passed to MultiSelect. Issues:
MultiSelect should:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.