README.md
1# Survey
2
3[![Build Status](https://travis-ci.org/AlecAivazis/survey.svg?branch=feature%2Fpretty)](https://travis-ci.org/AlecAivazis/survey)
4[![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg)](https://pkg.go.dev/github.com/AlecAivazis/survey/v2)
5
6A library for building interactive prompts on terminals supporting ANSI escape sequences.
7
8<img width="550" src="https://thumbs.gfycat.com/VillainousGraciousKouprey-size_restricted.gif"/>
9
10```go
11package main
12
13import (
14 "fmt"
15 "github.com/AlecAivazis/survey/v2"
16)
17
18// the questions to ask
19var qs = []*survey.Question{
20 {
21 Name: "name",
22 Prompt: &survey.Input{Message: "What is your name?"},
23 Validate: survey.Required,
24 Transform: survey.Title,
25 },
26 {
27 Name: "color",
28 Prompt: &survey.Select{
29 Message: "Choose a color:",
30 Options: []string{"red", "blue", "green"},
31 Default: "red",
32 },
33 },
34 {
35 Name: "age",
36 Prompt: &survey.Input{Message: "How old are you?"},
37 },
38}
39
40func main() {
41 // the answers will be written to this struct
42 answers := struct {
43 Name string // survey will match the question and field names
44 FavoriteColor string `survey:"color"` // or you can tag fields to match a specific name
45 Age int // if the types don't match, survey will convert it
46 }{}
47
48 // perform the questions
49 err := survey.Ask(qs, &answers)
50 if err != nil {
51 fmt.Println(err.Error())
52 return
53 }
54
55 fmt.Printf("%s chose %s.", answers.Name, answers.FavoriteColor)
56}
57```
58
59## Table of Contents
60
611. [Examples](#examples)
621. [Running the Prompts](#running-the-prompts)
631. [Prompts](#prompts)
64 1. [Input](#input)
65 1. [Suggestion Options](#suggestion-options)
66 1. [Multiline](#multiline)
67 1. [Password](#password)
68 1. [Confirm](#confirm)
69 1. [Select](#select)
70 1. [MultiSelect](#multiselect)
71 1. [Editor](#editor)
721. [Filtering Options](#filtering-options)
731. [Validation](#validation)
74 1. [Built-in Validators](#built-in-validators)
751. [Help Text](#help-text)
76 1. [Changing the input rune](#changing-the-input-rune)
771. [Changing the Icons ](#changing-the-icons)
781. [Custom Types](#custom-types)
791. [Testing](#testing)
801. [FAQ](#faq)
81
82## Examples
83
84Examples can be found in the `examples/` directory. Run them
85to see basic behavior:
86
87```bash
88go get github.com/AlecAivazis/survey/v2
89
90cd $GOPATH/src/github.com/AlecAivazis/survey
91
92go run examples/simple.go
93go run examples/validation.go
94```
95
96## Running the Prompts
97
98There are two primary ways to execute prompts and start collecting information from your users: `Ask` and
99`AskOne`. The primary difference is whether you are interested in collecting a single piece of information
100or if you have a list of questions to ask whose answers should be collected in a single struct.
101For most basic usecases, `Ask` should be enough. However, for surveys with complicated branching logic,
102we recommend that you break out your questions into multiple calls to both of these functions to fit your needs.
103
104### Configuring the Prompts
105
106Most prompts take fine-grained configuration through fields on the structs you instantiate. It is also
107possible to change survey's default behaviors by passing `AskOpts` to either `Ask` or `AskOne`. Examples
108in this document will do both interchangeably:
109
110```golang
111prompt := &Select{
112 Message: "Choose a color:",
113 Options: []string{"red", "blue", "green"},
114 // can pass a validator directly
115 Validate: survey.Required,
116}
117
118// or define a default for the single call to `AskOne`
119// the answer will get written to the color variable
120survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))
121
122// or define a default for every entry in a list of questions
123// the answer will get copied into the matching field of the struct as shown above
124survey.Ask(questions, &answers, survey.WithValidator(survey.Required))
125```
126
127## Prompts
128
129### Input
130
131<img src="https://thumbs.gfycat.com/LankyBlindAmericanpainthorse-size_restricted.gif" width="400px"/>
132
133```golang
134name := ""
135prompt := &survey.Input{
136 Message: "ping",
137}
138survey.AskOne(prompt, &name)
139```
140
141#### Suggestion Options
142
143<img src="https://i.imgur.com/Q7POpA1.gif" width="800px"/>
144
145```golang
146file := ""
147prompt := &survey.Input{
148 Message: "inform a file to save:",
149 Suggest: func (toComplete string) []string {
150 files, _ := filepath.Glob(toComplete + "*")
151 return files
152 },
153}
154}
155survey.AskOne(prompt, &file)
156```
157
158### Multiline
159
160<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
161
162```golang
163text := ""
164prompt := &survey.Multiline{
165 Message: "ping",
166}
167survey.AskOne(prompt, &text)
168```
169
170### Password
171
172<img src="https://thumbs.gfycat.com/CompassionateSevereHypacrosaurus-size_restricted.gif" width="400px" />
173
174```golang
175password := ""
176prompt := &survey.Password{
177 Message: "Please type your password",
178}
179survey.AskOne(prompt, &password)
180```
181
182### Confirm
183
184<img src="https://thumbs.gfycat.com/UnkemptCarefulGermanpinscher-size_restricted.gif" width="400px"/>
185
186```golang
187name := false
188prompt := &survey.Confirm{
189 Message: "Do you like pie?",
190}
191survey.AskOne(prompt, &name)
192```
193
194### Select
195
196<img src="https://thumbs.gfycat.com/GrimFilthyAmazonparrot-size_restricted.gif" width="450px"/>
197
198```golang
199color := ""
200prompt := &survey.Select{
201 Message: "Choose a color:",
202 Options: []string{"red", "blue", "green"},
203}
204survey.AskOne(prompt, &color)
205```
206
207Fields and values that come from a `Select` prompt can be one of two different things. If you pass an `int`
208the field will have the value of the selected index. If you instead pass a string, the string value selected
209will be written to the field.
210
211The 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.
212
213By default, the select prompt is limited to showing 7 options at a time
214and will paginate lists of options longer than that. This can be changed a number of ways:
215
216```golang
217// as a field on a single select
218prompt := &survey.MultiSelect{..., PageSize: 10}
219
220// or as an option to Ask or AskOne
221survey.AskOne(prompt, &days, survey.WithPageSize(10))
222```
223
224### MultiSelect
225
226![Example](img/multi-select-all-none.gif)
227
228```golang
229days := []string{}
230prompt := &survey.MultiSelect{
231 Message: "What days do you prefer:",
232 Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
233}
234survey.AskOne(prompt, &days)
235```
236
237Fields and values that come from a `MultiSelect` prompt can be one of two different things. If you pass an `int`
238the field will have a slice of the selected indices. If you instead pass a string, a slice of the string values
239selected will be written to the field.
240
241The 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.
242
243By default, the MultiSelect prompt is limited to showing 7 options at a time
244and will paginate lists of options longer than that. This can be changed a number of ways:
245
246```golang
247// as a field on a single select
248prompt := &survey.MultiSelect{..., PageSize: 10}
249
250// or as an option to Ask or AskOne
251survey.AskOne(prompt, &days, survey.WithPageSize(10))
252```
253
254### Editor
255
256Launches the user's preferred editor (defined by the \$VISUAL or \$EDITOR environment variables) on a
257temporary file. Once the user exits their editor, the contents of the temporary file are read in as
258the result. If neither of those are present, notepad (on Windows) or vim (Linux or Mac) is used.
259
260You can also specify a [pattern](https://golang.org/pkg/io/ioutil/#TempFile) for the name of the temporary file. This
261can be useful for ensuring syntax highlighting matches your usecase.
262
263```golang
264prompt := &survey.Editor{
265 Message: "Shell code snippet",
266 FileName: "*.sh",
267}
268
269survey.AskOne(prompt, &content)
270```
271
272## Filtering Options
273
274By default, the user can filter for options in Select and MultiSelects by typing while the prompt
275is active. This will filter out all options that don't contain the typed string anywhere in their name, ignoring case.
276
277A custom filter function can also be provided to change this behavior:
278
279```golang
280func myFilter(filterValue string, optValue string, optIndex int) bool {
281 // only include the option if it includes the filter and has length greater than 5
282 return strings.Contains(optValue, filterValue) && len(optValue) >= 5
283}
284
285// configure it for a specific prompt
286&Select{
287 Message: "Choose a color:",
288 Options: []string{"red", "blue", "green"},
289 Filter: myFilter,
290}
291
292// or define a default for all of the questions
293survey.AskOne(prompt, &color, survey.WithFilter(myFilter))
294```
295
296## Keeping the filter active
297
298By 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.
299
300However the user can prevent this from happening and keep the filter active for multiple selections in a e.g. MultiSelect:
301
302```golang
303// configure it for a specific prompt
304&Select{
305 Message: "Choose a color:",
306 Options: []string{"light-green", "green", "dark-green", "red"},
307 KeepFilter: true,
308}
309
310// or define a default for all of the questions
311survey.AskOne(prompt, &color, survey.WithKeepFilter(true))
312```
313
314## Validation
315
316Validating individual responses for a particular question can be done by defining a
317`Validate` field on the `survey.Question` to be validated. This function takes an
318`interface{}` type and returns an error to show to the user, prompting them for another
319response. Like usual, validators can be provided directly to the prompt or with `survey.WithValidator`:
320
321```golang
322q := &survey.Question{
323 Prompt: &survey.Input{Message: "Hello world validation"},
324 Validate: func (val interface{}) error {
325 // since we are validating an Input, the assertion will always succeed
326 if str, ok := val.(string) ; !ok || len(str) > 10 {
327 return errors.New("This response cannot be longer than 10 characters.")
328 }
329 return nil
330 },
331}
332
333color := ""
334prompt := &survey.Input{ Message: "Whats your name?" }
335
336// you can pass multiple validators here and survey will make sure each one passes
337survey.AskOne(prompt, &color, survey.WithValidator(survey.Required))
338```
339
340### Built-in Validators
341
342`survey` comes prepackaged with a few validators to fit common situations. Currently these
343validators include:
344
345| name | valid types | description | notes |
346| ------------ | ----------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------- |
347| Required | any | Rejects zero values of the response type | Boolean values pass straight through since the zero value (false) is a valid response |
348| MinLength(n) | string | Enforces that a response is at least the given length | |
349| MaxLength(n) | string | Enforces that a response is no longer than the given length | |
350
351## Help Text
352
353All of the prompts have a `Help` field which can be defined to provide more information to your users:
354
355<img src="https://thumbs.gfycat.com/CloudyRemorsefulFossa-size_restricted.gif" width="400px" style="margin-top: 8px"/>
356
357```golang
358&survey.Input{
359 Message: "What is your phone number:",
360 Help: "Phone number should include the area code",
361}
362```
363
364### Changing the input rune
365
366In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
367looks for with `WithHelpInput`:
368
369```golang
370import (
371 "github.com/AlecAivazis/survey/v2"
372)
373
374number := ""
375prompt := &survey.Input{
376 Message: "If you have this need, please give me a reasonable message.",
377 Help: "I couldn't come up with one.",
378}
379
380survey.AskOne(prompt, &number, survey.WithHelpInput('^'))
381```
382
383## Changing the Icons
384
385Changing the icons and their color/format can be done by passing the `WithIcons` option. The format
386follows the patterns outlined [here](https://github.com/mgutz/ansi#style-format). For example:
387
388```golang
389import (
390 "github.com/AlecAivazis/survey/v2"
391)
392
393number := ""
394prompt := &survey.Input{
395 Message: "If you have this need, please give me a reasonable message.",
396 Help: "I couldn't come up with one.",
397}
398
399survey.AskOne(prompt, &number, survey.WithIcons(func(icons *survey.IconSet) {
400 // you can set any icons
401 icons.Question.Text = "⁇"
402 // for more information on formatting the icons, see here: https://github.com/mgutz/ansi#style-format
403 icons.Question.Format = "yellow+hb"
404}))
405```
406
407The icons and their default text and format are summarized below:
408
409| name | text | format | description |
410| -------------- | ---- | ---------- | ------------------------------------------------------------- |
411| Error | X | red | Before an error |
412| Help | i | cyan | Before help text |
413| Question | ? | green+hb | Before the message of a prompt |
414| SelectFocus | > | green | Marks the current focus in `Select` and `MultiSelect` prompts |
415| UnmarkedOption | [ ] | default+hb | Marks an unselected option in a `MultiSelect` prompt |
416| MarkedOption | [x] | cyan+b | Marks a chosen selection in a `MultiSelect` prompt |
417
418## Custom Types
419
420survey will assign prompt answers to your custom types if they implement this interface:
421
422```golang
423type Settable interface {
424 WriteAnswer(field string, value interface{}) error
425}
426```
427
428Here is an example how to use them:
429
430```golang
431type MyValue struct {
432 value string
433}
434func (my *MyValue) WriteAnswer(name string, value interface{}) error {
435 my.value = value.(string)
436}
437
438myval := MyValue{}
439survey.AskOne(
440 &survey.Input{
441 Message: "Enter something:",
442 },
443 &myval
444)
445```
446
447## Testing
448
449You can test your program's interactive prompts using [go-expect](https://github.com/Netflix/go-expect). The library
450can be used to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY,
451if you are manipulating the cursor or using `survey`, you will need a way to interpret terminal / ANSI escape sequences
452for things like `CursorLocation`. `vt10x.NewVT10XConsole` will create a `go-expect` console that also multiplexes
453stdio to an in-memory [virtual terminal](https://github.com/hinshun/vt10x).
454
455For some examples, you can see any of the tests in this repo.
456
457## FAQ
458
459### What kinds of IO are supported by `survey`?
460survey aims to support most terminal emulators; it expects support for ANSI escape sequences.
461This means that reading from piped stdin or writing to piped stdout is **not supported**,
462and likely to break your application in these situations. See [#337](https://github.com/AlecAivazis/survey/pull/337#issue-581351617)
463
464### Why isn't sending a SIGINT (aka. CTRL-C) signal working?
465
466When you send an interrupt signal to the process, it only interrupts the current prompt instead of the entire process. This manifests in a `github.com/AlecAivazis/survey/v2/terminal.InterruptErr` being returned from `Ask` and `AskOne`. If you want to stop the process, handle the returned error in your code:
467
468```go
469err := survey.AskOne(prompt, &myVar)
470if err == terminal.InterruptErr {
471 fmt.Println("interrupted")
472
473 os.Exit(0)
474} else if err != nil {
475 panic(err)
476}
477```
478
479