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