mirror of
https://github.com/openshift/installer.git
synced 2026-02-05 15:47:14 +01:00
vendor/AlecAivazis/survey: Bump API to v2
This commit is contained in:
2
go.mod
2
go.mod
@@ -4,6 +4,7 @@ go 1.14
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.65.0
|
||||
github.com/AlecAivazis/survey/v2 v2.2.12
|
||||
github.com/Azure/azure-sdk-for-go v51.2.0+incompatible
|
||||
github.com/Azure/go-autorest/autorest v0.11.17
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.10
|
||||
@@ -95,7 +96,6 @@ require (
|
||||
google.golang.org/api v0.33.0
|
||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a
|
||||
google.golang.org/grpc v1.32.0
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.9-0.20200217094205-6773bdf39b7f
|
||||
gopkg.in/ini.v1 v1.61.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
k8s.io/api v0.21.0-rc.0
|
||||
|
||||
5
go.sum
5
go.sum
@@ -33,7 +33,8 @@ contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRq
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/14rcole/gopopulate v0.0.0-20180821133914-b175b219e774/go.mod h1:6/0dYRLLXyJjbkIPeeGyoJ/eKOSI0eU6eTlCBYibgd0=
|
||||
github.com/360EntSecGroup-Skylar/excelize v1.4.1/go.mod h1:vnax29X2usfl7HHkBrX5EvSCJcmH3dT9luvxzu8iGAE=
|
||||
github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.12 h1:5a07y93zA6SZ09gOa9wLVLznF5zTJMQ+pJ3cZK4IuO8=
|
||||
github.com/AlecAivazis/survey/v2 v2.2.12/go.mod h1:6d4saEvBsfSHXeN1a5OA5m2+HJ2LuVokllnC77pAIKI=
|
||||
github.com/Azure/azure-sdk-for-go v29.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
github.com/Azure/azure-sdk-for-go v36.2.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||
@@ -2153,8 +2154,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.9-0.20200217094205-6773bdf39b7f h1:AQkMzsSzHWrgZWqGRpuRaRPDmyNibcXlpGcnQJ7HxZw=
|
||||
gopkg.in/AlecAivazis/survey.v1 v1.8.9-0.20200217094205-6773bdf39b7f/go.mod h1:CaHjv79TCgAvXMSFJSVgonHXYWxnhzI3eoHtnX5UgUo=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
|
||||
@@ -9,7 +9,7 @@ os:
|
||||
- osx
|
||||
- windows
|
||||
|
||||
go_import_path: gopkg.in/AlecAivazis/survey.v1
|
||||
go_import_path: github.com/AlecAivazis/survey/v2
|
||||
|
||||
before_install:
|
||||
- go get github.com/alecaivazis/run
|
||||
@@ -19,11 +19,12 @@ This project and its contibutors are expected to uphold the [Go Community Code o
|
||||
|
||||
## Getting help
|
||||
|
||||
Feel free to [open up an issue](https://github.com/AlecAivazis/survey/issues/new) on GitHub when asking a question so others will be able to find it. Please remember to tag the issue with the `Question` label so the maintainers can get to your question as soon as possible. If the question is urgent, feel free to reach out to `@AlecAivazis` directly in the gophers slack channel.
|
||||
Feel free to [open up an issue](https://github.com/AlecAivazis/survey/v2/issues/new) on GitHub when asking a question so others will be able to find it. Please remember to tag the issue with the `Question` label so the maintainers can get to your question as soon as possible. If the question is urgent, feel free to reach out to `@AlecAivazis` directly in the gophers slack channel.
|
||||
|
||||
## How to file a bug report
|
||||
|
||||
Bugs are tracked using the Github Issue tracker. When filing a bug, please remember to label the issue as a `Bug` and answer/provide the following:
|
||||
|
||||
1. What operating system and terminal are you using?
|
||||
1. An example that showcases the bug.
|
||||
1. What did you expect to see?
|
||||
@@ -31,20 +32,20 @@ Bugs are tracked using the Github Issue tracker. When filing a bug, please remem
|
||||
|
||||
## Suggesting an API change
|
||||
|
||||
If you have an idea, I'm more than happy to discuss it. Please open an issue labeled `Discussion` and we can work through it. In order to maintain some sense of stability, additions to the top-level API are taken just as seriously as changes that break it. Adding stuff is much easier than removing it.
|
||||
If you have an idea, I'm more than happy to discuss it. Please open an issue and we can work through it. In order to maintain some sense of stability, additions to the top-level API are taken just as seriously as changes that break it. Adding stuff is much easier than removing it.
|
||||
|
||||
## Submitting a contribution
|
||||
|
||||
In order to maintain stability, most features get fully integrated in more than one PR. This allows for more opportunity to think through each API change without amassing large amounts of tech debt and API changes at once. If your feature can be broken into separate chunks, it will be able to be reviewed much quicker. For example, if the PR that implemented the `Validate` field was submitted in a PR separately from one that included `survey.Required`, it would be able to get merge without having to decide how many different `Validators` we want to provide as part of `survey`'s API.
|
||||
In order to maintain stability, most features get fully integrated in more than one PR. This allows for more opportunity to think through each API change without amassing large amounts of tech debt and API changes at once. If your feature can be broken into separate chunks, it will be able to be reviewed much quicker. For example, if the PR that implemented the `Validate` field was submitted in a PR separately from one that included `survey.Required`, it would be able to get merge without having to decide how many different `Validators` we want to provide as part of `survey`'s API.
|
||||
|
||||
When submitting a contribution,
|
||||
* Provide a description of the feature or change
|
||||
* Reference the ticket addressed by the PR if there is one
|
||||
* Following community standards, add comments for all exported members so that all necessary information is available on godocs
|
||||
* Remember to update the project README.md with changes to the high-level API
|
||||
* Include both positive and negative unit tests (when applicable)
|
||||
* Contributions with visual ramifications or interaction changes should be accompanied with the appropriate `go-expect` tests. For more information on writing these tests, see [Writing and Running Tests](#writing-and-running-tests)
|
||||
|
||||
- Provide a description of the feature or change
|
||||
- Reference the ticket addressed by the PR if there is one
|
||||
- Following community standards, add comments for all exported members so that all necessary information is available on godocs
|
||||
- Remember to update the project README.md with changes to the high-level API
|
||||
- Include both positive and negative unit tests (when applicable)
|
||||
- Contributions with visual ramifications or interaction changes should be accompanied with the appropriate `go-expect` tests. For more information on writing these tests, see [Writing and Running Tests](#writing-and-running-tests)
|
||||
|
||||
## Writing and running tests
|
||||
|
||||
@@ -74,5 +75,3 @@ For example, you can extend the tests for Input by specifying the following test
|
||||
If you want to write your own `go-expect` test from scratch, you'll need to instantiate a virtual terminal,
|
||||
multiplex it into an `*expect.Console`, and hook up its tty with survey's optional stdio. Please see `go-expect`
|
||||
[documentation](https://godoc.org/github.com/Netflix/go-expect) for more detail.
|
||||
|
||||
|
||||
478
vendor/github.com/AlecAivazis/survey/v2/README.md
generated
vendored
Normal file
478
vendor/github.com/AlecAivazis/survey/v2/README.md
generated
vendored
Normal file
@@ -0,0 +1,478 @@
|
||||
# Survey
|
||||
|
||||
[](https://travis-ci.org/AlecAivazis/survey)
|
||||
[](https://pkg.go.dev/github.com/AlecAivazis/survey/v2)
|
||||
|
||||
A library for building interactive prompts on terminals supporting ANSI escape sequences.
|
||||
|
||||
<img width="550" src="https://thumbs.gfycat.com/VillainousGraciousKouprey-size_restricted.gif"/>
|
||||
|
||||
```go
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Examples](#examples)
|
||||
1. [Running the Prompts](#running-the-prompts)
|
||||
1. [Prompts](#prompts)
|
||||
1. [Input](#input)
|
||||
1. [Suggestion Options](#suggestion-options)
|
||||
1. [Multiline](#multiline)
|
||||
1. [Password](#password)
|
||||
1. [Confirm](#confirm)
|
||||
1. [Select](#select)
|
||||
1. [MultiSelect](#multiselect)
|
||||
1. [Editor](#editor)
|
||||
1. [Filtering Options](#filtering-options)
|
||||
1. [Validation](#validation)
|
||||
1. [Built-in Validators](#built-in-validators)
|
||||
1. [Help Text](#help-text)
|
||||
1. [Changing the input rune](#changing-the-input-rune)
|
||||
1. [Changing the Icons ](#changing-the-icons)
|
||||
1. [Custom Types](#custom-types)
|
||||
1. [Testing](#testing)
|
||||
1. [FAQ](#faq)
|
||||
|
||||
## Examples
|
||||
|
||||
Examples can be found in the `examples/` directory. Run them
|
||||
to see basic behavior:
|
||||
|
||||
```bash
|
||||
go get github.com/AlecAivazis/survey/v2
|
||||
|
||||
cd $GOPATH/src/github.com/AlecAivazis/survey
|
||||
|
||||
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:
|
||||
|
||||
```golang
|
||||
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
|
||||
|
||||
<img src="https://thumbs.gfycat.com/LankyBlindAmericanpainthorse-size_restricted.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
name := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "ping",
|
||||
}
|
||||
survey.AskOne(prompt, &name)
|
||||
```
|
||||
|
||||
#### Suggestion Options
|
||||
|
||||
<img src="https://i.imgur.com/Q7POpA1.gif" width="800px"/>
|
||||
|
||||
```golang
|
||||
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
|
||||
|
||||
<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
text := ""
|
||||
prompt := &survey.Multiline{
|
||||
Message: "ping",
|
||||
}
|
||||
survey.AskOne(prompt, &text)
|
||||
```
|
||||
|
||||
### Password
|
||||
|
||||
<img src="https://thumbs.gfycat.com/CompassionateSevereHypacrosaurus-size_restricted.gif" width="400px" />
|
||||
|
||||
```golang
|
||||
password := ""
|
||||
prompt := &survey.Password{
|
||||
Message: "Please type your password",
|
||||
}
|
||||
survey.AskOne(prompt, &password)
|
||||
```
|
||||
|
||||
### Confirm
|
||||
|
||||
<img src="https://thumbs.gfycat.com/UnkemptCarefulGermanpinscher-size_restricted.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
name := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: "Do you like pie?",
|
||||
}
|
||||
survey.AskOne(prompt, &name)
|
||||
```
|
||||
|
||||
### Select
|
||||
|
||||
<img src="https://thumbs.gfycat.com/GrimFilthyAmazonparrot-size_restricted.gif" width="450px"/>
|
||||
|
||||
```golang
|
||||
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:
|
||||
|
||||
```golang
|
||||
// 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))
|
||||
```
|
||||
|
||||
### MultiSelect
|
||||
|
||||

|
||||
|
||||
```golang
|
||||
days := []string{}
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
}
|
||||
survey.AskOne(prompt, &days)
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```golang
|
||||
// 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](https://golang.org/pkg/io/ioutil/#TempFile) for the name of the temporary file. This
|
||||
can be useful for ensuring syntax highlighting matches your usecase.
|
||||
|
||||
```golang
|
||||
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:
|
||||
|
||||
```golang
|
||||
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:
|
||||
|
||||
```golang
|
||||
// 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`:
|
||||
|
||||
```golang
|
||||
q := &survey.Question{
|
||||
Prompt: &survey.Input{Message: "Hello world validation"},
|
||||
Validate: func (val interface{}) error {
|
||||
// since we are validating an Input, the assertion will always succeed
|
||||
if str, ok := val.(string) ; !ok || len(str) > 10 {
|
||||
return errors.New("This response cannot be longer than 10 characters.")
|
||||
}
|
||||
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 | |
|
||||
|
||||
## Help Text
|
||||
|
||||
All of the prompts have a `Help` field which can be defined to provide more information to your users:
|
||||
|
||||
<img src="https://thumbs.gfycat.com/CloudyRemorsefulFossa-size_restricted.gif" width="400px" style="margin-top: 8px"/>
|
||||
|
||||
```golang
|
||||
&survey.Input{
|
||||
Message: "What is your phone number:",
|
||||
Help: "Phone number should include the area code",
|
||||
}
|
||||
```
|
||||
|
||||
### Changing the input rune
|
||||
|
||||
In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
|
||||
looks for with `WithHelpInput`:
|
||||
|
||||
```golang
|
||||
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](https://github.com/mgutz/ansi#style-format). For example:
|
||||
|
||||
```golang
|
||||
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:
|
||||
|
||||
```golang
|
||||
type Settable interface {
|
||||
WriteAnswer(field string, value interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example how to use them:
|
||||
|
||||
```golang
|
||||
type MyValue struct {
|
||||
value string
|
||||
}
|
||||
func (my *MyValue) WriteAnswer(name string, value interface{}) error {
|
||||
my.value = value.(string)
|
||||
}
|
||||
|
||||
myval := MyValue{}
|
||||
survey.AskOne(
|
||||
&survey.Input{
|
||||
Message: "Enter something:",
|
||||
},
|
||||
&myval
|
||||
)
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
You can test your program's interactive prompts using [go-expect](https://github.com/Netflix/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](https://github.com/hinshun/vt10x).
|
||||
|
||||
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](https://github.com/AlecAivazis/survey/pull/337#issue-581351617)
|
||||
|
||||
### Why isn't sending a SIGINT (aka. CTRL-C) signal working?
|
||||
|
||||
When 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:
|
||||
|
||||
```go
|
||||
err := survey.AskOne(prompt, &myVar)
|
||||
if err == terminal.InterruptErr {
|
||||
fmt.Println("interrupted")
|
||||
|
||||
os.Exit(0)
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -8,12 +8,12 @@ task "install-deps" {
|
||||
task "tests" {
|
||||
description = "Run the test suite"
|
||||
command = "go test {{.files}}"
|
||||
environment {
|
||||
environment = {
|
||||
GOFLAGS = "-mod=vendor"
|
||||
}
|
||||
}
|
||||
|
||||
variables {
|
||||
variables = {
|
||||
files = "$(go list -v ./... | grep -iEv \"tests|examples\")"
|
||||
}
|
||||
|
||||
@@ -3,13 +3,11 @@ package survey
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
)
|
||||
|
||||
// Confirm is a regular text input that accept yes/no answers. Response type is a bool.
|
||||
type Confirm struct {
|
||||
core.Renderer
|
||||
Renderer
|
||||
Message string
|
||||
Default bool
|
||||
Help string
|
||||
@@ -20,17 +18,18 @@ type ConfirmTemplateData struct {
|
||||
Confirm
|
||||
Answer string
|
||||
ShowHelp bool
|
||||
Config *PromptConfig
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var ConfirmQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .Answer}}
|
||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
||||
{{- color "white"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}}
|
||||
{{- end}}`
|
||||
|
||||
@@ -47,7 +46,7 @@ func yesNo(t bool) string {
|
||||
return "No"
|
||||
}
|
||||
|
||||
func (c *Confirm) getBool(showHelp bool) (bool, error) {
|
||||
func (c *Confirm) getBool(showHelp bool, config *PromptConfig) (bool, error) {
|
||||
cursor := c.NewCursor()
|
||||
rr := c.NewRuneReader()
|
||||
rr.SetTermMode()
|
||||
@@ -72,10 +71,14 @@ func (c *Confirm) getBool(showHelp bool) (bool, error) {
|
||||
answer = false
|
||||
case val == "":
|
||||
answer = c.Default
|
||||
case val == string(core.HelpInputRune) && c.Help != "":
|
||||
case val == config.HelpInput && c.Help != "":
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c, ShowHelp: true},
|
||||
ConfirmTemplateData{
|
||||
Confirm: *c,
|
||||
ShowHelp: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// use the default value and bubble up
|
||||
@@ -85,12 +88,16 @@ func (c *Confirm) getBool(showHelp bool) (bool, error) {
|
||||
continue
|
||||
default:
|
||||
// we didnt get a valid answer, so print error and prompt again
|
||||
if err := c.Error(fmt.Errorf("%q is not a valid answer, please try again.", val)); err != nil {
|
||||
if err := c.Error(config, fmt.Errorf("%q is not a valid answer, please try again.", val)); err != nil {
|
||||
return c.Default, err
|
||||
}
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c, ShowHelp: showHelp},
|
||||
ConfirmTemplateData{
|
||||
Confirm: *c,
|
||||
ShowHelp: showHelp,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// use the default value and bubble up
|
||||
@@ -110,29 +117,37 @@ by a carriage return.
|
||||
|
||||
likesPie := false
|
||||
prompt := &survey.Confirm{ Message: "What is your name?" }
|
||||
survey.AskOne(prompt, &likesPie, nil)
|
||||
survey.AskOne(prompt, &likesPie)
|
||||
*/
|
||||
func (c *Confirm) Prompt() (interface{}, error) {
|
||||
func (c *Confirm) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// render the question template
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c},
|
||||
ConfirmTemplateData{
|
||||
Confirm: *c,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// get input and return
|
||||
return c.getBool(false)
|
||||
return c.getBool(false, config)
|
||||
}
|
||||
|
||||
// Cleanup overwrite the line with the finalized formatted version
|
||||
func (c *Confirm) Cleanup(val interface{}) error {
|
||||
func (c *Confirm) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
// if the value was previously true
|
||||
ans := yesNo(val.(bool))
|
||||
|
||||
// render the template
|
||||
return c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{Confirm: *c, Answer: ans},
|
||||
ConfirmTemplateData{
|
||||
Confirm: *c,
|
||||
Answer: ans,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
||||
91
vendor/github.com/AlecAivazis/survey/v2/core/template.go
generated
vendored
Normal file
91
vendor/github.com/AlecAivazis/survey/v2/core/template.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/mgutz/ansi"
|
||||
)
|
||||
|
||||
// DisableColor can be used to make testing reliable
|
||||
var DisableColor = false
|
||||
|
||||
var TemplateFuncsWithColor = map[string]interface{}{
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
"color": ansi.ColorCode,
|
||||
}
|
||||
|
||||
var TemplateFuncsNoColor = map[string]interface{}{
|
||||
// Templates without Color formatting. For layout/ testing.
|
||||
"color": func(color string) string {
|
||||
return ""
|
||||
},
|
||||
}
|
||||
|
||||
//RunTemplate returns two formatted strings given a template and
|
||||
//the data it requires. The first string returned is generated for
|
||||
//user-facing output and may or may not contain ANSI escape codes
|
||||
//for colored output. The second string does not contain escape codes
|
||||
//and can be used by the renderer for layout purposes.
|
||||
func RunTemplate(tmpl string, data interface{}) (string, string, error) {
|
||||
tPair, err := getTemplatePair(tmpl)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
userBuf := bytes.NewBufferString("")
|
||||
err = tPair[0].Execute(userBuf, data)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
layoutBuf := bytes.NewBufferString("")
|
||||
err = tPair[1].Execute(layoutBuf, data)
|
||||
if err != nil {
|
||||
return userBuf.String(), "", err
|
||||
}
|
||||
return userBuf.String(), layoutBuf.String(), err
|
||||
}
|
||||
|
||||
var (
|
||||
memoizedGetTemplate = map[string][2]*template.Template{}
|
||||
|
||||
memoMutex = &sync.RWMutex{}
|
||||
)
|
||||
|
||||
//getTemplatePair returns a pair of compiled templates where the
|
||||
//first template is generated for user-facing output and the
|
||||
//second is generated for use by the renderer. The second
|
||||
//template does not contain any color escape codes, whereas
|
||||
//the first template may or may not depending on DisableColor.
|
||||
func getTemplatePair(tmpl string) ([2]*template.Template, error) {
|
||||
memoMutex.RLock()
|
||||
if t, ok := memoizedGetTemplate[tmpl]; ok {
|
||||
memoMutex.RUnlock()
|
||||
return t, nil
|
||||
}
|
||||
memoMutex.RUnlock()
|
||||
|
||||
templatePair := [2]*template.Template{nil, nil}
|
||||
|
||||
templateNoColor, err := template.New("prompt").Funcs(TemplateFuncsNoColor).Parse(tmpl)
|
||||
if err != nil {
|
||||
return [2]*template.Template{}, err
|
||||
}
|
||||
|
||||
templatePair[1] = templateNoColor
|
||||
|
||||
if DisableColor {
|
||||
templatePair[0] = templatePair[1]
|
||||
} else {
|
||||
templateWithColor, err := template.New("prompt").Funcs(TemplateFuncsWithColor).Parse(tmpl)
|
||||
templatePair[0] = templateWithColor
|
||||
if err != nil {
|
||||
return [2]*template.Template{}, err
|
||||
}
|
||||
}
|
||||
|
||||
memoMutex.Lock()
|
||||
memoizedGetTemplate[tmpl] = templatePair
|
||||
memoMutex.Unlock()
|
||||
return templatePair, nil
|
||||
}
|
||||
@@ -6,19 +6,35 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// the tag used to denote the name of the question
|
||||
const tagName = "survey"
|
||||
|
||||
// add a few interfaces so users can configure how the prompt values are set
|
||||
type settable interface {
|
||||
// Settable allow for configuration when assigning answers
|
||||
type Settable interface {
|
||||
WriteAnswer(field string, value interface{}) error
|
||||
}
|
||||
|
||||
// OptionAnswer is the return type of Selects/MultiSelects that lets the appropriate information
|
||||
// get copied to the user's struct
|
||||
type OptionAnswer struct {
|
||||
Value string
|
||||
Index int
|
||||
}
|
||||
|
||||
func OptionAnswerList(incoming []string) []OptionAnswer {
|
||||
list := []OptionAnswer{}
|
||||
for i, opt := range incoming {
|
||||
list = append(list, OptionAnswer{Value: opt, Index: i})
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
||||
// if the field is a custom type
|
||||
if s, ok := t.(settable); ok {
|
||||
if s, ok := t.(Settable); ok {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
@@ -39,6 +55,13 @@ func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
||||
switch elem.Kind() {
|
||||
// if we are writing to a struct
|
||||
case reflect.Struct:
|
||||
// if we are writing to an option answer than we want to treat
|
||||
// it like a single thing and not a place to deposit answers
|
||||
if elem.Type().Name() == "OptionAnswer" {
|
||||
// copy the value over to the normal struct
|
||||
return copy(elem, value)
|
||||
}
|
||||
|
||||
// get the name of the field that matches the string we were given
|
||||
fieldIndex, err := findFieldIndex(elem, name)
|
||||
// if something went wrong
|
||||
@@ -47,13 +70,13 @@ func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
||||
return err
|
||||
}
|
||||
field := elem.Field(fieldIndex)
|
||||
// handle references to the settable interface aswell
|
||||
if s, ok := field.Interface().(settable); ok {
|
||||
// handle references to the Settable interface aswell
|
||||
if s, ok := field.Interface().(Settable); ok {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
if field.CanAddr() {
|
||||
if s, ok := field.Addr().Interface().(settable); ok {
|
||||
if s, ok := field.Addr().Interface().(Settable); ok {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
@@ -63,7 +86,25 @@ func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
||||
return copy(field, value)
|
||||
case reflect.Map:
|
||||
mapType := reflect.TypeOf(t).Elem()
|
||||
if mapType.Key().Kind() != reflect.String || mapType.Elem().Kind() != reflect.Interface {
|
||||
if mapType.Key().Kind() != reflect.String {
|
||||
return errors.New("answer maps key must be of type string")
|
||||
}
|
||||
|
||||
// copy only string value/index value to map if,
|
||||
// map is not of type interface and is 'OptionAnswer'
|
||||
if value.Type().Name() == "OptionAnswer" {
|
||||
if kval := mapType.Elem().Kind(); kval == reflect.String {
|
||||
mt := *t.(*map[string]string)
|
||||
mt[name] = value.FieldByName("Value").String()
|
||||
return nil
|
||||
} else if kval == reflect.Int {
|
||||
mt := *t.(*map[string]int)
|
||||
mt[name] = int(value.FieldByName("Index").Int())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if mapType.Elem().Kind() != reflect.Interface {
|
||||
return errors.New("answer maps must be of type map[string]interface")
|
||||
}
|
||||
mt := *t.(*map[string]interface{})
|
||||
@@ -74,6 +115,45 @@ func WriteAnswer(t interface{}, name string, v interface{}) (err error) {
|
||||
return copy(elem, value)
|
||||
}
|
||||
|
||||
type errFieldNotMatch struct {
|
||||
questionName string
|
||||
}
|
||||
|
||||
func (err errFieldNotMatch) Error() string {
|
||||
return fmt.Sprintf("could not find field matching %v", err.questionName)
|
||||
}
|
||||
|
||||
func (err errFieldNotMatch) Is(target error) bool { // implements the dynamic errors.Is interface.
|
||||
if target != nil {
|
||||
if name, ok := IsFieldNotMatch(target); ok {
|
||||
// if have a filled questionName then perform "deeper" comparison.
|
||||
return name == "" || err.questionName == "" || name == err.questionName
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsFieldNotMatch reports whether an "err" is caused by a non matching field.
|
||||
// It returns the Question.Name that couldn't be matched with a destination field.
|
||||
//
|
||||
// Usage:
|
||||
// err := survey.Ask(qs, &v);
|
||||
// if err != nil {
|
||||
// if name, ok := core.IsFieldNotMatch(err); ok {
|
||||
// [...name is the not matched question name]
|
||||
// }
|
||||
// }
|
||||
func IsFieldNotMatch(err error) (string, bool) {
|
||||
if err != nil {
|
||||
if v, ok := err.(errFieldNotMatch); ok {
|
||||
return v.questionName, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// BUG(AlecAivazis): the current implementation might cause weird conflicts if there are
|
||||
// two fields with same name that only differ by casing.
|
||||
func findFieldIndex(s reflect.Value, name string) (int, error) {
|
||||
@@ -106,7 +186,7 @@ func findFieldIndex(s reflect.Value, name string) (int, error) {
|
||||
}
|
||||
|
||||
// we didn't find the field
|
||||
return -1, fmt.Errorf("could not find field matching %v", name)
|
||||
return -1, errFieldNotMatch{name}
|
||||
}
|
||||
|
||||
// isList returns true if the element is something we can Len()
|
||||
@@ -165,7 +245,11 @@ func copy(t reflect.Value, v reflect.Value) (err error) {
|
||||
castVal = int32(val64)
|
||||
}
|
||||
case reflect.Int64:
|
||||
castVal, casterr = strconv.ParseInt(vString, 10, 64)
|
||||
if t.Type() == reflect.TypeOf(time.Duration(0)) {
|
||||
castVal, casterr = time.ParseDuration(vString)
|
||||
} else {
|
||||
castVal, casterr = strconv.ParseInt(vString, 10, 64)
|
||||
}
|
||||
case reflect.Uint:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
||||
@@ -212,6 +296,32 @@ func copy(t reflect.Value, v reflect.Value) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// if we are copying from an OptionAnswer to something
|
||||
if v.Type().Name() == "OptionAnswer" {
|
||||
// copying an option answer to a string
|
||||
if t.Kind() == reflect.String {
|
||||
// copies the Value field of the struct
|
||||
t.Set(reflect.ValueOf(v.FieldByName("Value").Interface()))
|
||||
return
|
||||
}
|
||||
|
||||
// copying an option answer to an int
|
||||
if t.Kind() == reflect.Int {
|
||||
// copies the Index field of the struct
|
||||
t.Set(reflect.ValueOf(v.FieldByName("Index").Interface()))
|
||||
return
|
||||
}
|
||||
|
||||
// copying an OptionAnswer to an OptionAnswer
|
||||
if t.Type().Name() == "OptionAnswer" {
|
||||
t.Set(v)
|
||||
return
|
||||
}
|
||||
|
||||
// we're copying an option answer to an incorrect type
|
||||
return fmt.Errorf("Unable to convert from OptionAnswer to type %s", t.Kind())
|
||||
}
|
||||
|
||||
// if we are copying from one slice or array to another
|
||||
if isList(v) && isList(t) {
|
||||
// loop over every item in the desired value
|
||||
@@ -7,9 +7,8 @@ import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -23,16 +22,17 @@ Response type is a string.
|
||||
|
||||
message := ""
|
||||
prompt := &survey.Editor{ Message: "What is your commit message?" }
|
||||
survey.AskOne(prompt, &message, nil)
|
||||
survey.AskOne(prompt, &message)
|
||||
*/
|
||||
type Editor struct {
|
||||
core.Renderer
|
||||
Renderer
|
||||
Message string
|
||||
Default string
|
||||
Help string
|
||||
Editor string
|
||||
HideDefault bool
|
||||
AppendDefault bool
|
||||
FileName string
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
@@ -41,17 +41,18 @@ type EditorTemplateData struct {
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
Config *PromptConfig
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var EditorQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .ShowAnswer}}
|
||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
||||
{{- if and .Default (not .HideDefault)}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||
{{- color "cyan"}}[Enter to launch editor] {{color "reset"}}
|
||||
{{- end}}`
|
||||
@@ -72,24 +73,27 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Editor) PromptAgain(invalid interface{}, err error) (interface{}, error) {
|
||||
func (e *Editor) PromptAgain(config *PromptConfig, invalid interface{}, err error) (interface{}, error) {
|
||||
initialValue := invalid.(string)
|
||||
return e.prompt(initialValue)
|
||||
return e.prompt(initialValue, config)
|
||||
}
|
||||
|
||||
func (e *Editor) Prompt() (interface{}, error) {
|
||||
func (e *Editor) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
initialValue := ""
|
||||
if e.Default != "" && e.AppendDefault {
|
||||
initialValue = e.Default
|
||||
}
|
||||
return e.prompt(initialValue)
|
||||
return e.prompt(initialValue, config)
|
||||
}
|
||||
|
||||
func (e *Editor) prompt(initialValue string) (interface{}, error) {
|
||||
func (e *Editor) prompt(initialValue string, config *PromptConfig) (interface{}, error) {
|
||||
// render the template
|
||||
err := e.Render(
|
||||
EditorQuestionTemplate,
|
||||
EditorTemplateData{Editor: *e},
|
||||
EditorTemplateData{
|
||||
Editor: *e,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -118,10 +122,14 @@ func (e *Editor) prompt(initialValue string) (interface{}, error) {
|
||||
if r == terminal.KeyEndTransmission {
|
||||
break
|
||||
}
|
||||
if r == core.HelpInputRune && e.Help != "" {
|
||||
if string(r) == config.HelpInput && e.Help != "" {
|
||||
err = e.Render(
|
||||
EditorQuestionTemplate,
|
||||
EditorTemplateData{Editor: *e, ShowHelp: true},
|
||||
EditorTemplateData{
|
||||
Editor: *e,
|
||||
ShowHelp: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -131,7 +139,11 @@ func (e *Editor) prompt(initialValue string) (interface{}, error) {
|
||||
}
|
||||
|
||||
// prepare the temp file
|
||||
f, err := ioutil.TempFile("", "survey")
|
||||
pattern := e.FileName
|
||||
if pattern == "" {
|
||||
pattern = "survey*.txt"
|
||||
}
|
||||
f, err := ioutil.TempFile("", pattern)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -197,9 +209,14 @@ func (e *Editor) prompt(initialValue string) (interface{}, error) {
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func (e *Editor) Cleanup(val interface{}) error {
|
||||
func (e *Editor) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
return e.Render(
|
||||
EditorQuestionTemplate,
|
||||
EditorTemplateData{Editor: *e, Answer: "<Received>", ShowAnswer: true},
|
||||
EditorTemplateData{
|
||||
Editor: *e,
|
||||
Answer: "<Received>",
|
||||
ShowAnswer: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
||||
1
vendor/github.com/AlecAivazis/survey/v2/filter.go
generated
vendored
Normal file
1
vendor/github.com/AlecAivazis/survey/v2/filter.go
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
package survey
|
||||
19
vendor/github.com/AlecAivazis/survey/v2/go.mod
generated
vendored
Normal file
19
vendor/github.com/AlecAivazis/survey/v2/go.mod
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
module github.com/AlecAivazis/survey/v2
|
||||
|
||||
require (
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/kr/pty v1.1.4
|
||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||
github.com/mattn/go-isatty v0.0.8
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.1
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5
|
||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1 // indirect
|
||||
golang.org/x/text v0.3.3
|
||||
)
|
||||
|
||||
go 1.13
|
||||
@@ -1,26 +1,15 @@
|
||||
github.com/AlecAivazis/survey v1.8.7 h1:QIBq36/0wfYpXxdBqDXNAjKHx1bKnRGu/EDnva27k84=
|
||||
github.com/AlecAivazis/survey/v2 v2.0.5 h1:xpZp+Q55wi5C7Iaze+40onHnEkex1jSc34CltJjOoPM=
|
||||
github.com/AlecAivazis/survey/v2 v2.0.5/go.mod h1:WYBhg6f0y/fNYUuesWQc0PKbJcEliGcYHB9sNT3Bg74=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
|
||||
github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||
@@ -29,16 +18,19 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b h1:Elez2XeF2p9uyVj0yEUDqQ56NFcDtcBNkYP7yv8YbUE=
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5 h1:8dUaAV7K4uHsF56JQWkprecIQKdPHtR9jCHF5nB8uzc=
|
||||
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b h1:5rOiLYVqtE+JehJPVJTXQJaP8aT3cpJC1Iy22+5WLFU=
|
||||
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1 h1:R4dVlxdmKenVdMRS/tTspEpSTRWINYrHD8ySIU9yCIU=
|
||||
golang.org/x/sys v0.0.0-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
212
vendor/github.com/AlecAivazis/survey/v2/input.go
generated
vendored
Normal file
212
vendor/github.com/AlecAivazis/survey/v2/input.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
Input is a regular text input that prints each character the user types on the screen
|
||||
and accepts the input with the enter key. Response type is a string.
|
||||
|
||||
name := ""
|
||||
prompt := &survey.Input{ Message: "What is your name?" }
|
||||
survey.AskOne(prompt, &name)
|
||||
*/
|
||||
type Input struct {
|
||||
Renderer
|
||||
Message string
|
||||
Default string
|
||||
Help string
|
||||
Suggest func(toComplete string) []string
|
||||
typedAnswer string
|
||||
answer string
|
||||
options []core.OptionAnswer
|
||||
selectedIndex int
|
||||
showingHelp bool
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type InputTemplateData struct {
|
||||
Input
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
Answer string
|
||||
PageEntries []core.OptionAnswer
|
||||
SelectedIndex int
|
||||
Config *PromptConfig
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var InputQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .ShowAnswer}}
|
||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else if .PageEntries -}}
|
||||
{{- .Answer}} [Use arrows to move, enter to select, type to continue]
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $choice := .PageEntries}}
|
||||
{{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
|
||||
{{- $choice.Value}}
|
||||
{{- color "reset"}}{{"\n"}}
|
||||
{{- end}}
|
||||
{{- else }}
|
||||
{{- if or (and .Help (not .ShowHelp)) .Suggest }}{{color "cyan"}}[
|
||||
{{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}}
|
||||
{{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}}
|
||||
]{{color "reset"}} {{end}}
|
||||
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||
{{- .Answer -}}
|
||||
{{- end}}`
|
||||
|
||||
func (i *Input) OnChange(key rune, config *PromptConfig) (bool, error) {
|
||||
if key == terminal.KeyEnter || key == '\n' {
|
||||
if i.answer != config.HelpInput || i.Help == "" {
|
||||
// we're done
|
||||
return true, nil
|
||||
} else {
|
||||
i.answer = ""
|
||||
i.showingHelp = true
|
||||
}
|
||||
} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
|
||||
i.answer = ""
|
||||
} else if key == terminal.KeyEscape && i.Suggest != nil {
|
||||
if len(i.options) > 0 {
|
||||
i.answer = i.typedAnswer
|
||||
}
|
||||
i.options = nil
|
||||
} else if key == terminal.KeyArrowUp && len(i.options) > 0 {
|
||||
if i.selectedIndex == 0 {
|
||||
i.selectedIndex = len(i.options) - 1
|
||||
} else {
|
||||
i.selectedIndex--
|
||||
}
|
||||
i.answer = i.options[i.selectedIndex].Value
|
||||
} else if (key == terminal.KeyArrowDown || key == terminal.KeyTab) && len(i.options) > 0 {
|
||||
if i.selectedIndex == len(i.options)-1 {
|
||||
i.selectedIndex = 0
|
||||
} else {
|
||||
i.selectedIndex++
|
||||
}
|
||||
i.answer = i.options[i.selectedIndex].Value
|
||||
} else if key == terminal.KeyTab && i.Suggest != nil {
|
||||
options := i.Suggest(i.answer)
|
||||
i.selectedIndex = 0
|
||||
i.typedAnswer = i.answer
|
||||
if len(options) > 0 {
|
||||
i.answer = options[0]
|
||||
if len(options) == 1 {
|
||||
i.options = nil
|
||||
} else {
|
||||
i.options = core.OptionAnswerList(options)
|
||||
}
|
||||
}
|
||||
} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
|
||||
if i.answer != "" {
|
||||
runeAnswer := []rune(i.answer)
|
||||
i.answer = string(runeAnswer[0 : len(runeAnswer)-1])
|
||||
}
|
||||
} else if key >= terminal.KeySpace {
|
||||
i.answer += string(key)
|
||||
i.typedAnswer = i.answer
|
||||
i.options = nil
|
||||
}
|
||||
|
||||
pageSize := config.PageSize
|
||||
opts, idx := paginate(pageSize, i.options, i.selectedIndex)
|
||||
err := i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{
|
||||
Input: *i,
|
||||
Answer: i.answer,
|
||||
ShowHelp: i.showingHelp,
|
||||
SelectedIndex: idx,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
|
||||
return err != nil, err
|
||||
}
|
||||
|
||||
func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// render the template
|
||||
err := i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{
|
||||
Input: *i,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// start reading runes from the standard in
|
||||
rr := i.NewRuneReader()
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
cursor := i.NewCursor()
|
||||
if !config.ShowCursor {
|
||||
cursor.Hide() // hide the cursor
|
||||
defer cursor.Show() // show the cursor when we're done
|
||||
}
|
||||
|
||||
// start waiting for input
|
||||
for {
|
||||
r, _, err := rr.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r == terminal.KeyInterrupt {
|
||||
return "", terminal.InterruptErr
|
||||
}
|
||||
if r == terminal.KeyEndTransmission {
|
||||
break
|
||||
}
|
||||
|
||||
b, err := i.OnChange(r, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if b {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// if the line is empty
|
||||
if len(i.answer) == 0 {
|
||||
// use the default value
|
||||
return i.Default, err
|
||||
}
|
||||
|
||||
lineStr := i.answer
|
||||
|
||||
i.AppendRenderedText(lineStr)
|
||||
|
||||
// we're done
|
||||
return lineStr, err
|
||||
}
|
||||
|
||||
func (i *Input) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
// use the default answer when cleaning up the prompt if necessary
|
||||
ans := i.answer
|
||||
if ans == "" && i.Default != "" {
|
||||
ans = i.Default
|
||||
}
|
||||
|
||||
// render the cleanup
|
||||
return i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{
|
||||
Input: *i,
|
||||
ShowAnswer: true,
|
||||
Config: config,
|
||||
Answer: ans,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
type Multiline struct {
|
||||
core.Renderer
|
||||
Renderer
|
||||
Message string
|
||||
Default string
|
||||
Help string
|
||||
@@ -21,12 +19,13 @@ type MultilineTemplateData struct {
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
Config *PromptConfig
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var MultilineQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .ShowAnswer}}
|
||||
{{- "\n"}}{{color "cyan"}}{{.Answer}}{{color "reset"}}
|
||||
@@ -36,16 +35,18 @@ var MultilineQuestionTemplate = `
|
||||
{{- color "cyan"}}[Enter 2 empty lines to finish]{{color "reset"}}
|
||||
{{- end}}`
|
||||
|
||||
func (i *Multiline) Prompt() (interface{}, error) {
|
||||
func (i *Multiline) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// render the template
|
||||
err := i.Render(
|
||||
MultilineQuestionTemplate,
|
||||
MultilineTemplateData{Multiline: *i},
|
||||
MultilineTemplateData{
|
||||
Multiline: *i,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
// start reading runes from the standard in
|
||||
rr := i.NewRuneReader()
|
||||
@@ -92,13 +93,18 @@ func (i *Multiline) Prompt() (interface{}, error) {
|
||||
return i.Default, err
|
||||
}
|
||||
|
||||
// we're done
|
||||
i.AppendRenderedText(val)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (i *Multiline) Cleanup(val interface{}) error {
|
||||
func (i *Multiline) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
return i.Render(
|
||||
MultilineQuestionTemplate,
|
||||
MultilineTemplateData{Multiline: *i, Answer: val.(string), ShowAnswer: true},
|
||||
MultilineTemplateData{
|
||||
Multiline: *i,
|
||||
Answer: val.(string),
|
||||
ShowAnswer: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
||||
331
vendor/github.com/AlecAivazis/survey/v2/multiselect.go
generated
vendored
Normal file
331
vendor/github.com/AlecAivazis/survey/v2/multiselect.go
generated
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
MultiSelect is a prompt that presents a list of various options to the user
|
||||
for them to select using the arrow keys and enter. Response type is a slice of strings.
|
||||
|
||||
days := []string{}
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
}
|
||||
survey.AskOne(prompt, &days)
|
||||
*/
|
||||
type MultiSelect struct {
|
||||
Renderer
|
||||
Message string
|
||||
Options []string
|
||||
Default interface{}
|
||||
Help string
|
||||
PageSize int
|
||||
VimMode bool
|
||||
FilterMessage string
|
||||
Filter func(filter string, value string, index int) bool
|
||||
filter string
|
||||
selectedIndex int
|
||||
checked map[int]bool
|
||||
showingHelp bool
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type MultiSelectTemplateData struct {
|
||||
MultiSelect
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
Checked map[int]bool
|
||||
SelectedIndex int
|
||||
ShowHelp bool
|
||||
PageEntries []core.OptionAnswer
|
||||
Config *PromptConfig
|
||||
}
|
||||
|
||||
var MultiSelectQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
|
||||
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- " "}}{{- color "cyan"}}[Use arrows to move, space to select, <right> to all, <left> to none, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $option := .PageEntries}}
|
||||
{{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }}{{color "reset"}}{{else}} {{end}}
|
||||
{{- if index $.Checked $option.Index }}{{color $.Config.Icons.MarkedOption.Format }} {{ $.Config.Icons.MarkedOption.Text }} {{else}}{{color $.Config.Icons.UnmarkedOption.Format }} {{ $.Config.Icons.UnmarkedOption.Text }} {{end}}
|
||||
{{- color "reset"}}
|
||||
{{- " "}}{{$option.Value}}{{"\n"}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
// OnChange is called on every keypress.
|
||||
func (m *MultiSelect) OnChange(key rune, config *PromptConfig) {
|
||||
options := m.filterOptions(config)
|
||||
oldFilter := m.filter
|
||||
|
||||
if key == terminal.KeyArrowUp || (m.VimMode && key == 'k') {
|
||||
// if we are at the top of the list
|
||||
if m.selectedIndex == 0 {
|
||||
// go to the bottom
|
||||
m.selectedIndex = len(options) - 1
|
||||
} else {
|
||||
// decrement the selected index
|
||||
m.selectedIndex--
|
||||
}
|
||||
} else if key == terminal.KeyTab || key == terminal.KeyArrowDown || (m.VimMode && key == 'j') {
|
||||
// if we are at the bottom of the list
|
||||
if m.selectedIndex == len(options)-1 {
|
||||
// start at the top
|
||||
m.selectedIndex = 0
|
||||
} else {
|
||||
// increment the selected index
|
||||
m.selectedIndex++
|
||||
}
|
||||
// if the user pressed down and there is room to move
|
||||
} else if key == terminal.KeySpace {
|
||||
// the option they have selected
|
||||
if m.selectedIndex < len(options) {
|
||||
selectedOpt := options[m.selectedIndex]
|
||||
|
||||
// if we haven't seen this index before
|
||||
if old, ok := m.checked[selectedOpt.Index]; !ok {
|
||||
// set the value to true
|
||||
m.checked[selectedOpt.Index] = true
|
||||
} else {
|
||||
// otherwise just invert the current value
|
||||
m.checked[selectedOpt.Index] = !old
|
||||
}
|
||||
if !config.KeepFilter {
|
||||
m.filter = ""
|
||||
}
|
||||
}
|
||||
// only show the help message if we have one to show
|
||||
} else if string(key) == config.HelpInput && m.Help != "" {
|
||||
m.showingHelp = true
|
||||
} else if key == terminal.KeyEscape {
|
||||
m.VimMode = !m.VimMode
|
||||
} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
|
||||
m.filter = ""
|
||||
} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
|
||||
if m.filter != "" {
|
||||
runeFilter := []rune(m.filter)
|
||||
m.filter = string(runeFilter[0 : len(runeFilter)-1])
|
||||
}
|
||||
} else if key >= terminal.KeySpace {
|
||||
m.filter += string(key)
|
||||
m.VimMode = false
|
||||
} else if key == terminal.KeyArrowRight {
|
||||
for _, v := range options {
|
||||
m.checked[v.Index] = true
|
||||
}
|
||||
if !config.KeepFilter {
|
||||
m.filter = ""
|
||||
}
|
||||
} else if key == terminal.KeyArrowLeft {
|
||||
for _, v := range options {
|
||||
m.checked[v.Index] = false
|
||||
}
|
||||
if !config.KeepFilter {
|
||||
m.filter = ""
|
||||
}
|
||||
}
|
||||
|
||||
m.FilterMessage = ""
|
||||
if m.filter != "" {
|
||||
m.FilterMessage = " " + m.filter
|
||||
}
|
||||
if oldFilter != m.filter {
|
||||
// filter changed
|
||||
options = m.filterOptions(config)
|
||||
if len(options) > 0 && len(options) <= m.selectedIndex {
|
||||
m.selectedIndex = len(options) - 1
|
||||
}
|
||||
}
|
||||
// paginate the options
|
||||
// figure out the page size
|
||||
pageSize := m.PageSize
|
||||
// if we dont have a specific one
|
||||
if pageSize == 0 {
|
||||
// grab the global value
|
||||
pageSize = config.PageSize
|
||||
}
|
||||
|
||||
// TODO if we have started filtering and were looking at the end of a list
|
||||
// and we have modified the filter then we should move the page back!
|
||||
opts, idx := paginate(pageSize, options, m.selectedIndex)
|
||||
|
||||
// render the options
|
||||
m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Checked: m.checked,
|
||||
ShowHelp: m.showingHelp,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (m *MultiSelect) filterOptions(config *PromptConfig) []core.OptionAnswer {
|
||||
// the filtered list
|
||||
answers := []core.OptionAnswer{}
|
||||
|
||||
// if there is no filter applied
|
||||
if m.filter == "" {
|
||||
// return all of the options
|
||||
return core.OptionAnswerList(m.Options)
|
||||
}
|
||||
|
||||
// the filter to apply
|
||||
filter := m.Filter
|
||||
if filter == nil {
|
||||
filter = config.Filter
|
||||
}
|
||||
|
||||
// apply the filter to each option
|
||||
for i, opt := range m.Options {
|
||||
// i the filter says to include the option
|
||||
if filter(m.filter, opt, i) {
|
||||
answers = append(answers, core.OptionAnswer{
|
||||
Index: i,
|
||||
Value: opt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// we're done here
|
||||
return answers
|
||||
}
|
||||
|
||||
func (m *MultiSelect) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// compute the default state
|
||||
m.checked = make(map[int]bool)
|
||||
// if there is a default
|
||||
if m.Default != nil {
|
||||
// if the default is string values
|
||||
if defaultValues, ok := m.Default.([]string); ok {
|
||||
for _, dflt := range defaultValues {
|
||||
for i, opt := range m.Options {
|
||||
// if the option corresponds to the default
|
||||
if opt == dflt {
|
||||
// we found our initial value
|
||||
m.checked[i] = true
|
||||
// stop looking
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the default value is index values
|
||||
} else if defaultIndices, ok := m.Default.([]int); ok {
|
||||
// go over every index we need to enable by default
|
||||
for _, idx := range defaultIndices {
|
||||
// and enable it
|
||||
m.checked[idx] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no options to render
|
||||
if len(m.Options) == 0 {
|
||||
// we failed
|
||||
return "", errors.New("please provide options to select from")
|
||||
}
|
||||
|
||||
// figure out the page size
|
||||
pageSize := m.PageSize
|
||||
// if we dont have a specific one
|
||||
if pageSize == 0 {
|
||||
// grab the global value
|
||||
pageSize = config.PageSize
|
||||
}
|
||||
// paginate the options
|
||||
// build up a list of option answers
|
||||
opts, idx := paginate(pageSize, core.OptionAnswerList(m.Options), m.selectedIndex)
|
||||
|
||||
cursor := m.NewCursor()
|
||||
cursor.Hide() // hide the cursor
|
||||
defer cursor.Show() // show the cursor when we're done
|
||||
|
||||
// ask the question
|
||||
err := m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Checked: m.checked,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := m.NewRuneReader()
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
// start waiting for input
|
||||
for {
|
||||
r, _, err := rr.ReadRune()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r == '\r' || r == '\n' {
|
||||
break
|
||||
}
|
||||
if r == terminal.KeyInterrupt {
|
||||
return "", terminal.InterruptErr
|
||||
}
|
||||
if r == terminal.KeyEndTransmission {
|
||||
break
|
||||
}
|
||||
m.OnChange(r, config)
|
||||
}
|
||||
m.filter = ""
|
||||
m.FilterMessage = ""
|
||||
|
||||
answers := []core.OptionAnswer{}
|
||||
for i, option := range m.Options {
|
||||
if val, ok := m.checked[i]; ok && val {
|
||||
answers = append(answers, core.OptionAnswer{Value: option, Index: i})
|
||||
}
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
// Cleanup removes the options section, and renders the ask like a normal question.
|
||||
func (m *MultiSelect) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
// the answer to show
|
||||
answer := ""
|
||||
for _, ans := range val.([]core.OptionAnswer) {
|
||||
answer = fmt.Sprintf("%s, %s", answer, ans.Value)
|
||||
}
|
||||
|
||||
// if we answered anything
|
||||
if len(answer) > 2 {
|
||||
// remove the precending commas
|
||||
answer = answer[2:]
|
||||
}
|
||||
|
||||
// execute the output summary template with the answer
|
||||
return m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: m.selectedIndex,
|
||||
Checked: m.checked,
|
||||
Answer: answer,
|
||||
ShowAnswer: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
||||
101
vendor/github.com/AlecAivazis/survey/v2/password.go
generated
vendored
Normal file
101
vendor/github.com/AlecAivazis/survey/v2/password.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
Password is like a normal Input but the text shows up as *'s and there is no default. Response
|
||||
type is a string.
|
||||
|
||||
password := ""
|
||||
prompt := &survey.Password{ Message: "Please type your password" }
|
||||
survey.AskOne(prompt, &password)
|
||||
*/
|
||||
type Password struct {
|
||||
Renderer
|
||||
Message string
|
||||
Help string
|
||||
}
|
||||
|
||||
type PasswordTemplateData struct {
|
||||
Password
|
||||
ShowHelp bool
|
||||
Config *PromptConfig
|
||||
}
|
||||
|
||||
// PasswordQuestionTemplate is a template with color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var PasswordQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}`
|
||||
|
||||
func (p *Password) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// render the question template
|
||||
userOut, _, err := core.RunTemplate(
|
||||
PasswordQuestionTemplate,
|
||||
PasswordTemplateData{
|
||||
Password: *p,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
fmt.Fprint(terminal.NewAnsiStdout(p.Stdio().Out), userOut)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := p.NewRuneReader()
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
// no help msg? Just return any response
|
||||
if p.Help == "" {
|
||||
line, err := rr.ReadLine('*')
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
cursor := p.NewCursor()
|
||||
|
||||
line := []rune{}
|
||||
// process answers looking for help prompt answer
|
||||
for {
|
||||
line, err = rr.ReadLine('*')
|
||||
if err != nil {
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
if string(line) == config.HelpInput {
|
||||
// terminal will echo the \n so we need to jump back up one row
|
||||
cursor.PreviousLine(1)
|
||||
|
||||
err = p.Render(
|
||||
PasswordQuestionTemplate,
|
||||
PasswordTemplateData{
|
||||
Password: *p,
|
||||
ShowHelp: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
lineStr := string(line)
|
||||
p.AppendRenderedText(strings.Repeat("*", len(lineStr)))
|
||||
return lineStr, err
|
||||
}
|
||||
|
||||
// Cleanup hides the string with a fixed number of characters.
|
||||
func (prompt *Password) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
return nil
|
||||
}
|
||||
164
vendor/github.com/AlecAivazis/survey/v2/renderer.go
generated
vendored
Normal file
164
vendor/github.com/AlecAivazis/survey/v2/renderer.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
goterm "golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
stdio terminal.Stdio
|
||||
renderedErrors bytes.Buffer
|
||||
renderedText bytes.Buffer
|
||||
}
|
||||
|
||||
type ErrorTemplateData struct {
|
||||
Error error
|
||||
Icon Icon
|
||||
}
|
||||
|
||||
var ErrorTemplate = `{{color .Icon.Format }}{{ .Icon.Text }} Sorry, your reply was invalid: {{ .Error.Error }}{{color "reset"}}
|
||||
`
|
||||
|
||||
func (r *Renderer) WithStdio(stdio terminal.Stdio) {
|
||||
r.stdio = stdio
|
||||
}
|
||||
|
||||
func (r *Renderer) Stdio() terminal.Stdio {
|
||||
return r.stdio
|
||||
}
|
||||
|
||||
func (r *Renderer) NewRuneReader() *terminal.RuneReader {
|
||||
return terminal.NewRuneReader(r.stdio)
|
||||
}
|
||||
|
||||
func (r *Renderer) NewCursor() *terminal.Cursor {
|
||||
return &terminal.Cursor{
|
||||
In: r.stdio.In,
|
||||
Out: r.stdio.Out,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) Error(config *PromptConfig, invalid error) error {
|
||||
// cleanup the currently rendered errors
|
||||
r.resetPrompt(r.countLines(r.renderedErrors))
|
||||
r.renderedErrors.Reset()
|
||||
|
||||
// cleanup the rest of the prompt
|
||||
r.resetPrompt(r.countLines(r.renderedText))
|
||||
r.renderedText.Reset()
|
||||
|
||||
userOut, layoutOut, err := core.RunTemplate(ErrorTemplate, &ErrorTemplateData{
|
||||
Error: invalid,
|
||||
Icon: config.Icons.Error,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// send the message to the user
|
||||
fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut)
|
||||
|
||||
// add the printed text to the rendered error buffer so we can cleanup later
|
||||
r.appendRenderedError(layoutOut)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) Render(tmpl string, data interface{}) error {
|
||||
// cleanup the currently rendered text
|
||||
lineCount := r.countLines(r.renderedText)
|
||||
r.resetPrompt(lineCount)
|
||||
r.renderedText.Reset()
|
||||
|
||||
// render the template summarizing the current state
|
||||
userOut, layoutOut, err := core.RunTemplate(tmpl, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// print the summary
|
||||
fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut)
|
||||
|
||||
// add the printed text to the rendered text buffer so we can cleanup later
|
||||
r.AppendRenderedText(layoutOut)
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
|
||||
// appendRenderedError appends text to the renderer's error buffer
|
||||
// which is used to track what has been printed. It is not exported
|
||||
// as errors should only be displayed via Error(config, error).
|
||||
func (r *Renderer) appendRenderedError(text string) {
|
||||
r.renderedErrors.WriteString(text)
|
||||
}
|
||||
|
||||
// AppendRenderedText appends text to the renderer's text buffer
|
||||
// which is used to track of what has been printed. The buffer is used
|
||||
// to calculate how many lines to erase before updating the prompt.
|
||||
func (r *Renderer) AppendRenderedText(text string) {
|
||||
r.renderedText.WriteString(text)
|
||||
}
|
||||
|
||||
func (r *Renderer) resetPrompt(lines int) {
|
||||
// clean out current line in case tmpl didnt end in newline
|
||||
cursor := r.NewCursor()
|
||||
cursor.HorizontalAbsolute(0)
|
||||
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
|
||||
// clean up what we left behind last time
|
||||
for i := 0; i < lines; i++ {
|
||||
cursor.PreviousLine(1)
|
||||
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) termWidth() (int, error) {
|
||||
fd := int(r.stdio.Out.Fd())
|
||||
termWidth, _, err := goterm.GetSize(fd)
|
||||
return termWidth, err
|
||||
}
|
||||
|
||||
// countLines will return the count of `\n` with the addition of any
|
||||
// lines that have wrapped due to narrow terminal width
|
||||
func (r *Renderer) countLines(buf bytes.Buffer) int {
|
||||
w, err := r.termWidth()
|
||||
if err != nil || w == 0 {
|
||||
// if we got an error due to terminal.GetSize not being supported
|
||||
// on current platform then just assume a very wide terminal
|
||||
w = 10000
|
||||
}
|
||||
|
||||
bufBytes := buf.Bytes()
|
||||
|
||||
count := 0
|
||||
curr := 0
|
||||
delim := -1
|
||||
for curr < len(bufBytes) {
|
||||
// read until the next newline or the end of the string
|
||||
relDelim := bytes.IndexRune(bufBytes[curr:], '\n')
|
||||
if relDelim != -1 {
|
||||
count += 1 // new line found, add it to the count
|
||||
delim = curr + relDelim
|
||||
} else {
|
||||
delim = len(bufBytes) // no new line found, read rest of text
|
||||
}
|
||||
|
||||
if lineWidth := utf8.RuneCount(bufBytes[curr:delim]); lineWidth > w {
|
||||
// account for word wrapping
|
||||
count += lineWidth / w
|
||||
if (lineWidth % w) == 0 {
|
||||
// content whose width is exactly a multiplier of available width should not
|
||||
// count as having wrapped on the last line
|
||||
count -= 1
|
||||
}
|
||||
}
|
||||
curr = delim + 1
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
@@ -3,8 +3,8 @@ package survey
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -16,61 +16,69 @@ for them to select using the arrow keys and enter. Response type is a string.
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
}
|
||||
survey.AskOne(prompt, &color, nil)
|
||||
survey.AskOne(prompt, &color)
|
||||
*/
|
||||
type Select struct {
|
||||
core.Renderer
|
||||
Renderer
|
||||
Message string
|
||||
Options []string
|
||||
Default string
|
||||
Default interface{}
|
||||
Help string
|
||||
PageSize int
|
||||
VimMode bool
|
||||
FilterMessage string
|
||||
FilterFn func(string, []string) []string
|
||||
Filter func(filter string, value string, index int) bool
|
||||
filter string
|
||||
selectedIndex int
|
||||
useDefault bool
|
||||
showingHelp bool
|
||||
}
|
||||
|
||||
// the data available to the templates when processing
|
||||
// SelectTemplateData is the data available to the templates when processing
|
||||
type SelectTemplateData struct {
|
||||
Select
|
||||
PageEntries []string
|
||||
PageEntries []core.OptionAnswer
|
||||
SelectedIndex int
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
Config *PromptConfig
|
||||
}
|
||||
|
||||
var SelectQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
|
||||
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else}}
|
||||
{{- " "}}{{- color "cyan"}}[Use arrows to move, enter to select, type to filter{{- if and .Help (not .ShowHelp)}}, {{ HelpInputRune }} for more help{{end}}]{{color "reset"}}
|
||||
{{- " "}}{{- color "cyan"}}[Use arrows to move, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $choice := .PageEntries}}
|
||||
{{- if eq $ix $.SelectedIndex}}{{color "cyan+b"}}{{ SelectFocusIcon }} {{else}}{{color "default+hb"}} {{end}}
|
||||
{{- $choice}}
|
||||
{{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
|
||||
{{- $choice.Value}}
|
||||
{{- color "reset"}}{{"\n"}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
// OnChange is called on every keypress.
|
||||
func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
options := s.filterOptions()
|
||||
func (s *Select) OnChange(key rune, config *PromptConfig) bool {
|
||||
options := s.filterOptions(config)
|
||||
oldFilter := s.filter
|
||||
|
||||
// if the user pressed the enter key
|
||||
if key == terminal.KeyEnter {
|
||||
if s.selectedIndex < len(options) {
|
||||
return []rune(options[s.selectedIndex]), 0, true
|
||||
// if the user pressed the enter key and the index is a valid option
|
||||
if key == terminal.KeyEnter || key == '\n' {
|
||||
// if the selected index is a valid option
|
||||
if len(options) > 0 && s.selectedIndex < len(options) {
|
||||
|
||||
// we're done (stop prompting the user)
|
||||
return true
|
||||
}
|
||||
|
||||
// we're not done (keep prompting)
|
||||
return false
|
||||
|
||||
// if the user pressed the up arrow or 'k' to emulate vim
|
||||
} else if key == terminal.KeyArrowUp || (s.VimMode && key == 'k') && len(options) > 0 {
|
||||
} else if (key == terminal.KeyArrowUp || (s.VimMode && key == 'k')) && len(options) > 0 {
|
||||
s.useDefault = false
|
||||
|
||||
// if we are at the top of the list
|
||||
@@ -83,7 +91,7 @@ func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPo
|
||||
}
|
||||
|
||||
// if the user pressed down or 'j' to emulate vim
|
||||
} else if key == terminal.KeyArrowDown || (s.VimMode && key == 'j') && len(options) > 0 {
|
||||
} else if (key == terminal.KeyTab || key == terminal.KeyArrowDown || (s.VimMode && key == 'j')) && len(options) > 0 {
|
||||
s.useDefault = false
|
||||
// if we are at the bottom of the list
|
||||
if s.selectedIndex == len(options)-1 {
|
||||
@@ -94,7 +102,7 @@ func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPo
|
||||
s.selectedIndex++
|
||||
}
|
||||
// only show the help message if we have one
|
||||
} else if key == core.HelpInputRune && s.Help != "" {
|
||||
} else if string(key) == config.HelpInput && s.Help != "" {
|
||||
s.showingHelp = true
|
||||
// if the user wants to toggle vim mode on/off
|
||||
} else if key == terminal.KeyEscape {
|
||||
@@ -106,8 +114,9 @@ func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPo
|
||||
} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
|
||||
// if there is content in the filter to delete
|
||||
if s.filter != "" {
|
||||
runeFilter := []rune(s.filter)
|
||||
// subtract a line from the current filter
|
||||
s.filter = s.filter[0 : len(s.filter)-1]
|
||||
s.filter = string(runeFilter[0 : len(runeFilter)-1])
|
||||
// we removed the last value in the filter
|
||||
}
|
||||
} else if key >= terminal.KeySpace {
|
||||
@@ -124,17 +133,24 @@ func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPo
|
||||
}
|
||||
if oldFilter != s.filter {
|
||||
// filter changed
|
||||
options = s.filterOptions()
|
||||
options = s.filterOptions(config)
|
||||
if len(options) > 0 && len(options) <= s.selectedIndex {
|
||||
s.selectedIndex = len(options) - 1
|
||||
}
|
||||
}
|
||||
|
||||
// figure out the options and index to render
|
||||
// figure out the page size
|
||||
pageSize := s.PageSize
|
||||
// if we dont have a specific one
|
||||
if pageSize == 0 {
|
||||
// grab the global value
|
||||
pageSize = config.PageSize
|
||||
}
|
||||
|
||||
// TODO if we have started filtering and were looking at the end of a list
|
||||
// and we have modified the filter then we should move the page back!
|
||||
opts, idx := paginate(s.PageSize, options, s.selectedIndex)
|
||||
opts, idx := paginate(pageSize, options, s.selectedIndex)
|
||||
|
||||
// render the options
|
||||
s.Render(
|
||||
@@ -144,33 +160,45 @@ func (s *Select) OnChange(line []rune, pos int, key rune) (newLine []rune, newPo
|
||||
SelectedIndex: idx,
|
||||
ShowHelp: s.showingHelp,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
|
||||
// if we are not pressing ent
|
||||
if len(options) <= s.selectedIndex {
|
||||
return []rune{}, 0, false
|
||||
}
|
||||
|
||||
// s.selectedIndex should not be a negative number
|
||||
if s.selectedIndex < 0 {
|
||||
return []rune{}, 0, false
|
||||
}
|
||||
|
||||
return []rune(options[s.selectedIndex]), 0, true
|
||||
// keep prompting
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Select) filterOptions() []string {
|
||||
func (s *Select) filterOptions(config *PromptConfig) []core.OptionAnswer {
|
||||
// the filtered list
|
||||
answers := []core.OptionAnswer{}
|
||||
|
||||
// if there is no filter applied
|
||||
if s.filter == "" {
|
||||
return s.Options
|
||||
return core.OptionAnswerList(s.Options)
|
||||
}
|
||||
if s.FilterFn != nil {
|
||||
return s.FilterFn(s.filter, s.Options)
|
||||
|
||||
// the filter to apply
|
||||
filter := s.Filter
|
||||
if filter == nil {
|
||||
filter = config.Filter
|
||||
}
|
||||
return DefaultFilterFn(s.filter, s.Options)
|
||||
|
||||
//
|
||||
for i, opt := range s.Options {
|
||||
// i the filter says to include the option
|
||||
if filter(s.filter, opt, i) {
|
||||
answers = append(answers, core.OptionAnswer{
|
||||
Index: i,
|
||||
Value: opt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// return the list of answers
|
||||
return answers
|
||||
}
|
||||
|
||||
func (s *Select) Prompt() (interface{}, error) {
|
||||
func (s *Select) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// if there are no options to render
|
||||
if len(s.Options) == 0 {
|
||||
// we failed
|
||||
@@ -195,8 +223,16 @@ func (s *Select) Prompt() (interface{}, error) {
|
||||
// save the selected index
|
||||
s.selectedIndex = sel
|
||||
|
||||
// figure out the page size
|
||||
pageSize := s.PageSize
|
||||
// if we dont have a specific one
|
||||
if pageSize == 0 {
|
||||
// grab the global value
|
||||
pageSize = config.PageSize
|
||||
}
|
||||
|
||||
// figure out the options and index to render
|
||||
opts, idx := paginate(s.PageSize, s.Options, sel)
|
||||
opts, idx := paginate(pageSize, core.OptionAnswerList(s.Options), sel)
|
||||
|
||||
// ask the question
|
||||
err := s.Render(
|
||||
@@ -205,6 +241,7 @@ func (s *Select) Prompt() (interface{}, error) {
|
||||
Select: *s,
|
||||
PageEntries: opts,
|
||||
SelectedIndex: idx,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -228,47 +265,65 @@ func (s *Select) Prompt() (interface{}, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if r == '\r' || r == '\n' {
|
||||
break
|
||||
}
|
||||
if r == terminal.KeyInterrupt {
|
||||
return "", terminal.InterruptErr
|
||||
}
|
||||
if r == terminal.KeyEndTransmission {
|
||||
break
|
||||
}
|
||||
s.OnChange(nil, 0, r)
|
||||
if s.OnChange(r, config) {
|
||||
break
|
||||
}
|
||||
}
|
||||
options := s.filterOptions()
|
||||
options := s.filterOptions(config)
|
||||
s.filter = ""
|
||||
s.FilterMessage = ""
|
||||
|
||||
// the index to report
|
||||
var val string
|
||||
// if we are supposed to use the default value
|
||||
if s.useDefault || s.selectedIndex >= len(options) {
|
||||
// if there is a default value
|
||||
if s.Default != "" {
|
||||
// use the default value
|
||||
val = s.Default
|
||||
if s.Default != nil {
|
||||
// if the default is a string
|
||||
if defaultString, ok := s.Default.(string); ok {
|
||||
// use the default value
|
||||
val = defaultString
|
||||
// the default value could also be an interpret which is interpretted as the index
|
||||
} else if defaultIndex, ok := s.Default.(int); ok {
|
||||
val = s.Options[defaultIndex]
|
||||
} else {
|
||||
return val, errors.New("default value of select must be an int or string")
|
||||
}
|
||||
} else if len(options) > 0 {
|
||||
// there is no default value so use the first
|
||||
val = options[0]
|
||||
val = options[0].Value
|
||||
}
|
||||
// otherwise the selected index points to the value
|
||||
} else if s.selectedIndex >= 0 && s.selectedIndex < len(options) {
|
||||
} else if s.selectedIndex < len(options) {
|
||||
// the
|
||||
val = options[s.selectedIndex]
|
||||
val = options[s.selectedIndex].Value
|
||||
}
|
||||
return val, err
|
||||
|
||||
// now that we have the value lets go hunt down the right index to return
|
||||
idx = -1
|
||||
for i, optionValue := range s.Options {
|
||||
if optionValue == val {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
|
||||
return core.OptionAnswer{Value: val, Index: idx}, err
|
||||
}
|
||||
|
||||
func (s *Select) Cleanup(val interface{}) error {
|
||||
func (s *Select) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
return s.Render(
|
||||
SelectQuestionTemplate,
|
||||
SelectTemplateData{
|
||||
Select: *s,
|
||||
Answer: val.(string),
|
||||
Answer: val.(core.OptionAnswer).Value,
|
||||
ShowAnswer: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
||||
413
vendor/github.com/AlecAivazis/survey/v2/survey.go
generated
vendored
Normal file
413
vendor/github.com/AlecAivazis/survey/v2/survey.go
generated
vendored
Normal file
@@ -0,0 +1,413 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
// DefaultAskOptions is the default options on ask, using the OS stdio.
|
||||
func defaultAskOptions() *AskOptions {
|
||||
return &AskOptions{
|
||||
Stdio: terminal.Stdio{
|
||||
In: os.Stdin,
|
||||
Out: os.Stdout,
|
||||
Err: os.Stderr,
|
||||
},
|
||||
PromptConfig: PromptConfig{
|
||||
PageSize: 7,
|
||||
HelpInput: "?",
|
||||
SuggestInput: "tab",
|
||||
Icons: IconSet{
|
||||
Error: Icon{
|
||||
Text: "X",
|
||||
Format: "red",
|
||||
},
|
||||
Help: Icon{
|
||||
Text: "?",
|
||||
Format: "cyan",
|
||||
},
|
||||
Question: Icon{
|
||||
Text: "?",
|
||||
Format: "green+hb",
|
||||
},
|
||||
MarkedOption: Icon{
|
||||
Text: "[x]",
|
||||
Format: "green",
|
||||
},
|
||||
UnmarkedOption: Icon{
|
||||
Text: "[ ]",
|
||||
Format: "default+hb",
|
||||
},
|
||||
SelectFocus: Icon{
|
||||
Text: ">",
|
||||
Format: "cyan+b",
|
||||
},
|
||||
},
|
||||
Filter: func(filter string, value string, index int) (include bool) {
|
||||
filter = strings.ToLower(filter)
|
||||
|
||||
// include this option if it matches
|
||||
return strings.Contains(strings.ToLower(value), filter)
|
||||
},
|
||||
KeepFilter: false,
|
||||
ShowCursor: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
func defaultPromptConfig() *PromptConfig {
|
||||
return &defaultAskOptions().PromptConfig
|
||||
}
|
||||
|
||||
func defaultIcons() *IconSet {
|
||||
return &defaultPromptConfig().Icons
|
||||
}
|
||||
|
||||
// OptionAnswer is an ergonomic alias for core.OptionAnswer
|
||||
type OptionAnswer = core.OptionAnswer
|
||||
|
||||
// Icon holds the text and format to show for a particular icon
|
||||
type Icon struct {
|
||||
Text string
|
||||
Format string
|
||||
}
|
||||
|
||||
// IconSet holds the icons to use for various prompts
|
||||
type IconSet struct {
|
||||
HelpInput Icon
|
||||
Error Icon
|
||||
Help Icon
|
||||
Question Icon
|
||||
MarkedOption Icon
|
||||
UnmarkedOption Icon
|
||||
SelectFocus Icon
|
||||
}
|
||||
|
||||
// Validator is a function passed to a Question after a user has provided a response.
|
||||
// If the function returns an error, then the user will be prompted again for another
|
||||
// response.
|
||||
type Validator func(ans interface{}) error
|
||||
|
||||
// Transformer is a function passed to a Question after a user has provided a response.
|
||||
// The function can be used to implement a custom logic that will result to return
|
||||
// a different representation of the given answer.
|
||||
//
|
||||
// Look `TransformString`, `ToLower` `Title` and `ComposeTransformers` for more.
|
||||
type Transformer func(ans interface{}) (newAns interface{})
|
||||
|
||||
// Question is the core data structure for a survey questionnaire.
|
||||
type Question struct {
|
||||
Name string
|
||||
Prompt Prompt
|
||||
Validate Validator
|
||||
Transform Transformer
|
||||
}
|
||||
|
||||
// PromptConfig holds the global configuration for a prompt
|
||||
type PromptConfig struct {
|
||||
PageSize int
|
||||
Icons IconSet
|
||||
HelpInput string
|
||||
SuggestInput string
|
||||
Filter func(filter string, option string, index int) bool
|
||||
KeepFilter bool
|
||||
ShowCursor bool
|
||||
}
|
||||
|
||||
// Prompt is the primary interface for the objects that can take user input
|
||||
// and return a response.
|
||||
type Prompt interface {
|
||||
Prompt(config *PromptConfig) (interface{}, error)
|
||||
Cleanup(*PromptConfig, interface{}) error
|
||||
Error(*PromptConfig, error) error
|
||||
}
|
||||
|
||||
// PromptAgainer Interface for Prompts that support prompting again after invalid input
|
||||
type PromptAgainer interface {
|
||||
PromptAgain(config *PromptConfig, invalid interface{}, err error) (interface{}, error)
|
||||
}
|
||||
|
||||
// AskOpt allows setting optional ask options.
|
||||
type AskOpt func(options *AskOptions) error
|
||||
|
||||
// AskOptions provides additional options on ask.
|
||||
type AskOptions struct {
|
||||
Stdio terminal.Stdio
|
||||
Validators []Validator
|
||||
PromptConfig PromptConfig
|
||||
}
|
||||
|
||||
// WithStdio specifies the standard input, output and error files survey
|
||||
// interacts with. By default, these are os.Stdin, os.Stdout, and os.Stderr.
|
||||
func WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
options.Stdio.In = in
|
||||
options.Stdio.Out = out
|
||||
options.Stdio.Err = err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithFilter specifies the default filter to use when asking questions.
|
||||
func WithFilter(filter func(filter string, value string, index int) (include bool)) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// save the filter internally
|
||||
options.PromptConfig.Filter = filter
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithKeepFilter sets the if the filter is kept after selections
|
||||
func WithKeepFilter(KeepFilter bool) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// set the page size
|
||||
options.PromptConfig.KeepFilter = KeepFilter
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithValidator specifies a validator to use while prompting the user
|
||||
func WithValidator(v Validator) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// add the provided validator to the list
|
||||
options.Validators = append(options.Validators, v)
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type wantsStdio interface {
|
||||
WithStdio(terminal.Stdio)
|
||||
}
|
||||
|
||||
// WithPageSize sets the default page size used by prompts
|
||||
func WithPageSize(pageSize int) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// set the page size
|
||||
options.PromptConfig.PageSize = pageSize
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithHelpInput changes the character that prompts look for to give the user helpful information.
|
||||
func WithHelpInput(r rune) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// set the input character
|
||||
options.PromptConfig.HelpInput = string(r)
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithIcons sets the icons that will be used when prompting the user
|
||||
func WithIcons(setIcons func(*IconSet)) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// update the default icons with whatever the user says
|
||||
setIcons(&options.PromptConfig.Icons)
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithShowCursor sets the show cursor behavior when prompting the user
|
||||
func WithShowCursor(ShowCursor bool) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// set the page size
|
||||
options.PromptConfig.ShowCursor = ShowCursor
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
AskOne performs the prompt for a single prompt and asks for validation if required.
|
||||
Response types should be something that can be casted from the response type designated
|
||||
in the documentation. For example:
|
||||
|
||||
name := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "name",
|
||||
}
|
||||
|
||||
survey.AskOne(prompt, &name)
|
||||
|
||||
*/
|
||||
func AskOne(p Prompt, response interface{}, opts ...AskOpt) error {
|
||||
err := Ask([]*Question{{Prompt: p}}, response, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Ask performs the prompt loop, asking for validation when appropriate. The response
|
||||
type can be one of two options. If a struct is passed, the answer will be written to
|
||||
the field whose name matches the Name field on the corresponding question. Field types
|
||||
should be something that can be casted from the response type designated in the
|
||||
documentation. Note, a survey tag can also be used to identify a Otherwise, a
|
||||
map[string]interface{} can be passed, responses will be written to the key with the
|
||||
matching name. For example:
|
||||
|
||||
qs := []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{Message: "What is your name?"},
|
||||
Validate: survey.Required,
|
||||
Transform: survey.Title,
|
||||
},
|
||||
}
|
||||
|
||||
answers := struct{ Name string }{}
|
||||
|
||||
|
||||
err := survey.Ask(qs, &answers)
|
||||
*/
|
||||
func Ask(qs []*Question, response interface{}, opts ...AskOpt) error {
|
||||
// build up the configuration options
|
||||
options := defaultAskOptions()
|
||||
for _, opt := range opts {
|
||||
if opt == nil {
|
||||
continue
|
||||
}
|
||||
if err := opt(options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if we weren't passed a place to record the answers
|
||||
if response == nil {
|
||||
// we can't go any further
|
||||
return errors.New("cannot call Ask() with a nil reference to record the answers")
|
||||
}
|
||||
|
||||
// go over every question
|
||||
for _, q := range qs {
|
||||
// If Prompt implements controllable stdio, pass in specified stdio.
|
||||
if p, ok := q.Prompt.(wantsStdio); ok {
|
||||
p.WithStdio(options.Stdio)
|
||||
}
|
||||
|
||||
// grab the user input and save it
|
||||
ans, err := q.Prompt.Prompt(&options.PromptConfig)
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build up a list of validators that we have to apply to this question
|
||||
validators := []Validator{}
|
||||
|
||||
// make sure to include the question specific one
|
||||
if q.Validate != nil {
|
||||
validators = append(validators, q.Validate)
|
||||
}
|
||||
// add any "global" validators
|
||||
for _, validator := range options.Validators {
|
||||
validators = append(validators, validator)
|
||||
}
|
||||
|
||||
// apply every validator to thte response
|
||||
for _, validator := range validators {
|
||||
// wait for a valid response
|
||||
for invalid := validator(ans); invalid != nil; invalid = validator(ans) {
|
||||
err := q.Prompt.Error(&options.PromptConfig, invalid)
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ask for more input
|
||||
if promptAgainer, ok := q.Prompt.(PromptAgainer); ok {
|
||||
ans, err = promptAgainer.PromptAgain(&options.PromptConfig, ans, invalid)
|
||||
} else {
|
||||
ans, err = q.Prompt.Prompt(&options.PromptConfig)
|
||||
}
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if q.Transform != nil {
|
||||
// check if we have a transformer available, if so
|
||||
// then try to acquire the new representation of the
|
||||
// answer, if the resulting answer is not nil.
|
||||
if newAns := q.Transform(ans); newAns != nil {
|
||||
ans = newAns
|
||||
}
|
||||
}
|
||||
|
||||
// tell the prompt to cleanup with the validated value
|
||||
q.Prompt.Cleanup(&options.PromptConfig, ans)
|
||||
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// stop listening
|
||||
return err
|
||||
}
|
||||
|
||||
// add it to the map
|
||||
err = core.WriteAnswer(response, q.Name, ans)
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return the response
|
||||
return nil
|
||||
}
|
||||
|
||||
// paginate returns a single page of choices given the page size, the total list of
|
||||
// possible choices, and the current selected index in the total list.
|
||||
func paginate(pageSize int, choices []core.OptionAnswer, sel int) ([]core.OptionAnswer, int) {
|
||||
var start, end, cursor int
|
||||
|
||||
if len(choices) < pageSize {
|
||||
// if we dont have enough options to fill a page
|
||||
start = 0
|
||||
end = len(choices)
|
||||
cursor = sel
|
||||
|
||||
} else if sel < pageSize/2 {
|
||||
// if we are in the first half page
|
||||
start = 0
|
||||
end = pageSize
|
||||
cursor = sel
|
||||
|
||||
} else if len(choices)-sel-1 < pageSize/2 {
|
||||
// if we are in the last half page
|
||||
start = len(choices) - pageSize
|
||||
end = len(choices)
|
||||
cursor = sel - start
|
||||
|
||||
} else {
|
||||
// somewhere in the middle
|
||||
above := pageSize / 2
|
||||
below := pageSize - above
|
||||
|
||||
cursor = pageSize / 2
|
||||
start = sel - above
|
||||
end = sel + below
|
||||
}
|
||||
|
||||
// return the subset we care about and the index
|
||||
return choices[start:end], cursor
|
||||
}
|
||||
@@ -42,12 +42,14 @@ func (c *Cursor) Back(n int) {
|
||||
|
||||
// NextLine moves cursor to beginning of the line n lines down.
|
||||
func (c *Cursor) NextLine(n int) {
|
||||
fmt.Fprintf(c.Out, "\x1b[%dE", n)
|
||||
c.Down(1)
|
||||
c.HorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
// PreviousLine moves cursor to beginning of the line n lines up.
|
||||
func (c *Cursor) PreviousLine(n int) {
|
||||
fmt.Fprintf(c.Out, "\x1b[%dF", n)
|
||||
c.Up(1)
|
||||
c.HorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
// HorizontalAbsolute moves cursor horizontally to x.
|
||||
@@ -167,8 +169,11 @@ func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
|
||||
|
||||
// hide the cursor (so it doesn't blink when getting the size of the terminal)
|
||||
c.Hide()
|
||||
defer c.Show()
|
||||
|
||||
// save the current location of the cursor
|
||||
c.Save()
|
||||
defer c.Restore()
|
||||
|
||||
// move the cursor to the very bottom of the terminal
|
||||
c.Move(999, 999)
|
||||
@@ -179,12 +184,7 @@ func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// move back where we began
|
||||
c.Restore()
|
||||
|
||||
// show the cursor
|
||||
c.Show()
|
||||
// sice the bottom was calcuated in the lower right corner, it
|
||||
// since the bottom was calculated in the lower right corner, it
|
||||
// is the dimensions we are looking for
|
||||
return bottom, nil
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package terminal
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
)
|
||||
|
||||
type RuneReader struct {
|
||||
@@ -92,13 +94,15 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
// if we are at the end of the word
|
||||
if index == len(line) {
|
||||
// just remove the last letter from the internal representation
|
||||
// also count the number of cells the rune before the cursor occupied
|
||||
cells := runeWidth(line[len(line)-1])
|
||||
line = line[:len(line)-1]
|
||||
// go back one
|
||||
if cursorCurrent.X == 1 {
|
||||
cursor.PreviousLine(1)
|
||||
cursor.Forward(int(terminalSize.X))
|
||||
} else {
|
||||
cursor.Back(1)
|
||||
cursor.Back(cells)
|
||||
}
|
||||
|
||||
// clear the rest of the line
|
||||
@@ -106,6 +110,8 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
} else {
|
||||
// we need to remove a character from the middle of the word
|
||||
|
||||
cells := runeWidth(line[index-1])
|
||||
|
||||
// remove the current index from the list
|
||||
line = append(line[:index-1], line[index:]...)
|
||||
|
||||
@@ -115,7 +121,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
cursor.Save()
|
||||
|
||||
// clear the rest of the line
|
||||
cursor.Back(1)
|
||||
cursor.Back(cells)
|
||||
|
||||
// print what comes after
|
||||
for _, char := range line[index-1:] {
|
||||
@@ -136,7 +142,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
cursor.PreviousLine(1)
|
||||
cursor.Forward(int(terminalSize.X))
|
||||
} else {
|
||||
cursor.Back(1)
|
||||
cursor.Back(cells)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +166,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
cursor.PreviousLine(1)
|
||||
cursor.Forward(int(terminalSize.X))
|
||||
} else {
|
||||
cursor.Back(1)
|
||||
cursor.Back(runeWidth(line[index-1]))
|
||||
}
|
||||
//decrement the index
|
||||
index--
|
||||
@@ -183,7 +189,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||
cursor.NextLine(1)
|
||||
} else {
|
||||
cursor.Forward(1)
|
||||
cursor.Forward(runeWidth(line[index]))
|
||||
}
|
||||
index++
|
||||
|
||||
@@ -206,7 +212,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
cursorCurrent.Y--
|
||||
|
||||
} else {
|
||||
cursor.Back(1)
|
||||
cursor.Back(runeWidth(line[index-1]))
|
||||
cursorCurrent.X--
|
||||
}
|
||||
index--
|
||||
@@ -221,7 +227,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
cursorCurrent.Y++
|
||||
|
||||
} else {
|
||||
cursor.Forward(1)
|
||||
cursor.Forward(runeWidth(line[index]))
|
||||
cursorCurrent.X++
|
||||
}
|
||||
index++
|
||||
@@ -296,7 +302,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
// restore the position of the cursor horizontally
|
||||
cursor.Restore()
|
||||
// restore the position of the cursor vertically
|
||||
cursor.Up(1)
|
||||
cursor.PreviousLine(1)
|
||||
} else {
|
||||
// restore cursor
|
||||
cursor.Restore()
|
||||
@@ -306,7 +312,7 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||
cursor.NextLine(1)
|
||||
} else {
|
||||
cursor.Forward(1)
|
||||
cursor.Forward(runeWidth(r))
|
||||
}
|
||||
// increment the index
|
||||
index++
|
||||
@@ -314,3 +320,11 @@ func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runeWidth(r rune) int {
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
@@ -23,6 +23,7 @@ const (
|
||||
SpecialKeyEnd = '\x11'
|
||||
SpecialKeyDelete = '\x12'
|
||||
IgnoreKey = '\000'
|
||||
KeyTab = '\t'
|
||||
)
|
||||
|
||||
func soundBell(out io.Writer) {
|
||||
@@ -18,18 +18,21 @@ func TransformString(f func(s string) string) Transformer {
|
||||
return func(ans interface{}) interface{} {
|
||||
// if the answer value passed in is the zero value of the appropriate type
|
||||
if isZero(reflect.ValueOf(ans)) {
|
||||
// skip this `Transformer` by returning a nil value.
|
||||
// skip this `Transformer` by returning a zero value of string.
|
||||
// The original answer will be not affected,
|
||||
// see survey.go#L125.
|
||||
return nil
|
||||
// A zero value of string should be returned to be handled by
|
||||
// next Transformer in a composed Tranformer,
|
||||
// see tranform.go#L75
|
||||
return ""
|
||||
}
|
||||
|
||||
// "ans" is never nil here, so we don't have to check that
|
||||
// see survey.go#L97 for more.
|
||||
// see survey.go#L338 for more.
|
||||
// Make sure that the the answer's value was a typeof string.
|
||||
s, ok := ans.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
|
||||
return f(s)
|
||||
421
vendor/gopkg.in/AlecAivazis/survey.v1/README.md
generated
vendored
421
vendor/gopkg.in/AlecAivazis/survey.v1/README.md
generated
vendored
@@ -1,421 +0,0 @@
|
||||
# Survey
|
||||
|
||||
[](https://travis-ci.org/AlecAivazis/survey)
|
||||
[](https://godoc.org/gopkg.in/AlecAivazis/survey.v1)
|
||||
|
||||
A library for building interactive prompts.
|
||||
|
||||
<img width="550" src="https://thumbs.gfycat.com/VillainousGraciousKouprey-size_restricted.gif"/>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
)
|
||||
|
||||
// the questions to ask
|
||||
var qs = []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{Message: "What is your name?"},
|
||||
Validate: survey.Required,
|
||||
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 exactly, survey will try to convert for you
|
||||
}{}
|
||||
|
||||
// perform the questions
|
||||
err := survey.Ask(qs, &answers)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%s chose %s.", answers.Name, answers.FavoriteColor)
|
||||
}
|
||||
```
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Examples](#examples)
|
||||
1. [Prompts](#prompts)
|
||||
1. [Input](#input)
|
||||
1. [Multiline](#multiline)
|
||||
1. [Password](#password)
|
||||
1. [Confirm](#confirm)
|
||||
1. [Select](#select)
|
||||
1. [MultiSelect](#multiselect)
|
||||
1. [Editor](#editor)
|
||||
1. [Validation](#validation)
|
||||
1. [Built-in Validators](#built-in-validators)
|
||||
1. [Help Text](#help-text)
|
||||
1. [Changing the input rune](#changing-the-input-run)
|
||||
1. [Custom Types](#custom-types)
|
||||
1. [Customizing Output](#customizing-output)
|
||||
1. [Versioning](#versioning)
|
||||
1. [Testing](#testing)
|
||||
|
||||
## Examples
|
||||
|
||||
Examples can be found in the `examples/` directory. Run them
|
||||
to see basic behavior:
|
||||
|
||||
```bash
|
||||
go get gopkg.in/AlecAivazis/survey.v1
|
||||
|
||||
cd $GOPATH/src/gopkg.in/AlecAivazis/survey.v1
|
||||
|
||||
go run examples/simple.go
|
||||
go run examples/validation.go
|
||||
```
|
||||
|
||||
## Prompts
|
||||
|
||||
### Input
|
||||
|
||||
<img src="https://thumbs.gfycat.com/LankyBlindAmericanpainthorse-size_restricted.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
name := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "ping",
|
||||
}
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
```
|
||||
|
||||
### Multiline
|
||||
|
||||
<img src="https://thumbs.gfycat.com/ImperfectShimmeringBeagle-size_restricted.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
text := ""
|
||||
prompt := &survey.Multiline{
|
||||
Message: "ping",
|
||||
}
|
||||
survey.AskOne(prompt, &text, nil)
|
||||
```
|
||||
|
||||
### Password
|
||||
|
||||
<img src="https://thumbs.gfycat.com/CompassionateSevereHypacrosaurus-size_restricted.gif" width="400px" />
|
||||
|
||||
```golang
|
||||
password := ""
|
||||
prompt := &survey.Password{
|
||||
Message: "Please type your password",
|
||||
}
|
||||
survey.AskOne(prompt, &password, nil)
|
||||
```
|
||||
|
||||
### Confirm
|
||||
|
||||
<img src="https://thumbs.gfycat.com/UnkemptCarefulGermanpinscher-size_restricted.gif" width="400px"/>
|
||||
|
||||
```golang
|
||||
name := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: "Do you like pie?",
|
||||
}
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
```
|
||||
|
||||
### Select
|
||||
|
||||
<img src="https://thumbs.gfycat.com/GrimFilthyAmazonparrot-size_restricted.gif" width="450px"/>
|
||||
|
||||
```golang
|
||||
color := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
}
|
||||
survey.AskOne(prompt, &color, nil)
|
||||
```
|
||||
|
||||
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. To increase, you can either
|
||||
change the global `survey.PageSize`, or set the `PageSize` field on the prompt:
|
||||
|
||||
```golang
|
||||
prompt := &survey.Select{..., PageSize: 10}
|
||||
```
|
||||
|
||||
### MultiSelect
|
||||
|
||||
<img src="https://thumbs.gfycat.com/SharpTameAntelope-size_restricted.gif" width="450px"/>
|
||||
|
||||
```golang
|
||||
days := []string{}
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
}
|
||||
survey.AskOne(prompt, &days, nil)
|
||||
```
|
||||
|
||||
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. To increase, you can either
|
||||
change the global `survey.PageSize`, or set the `PageSize` field on the prompt:
|
||||
|
||||
```golang
|
||||
prompt := &survey.MultiSelect{..., PageSize: 10}
|
||||
```
|
||||
|
||||
### Editor
|
||||
|
||||
Launches the user's preferred editor (defined by the $EDITOR environment variable) 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.
|
||||
|
||||
## Filtering options in Select and MultiSelect
|
||||
|
||||
The user can filter for options 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. This default filtering behavior is provided by the `DefaultFilterFn`
|
||||
function.
|
||||
|
||||
A custom filter function can also be provided to change this default behavior by providing a value for the `FilterFn` field:
|
||||
|
||||
```golang
|
||||
&Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
FilterFn: func(filter string, options []string) (filtered []string) {
|
||||
result := DefaultFilterFn(filter, options)
|
||||
for _, v := range result {
|
||||
if len(v) >= 5 {
|
||||
filtered = append(filtered, v)
|
||||
}
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
While the example above is contrived, this allows for use cases where "smarter" filtering might be useful, for example, when
|
||||
options are backed by more complex types and filtering might need to occur on more metadata than just the displayed name.
|
||||
|
||||
## Validation
|
||||
|
||||
Validating individual responses for a particular question can be done by defining a
|
||||
`Validate` field on the `survey.Question` to be validated. This function takes an
|
||||
`interface{}` type and returns an error to show to the user, prompting them for another
|
||||
response:
|
||||
|
||||
```golang
|
||||
q := &survey.Question{
|
||||
Prompt: &survey.Input{Message: "Hello world validation"},
|
||||
Validate: func (val interface{}) error {
|
||||
// since we are validating an Input, the assertion will always succeed
|
||||
if str, ok := val.(string) ; !ok || len(str) > 10 {
|
||||
return errors.New("This response cannot be longer than 10 characters.")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### 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 | |
|
||||
|
||||
## Help Text
|
||||
|
||||
All of the prompts have a `Help` field which can be defined to provide more information to your users:
|
||||
|
||||
<img src="https://thumbs.gfycat.com/CloudyRemorsefulFossa-size_restricted.gif" width="400px" style="margin-top: 8px"/>
|
||||
|
||||
```golang
|
||||
&survey.Input{
|
||||
Message: "What is your phone number:",
|
||||
Help: "Phone number should include the area code",
|
||||
}
|
||||
```
|
||||
|
||||
### Changing the input rune
|
||||
|
||||
In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
|
||||
looks for by setting the `HelpInputRune` variable in `survey/core`:
|
||||
|
||||
```golang
|
||||
import (
|
||||
"gopkg.in/AlecAivazis/survey.v1"
|
||||
surveyCore "gopkg.in/AlecAivazis/survey.v1/core"
|
||||
)
|
||||
|
||||
number := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "If you have this need, please give me a reasonable message.",
|
||||
Help: "I couldn't come up with one.",
|
||||
}
|
||||
|
||||
surveyCore.HelpInputRune = '^'
|
||||
|
||||
survey.AskOne(prompt, &number, nil)
|
||||
```
|
||||
|
||||
## Custom Types
|
||||
|
||||
survey will assign prompt answers to your custom types if they implement this interface:
|
||||
|
||||
```golang
|
||||
type settable interface {
|
||||
WriteAnswer(field string, value interface{}) error
|
||||
}
|
||||
```
|
||||
|
||||
Here is an example how to use them:
|
||||
|
||||
```golang
|
||||
type MyValue struct {
|
||||
value string
|
||||
}
|
||||
func (my *MyValue) WriteAnswer(name string, value interface{}) error {
|
||||
my.value = value.(string)
|
||||
}
|
||||
|
||||
myval := MyValue{}
|
||||
survey.AskOne(
|
||||
&survey.Input{
|
||||
Message: "Enter something:",
|
||||
},
|
||||
&myval,
|
||||
nil,
|
||||
)
|
||||
```
|
||||
|
||||
## Customizing Output
|
||||
|
||||
Customizing the icons and various parts of survey can easily be done by setting the following variables
|
||||
in `survey/core`:
|
||||
|
||||
| name | default | description |
|
||||
| ------------------ | ------- | ------------------------------------------------------------- |
|
||||
| ErrorIcon | X | Before an error |
|
||||
| HelpIcon | i | Before help text |
|
||||
| QuestionIcon | ? | Before the message of a prompt |
|
||||
| SelectFocusIcon | > | Marks the current focus in `Select` and `MultiSelect` prompts |
|
||||
| UnmarkedOptionIcon | [ ] | Marks an unselected option in a `MultiSelect` prompt |
|
||||
| MarkedOptionIcon | [x] | Marks a chosen selection in a `MultiSelect` prompt |
|
||||
|
||||
## Versioning
|
||||
|
||||
This project tries to maintain semantic GitHub releases as closely as possible and relies on [gopkg.in](http://labix.org/gopkg.in)
|
||||
to maintain those releases. Importing version 1 of survey would look like:
|
||||
|
||||
```golang
|
||||
package main
|
||||
|
||||
import "gopkg.in/AlecAivazis/survey.v1"
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
You can test your program's interactive prompts using [go-expect](https://github.com/Netflix/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](https://github.com/hinshun/vt10x).
|
||||
|
||||
For example, you can test a binary utilizing `survey` by connecting the Console's tty to a subprocess's stdio.
|
||||
|
||||
```go
|
||||
func TestCLI(t *testing.T) {
|
||||
// Multiplex stdin/stdout to a virtual terminal to respond to ANSI escape
|
||||
// sequences (i.e. cursor position report).
|
||||
c, state, err := vt10x.NewVT10XConsole()
|
||||
require.Nil(t, err)
|
||||
defer c.Close()
|
||||
|
||||
donec := make(chan struct{})
|
||||
go func() {
|
||||
defer close(donec)
|
||||
c.ExpectString("What is your name?")
|
||||
c.SendLine("Johnny Appleseed")
|
||||
c.ExpectEOF()
|
||||
}()
|
||||
|
||||
cmd := exec.Command("your-cli")
|
||||
cmd.Stdin = c.Tty()
|
||||
cmd.Stdout = c.Tty()
|
||||
cmd.Stderr = c.Tty()
|
||||
|
||||
err = cmd.Run()
|
||||
require.Nil(t, err)
|
||||
|
||||
// Close the slave end of the pty, and read the remaining bytes from the master end.
|
||||
c.Tty().Close()
|
||||
<-donec
|
||||
|
||||
// Dump the terminal's screen.
|
||||
t.Log(expect.StripTrailingEmptyLines(state.String()))
|
||||
}
|
||||
```
|
||||
|
||||
If your application is decoupled from `os.Stdout` and `os.Stdin`, you can even test through the tty alone.
|
||||
`survey` itself is tested in this manner.
|
||||
|
||||
```go
|
||||
func TestCLI(t *testing.T) {
|
||||
// Multiplex stdin/stdout to a virtual terminal to respond to ANSI escape
|
||||
// sequences (i.e. cursor position report).
|
||||
c, state, err := vt10x.NewVT10XConsole()
|
||||
require.Nil(t, err)
|
||||
defer c.Close()
|
||||
|
||||
donec := make(chan struct{})
|
||||
go func() {
|
||||
defer close(donec)
|
||||
c.ExpectString("What is your name?")
|
||||
c.SendLine("Johnny Appleseed")
|
||||
c.ExpectEOF()
|
||||
}()
|
||||
|
||||
prompt := &Input{
|
||||
Message: "What is your name?",
|
||||
}
|
||||
prompt.WithStdio(Stdio(c))
|
||||
|
||||
answer, err := prompt.Prompt()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "Johnny Appleseed", answer)
|
||||
|
||||
// Close the slave end of the pty, and read the remaining bytes from the master end.
|
||||
c.Tty().Close()
|
||||
<-donec
|
||||
|
||||
// Dump the terminal's screen.
|
||||
t.Log(expect.StripTrailingEmptyLines(state.String()))
|
||||
}
|
||||
```
|
||||
84
vendor/gopkg.in/AlecAivazis/survey.v1/core/renderer.go
generated
vendored
84
vendor/gopkg.in/AlecAivazis/survey.v1/core/renderer.go
generated
vendored
@@ -1,84 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
)
|
||||
|
||||
type Renderer struct {
|
||||
stdio terminal.Stdio
|
||||
lineCount int
|
||||
errorLineCount int
|
||||
}
|
||||
|
||||
var ErrorTemplate = `{{color "red"}}{{ ErrorIcon }} Sorry, your reply was invalid: {{.Error}}{{color "reset"}}
|
||||
`
|
||||
|
||||
func (r *Renderer) WithStdio(stdio terminal.Stdio) {
|
||||
r.stdio = stdio
|
||||
}
|
||||
|
||||
func (r *Renderer) Stdio() terminal.Stdio {
|
||||
return r.stdio
|
||||
}
|
||||
|
||||
func (r *Renderer) NewRuneReader() *terminal.RuneReader {
|
||||
return terminal.NewRuneReader(r.stdio)
|
||||
}
|
||||
|
||||
func (r *Renderer) NewCursor() *terminal.Cursor {
|
||||
return &terminal.Cursor{
|
||||
In: r.stdio.In,
|
||||
Out: r.stdio.Out,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) Error(invalid error) error {
|
||||
// since errors are printed on top we need to reset the prompt
|
||||
// as well as any previous error print
|
||||
r.resetPrompt(r.lineCount + r.errorLineCount)
|
||||
// we just cleared the prompt lines
|
||||
r.lineCount = 0
|
||||
out, err := RunTemplate(ErrorTemplate, invalid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// keep track of how many lines are printed so we can clean up later
|
||||
r.errorLineCount = strings.Count(out, "\n")
|
||||
|
||||
// send the message to the user
|
||||
fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) resetPrompt(lines int) {
|
||||
// clean out current line in case tmpl didnt end in newline
|
||||
cursor := r.NewCursor()
|
||||
cursor.HorizontalAbsolute(0)
|
||||
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
|
||||
// clean up what we left behind last time
|
||||
for i := 0; i < lines; i++ {
|
||||
cursor.PreviousLine(1)
|
||||
terminal.EraseLine(r.stdio.Out, terminal.ERASE_LINE_ALL)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Renderer) Render(tmpl string, data interface{}) error {
|
||||
r.resetPrompt(r.lineCount)
|
||||
// render the template summarizing the current state
|
||||
out, err := RunTemplate(tmpl, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// keep track of how many lines are printed so we can clean up later
|
||||
r.lineCount = strings.Count(out, "\n")
|
||||
|
||||
// print the summary
|
||||
fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), out)
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
120
vendor/gopkg.in/AlecAivazis/survey.v1/core/template.go
generated
vendored
120
vendor/gopkg.in/AlecAivazis/survey.v1/core/template.go
generated
vendored
@@ -1,120 +0,0 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"text/template"
|
||||
|
||||
"github.com/mgutz/ansi"
|
||||
)
|
||||
|
||||
var DisableColor = false
|
||||
|
||||
var (
|
||||
// HelpInputRune is the rune which the user should enter to trigger
|
||||
// more detailed question help
|
||||
HelpInputRune = '?'
|
||||
|
||||
// ErrorIcon will be be shown before an error
|
||||
ErrorIcon = "X"
|
||||
|
||||
// HelpIcon will be shown before more detailed question help
|
||||
HelpIcon = "?"
|
||||
// QuestionIcon will be shown before a question Message
|
||||
QuestionIcon = "?"
|
||||
|
||||
// MarkedOptionIcon will be prepended before a selected multiselect option
|
||||
MarkedOptionIcon = "[x]"
|
||||
// UnmarkedOptionIcon will be prepended before an unselected multiselect option
|
||||
UnmarkedOptionIcon = "[ ]"
|
||||
|
||||
// SelectFocusIcon is prepended to an option to signify the user is
|
||||
// currently focusing that option
|
||||
SelectFocusIcon = ">"
|
||||
)
|
||||
|
||||
/*
|
||||
SetFancyIcons changes the err, help, marked, and focus input icons to their
|
||||
fancier forms. These forms may not be compatible with most terminals.
|
||||
This function will not touch the QuestionIcon as its fancy and non fancy form
|
||||
are the same.
|
||||
*/
|
||||
func SetFancyIcons() {
|
||||
ErrorIcon = "✘"
|
||||
HelpIcon = "ⓘ"
|
||||
// QuestionIcon fancy and non-fancy form are the same
|
||||
|
||||
MarkedOptionIcon = "◉"
|
||||
UnmarkedOptionIcon = "◯"
|
||||
|
||||
SelectFocusIcon = "❯"
|
||||
}
|
||||
|
||||
var TemplateFuncs = map[string]interface{}{
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
"color": func(color string) string {
|
||||
if DisableColor {
|
||||
return ""
|
||||
}
|
||||
return ansi.ColorCode(color)
|
||||
},
|
||||
"HelpInputRune": func() string {
|
||||
return string(HelpInputRune)
|
||||
},
|
||||
"ErrorIcon": func() string {
|
||||
return ErrorIcon
|
||||
},
|
||||
"HelpIcon": func() string {
|
||||
return HelpIcon
|
||||
},
|
||||
"QuestionIcon": func() string {
|
||||
return QuestionIcon
|
||||
},
|
||||
"MarkedOptionIcon": func() string {
|
||||
return MarkedOptionIcon
|
||||
},
|
||||
"UnmarkedOptionIcon": func() string {
|
||||
return UnmarkedOptionIcon
|
||||
},
|
||||
"SelectFocusIcon": func() string {
|
||||
return SelectFocusIcon
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
memoizedGetTemplate = map[string]*template.Template{}
|
||||
|
||||
memoMutex = &sync.RWMutex{}
|
||||
)
|
||||
|
||||
func getTemplate(tmpl string) (*template.Template, error) {
|
||||
memoMutex.RLock()
|
||||
if t, ok := memoizedGetTemplate[tmpl]; ok {
|
||||
memoMutex.RUnlock()
|
||||
return t, nil
|
||||
}
|
||||
memoMutex.RUnlock()
|
||||
|
||||
t, err := template.New("prompt").Funcs(TemplateFuncs).Parse(tmpl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memoMutex.Lock()
|
||||
memoizedGetTemplate[tmpl] = t
|
||||
memoMutex.Unlock()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func RunTemplate(tmpl string, data interface{}) (string, error) {
|
||||
t, err := getTemplate(tmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := bytes.NewBufferString("")
|
||||
err = t.Execute(buf, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), err
|
||||
}
|
||||
13
vendor/gopkg.in/AlecAivazis/survey.v1/filter.go
generated
vendored
13
vendor/gopkg.in/AlecAivazis/survey.v1/filter.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
package survey
|
||||
|
||||
import "strings"
|
||||
|
||||
var DefaultFilterFn = func(filter string, options []string) (answer []string) {
|
||||
filter = strings.ToLower(filter)
|
||||
for _, o := range options {
|
||||
if strings.Contains(strings.ToLower(o), filter) {
|
||||
answer = append(answer, o)
|
||||
}
|
||||
}
|
||||
return answer
|
||||
}
|
||||
13
vendor/gopkg.in/AlecAivazis/survey.v1/go.mod
generated
vendored
13
vendor/gopkg.in/AlecAivazis/survey.v1/go.mod
generated
vendored
@@ -1,13 +0,0 @@
|
||||
module gopkg.in/AlecAivazis/survey.v1
|
||||
|
||||
require (
|
||||
github.com/AlecAivazis/survey/v2 v2.0.5 // indirect
|
||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||
github.com/mattn/go-isatty v0.0.8
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
|
||||
github.com/stretchr/testify v1.2.1
|
||||
)
|
||||
|
||||
go 1.13
|
||||
97
vendor/gopkg.in/AlecAivazis/survey.v1/input.go
generated
vendored
97
vendor/gopkg.in/AlecAivazis/survey.v1/input.go
generated
vendored
@@ -1,97 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
)
|
||||
|
||||
/*
|
||||
Input is a regular text input that prints each character the user types on the screen
|
||||
and accepts the input with the enter key. Response type is a string.
|
||||
|
||||
name := ""
|
||||
prompt := &survey.Input{ Message: "What is your name?" }
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
*/
|
||||
type Input struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Default string
|
||||
Help string
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type InputTemplateData struct {
|
||||
Input
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var InputQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if .ShowAnswer}}
|
||||
{{- color "cyan"}}{{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}
|
||||
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||
{{- end}}`
|
||||
|
||||
func (i *Input) Prompt() (interface{}, error) {
|
||||
// render the template
|
||||
err := i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{Input: *i},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// start reading runes from the standard in
|
||||
rr := i.NewRuneReader()
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
cursor := i.NewCursor()
|
||||
|
||||
line := []rune{}
|
||||
// get the next line
|
||||
for {
|
||||
line, err = rr.ReadLine(0)
|
||||
if err != nil {
|
||||
return string(line), err
|
||||
}
|
||||
// terminal will echo the \n so we need to jump back up one row
|
||||
cursor.PreviousLine(1)
|
||||
|
||||
if string(line) == string(core.HelpInputRune) && i.Help != "" {
|
||||
err = i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{Input: *i, ShowHelp: true},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// if the line is empty
|
||||
if line == nil || len(line) == 0 {
|
||||
// use the default value
|
||||
return i.Default, err
|
||||
}
|
||||
|
||||
// we're done
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
func (i *Input) Cleanup(val interface{}) error {
|
||||
return i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{Input: *i, Answer: val.(string), ShowAnswer: true},
|
||||
)
|
||||
}
|
||||
248
vendor/gopkg.in/AlecAivazis/survey.v1/multiselect.go
generated
vendored
248
vendor/gopkg.in/AlecAivazis/survey.v1/multiselect.go
generated
vendored
@@ -1,248 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
MultiSelect is a prompt that presents a list of various options to the user
|
||||
for them to select using the arrow keys and enter. Response type is a slice of strings.
|
||||
|
||||
days := []string{}
|
||||
prompt := &survey.MultiSelect{
|
||||
Message: "What days do you prefer:",
|
||||
Options: []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"},
|
||||
}
|
||||
survey.AskOne(prompt, &days, nil)
|
||||
*/
|
||||
type MultiSelect struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Options []string
|
||||
Default []string
|
||||
Help string
|
||||
PageSize int
|
||||
VimMode bool
|
||||
FilterMessage string
|
||||
FilterFn func(string, []string) []string
|
||||
filter string
|
||||
selectedIndex int
|
||||
checked map[string]bool
|
||||
showingHelp bool
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type MultiSelectTemplateData struct {
|
||||
MultiSelect
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
Checked map[string]bool
|
||||
SelectedIndex int
|
||||
ShowHelp bool
|
||||
PageEntries []string
|
||||
}
|
||||
|
||||
var MultiSelectQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }}{{ .FilterMessage }}{{color "reset"}}
|
||||
{{- if .ShowAnswer}}{{color "cyan"}} {{.Answer}}{{color "reset"}}{{"\n"}}
|
||||
{{- else }}
|
||||
{{- " "}}{{- color "cyan"}}[Use arrows to move, enter to select, type to filter{{- if and .Help (not .ShowHelp)}}, {{ HelpInputRune }} for more help{{end}}]{{color "reset"}}
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $option := .PageEntries}}
|
||||
{{- if eq $ix $.SelectedIndex}}{{color "cyan"}}{{ SelectFocusIcon }}{{color "reset"}}{{else}} {{end}}
|
||||
{{- if index $.Checked $option}}{{color "green"}} {{ MarkedOptionIcon }} {{else}}{{color "default+hb"}} {{ UnmarkedOptionIcon }} {{end}}
|
||||
{{- color "reset"}}
|
||||
{{- " "}}{{$option}}{{"\n"}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
// OnChange is called on every keypress.
|
||||
func (m *MultiSelect) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
options := m.filterOptions()
|
||||
oldFilter := m.filter
|
||||
|
||||
if key == terminal.KeyArrowUp || (m.VimMode && key == 'k') {
|
||||
// if we are at the top of the list
|
||||
if m.selectedIndex == 0 {
|
||||
// go to the bottom
|
||||
m.selectedIndex = len(options) - 1
|
||||
} else {
|
||||
// decrement the selected index
|
||||
m.selectedIndex--
|
||||
}
|
||||
} else if key == terminal.KeyArrowDown || (m.VimMode && key == 'j') {
|
||||
// if we are at the bottom of the list
|
||||
if m.selectedIndex == len(options)-1 {
|
||||
// start at the top
|
||||
m.selectedIndex = 0
|
||||
} else {
|
||||
// increment the selected index
|
||||
m.selectedIndex++
|
||||
}
|
||||
// if the user pressed down and there is room to move
|
||||
} else if key == terminal.KeySpace {
|
||||
if m.selectedIndex < len(options) {
|
||||
if old, ok := m.checked[options[m.selectedIndex]]; !ok {
|
||||
// otherwise just invert the current value
|
||||
m.checked[options[m.selectedIndex]] = true
|
||||
} else {
|
||||
// otherwise just invert the current value
|
||||
m.checked[options[m.selectedIndex]] = !old
|
||||
}
|
||||
m.filter = ""
|
||||
}
|
||||
// only show the help message if we have one to show
|
||||
} else if key == core.HelpInputRune && m.Help != "" {
|
||||
m.showingHelp = true
|
||||
} else if key == terminal.KeyEscape {
|
||||
m.VimMode = !m.VimMode
|
||||
} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
|
||||
m.filter = ""
|
||||
} else if key == terminal.KeyDelete || key == terminal.KeyBackspace {
|
||||
if m.filter != "" {
|
||||
m.filter = m.filter[0 : len(m.filter)-1]
|
||||
}
|
||||
} else if key >= terminal.KeySpace {
|
||||
m.filter += string(key)
|
||||
m.VimMode = false
|
||||
}
|
||||
|
||||
m.FilterMessage = ""
|
||||
if m.filter != "" {
|
||||
m.FilterMessage = " " + m.filter
|
||||
}
|
||||
if oldFilter != m.filter {
|
||||
// filter changed
|
||||
options = m.filterOptions()
|
||||
if len(options) > 0 && len(options) <= m.selectedIndex {
|
||||
m.selectedIndex = len(options) - 1
|
||||
}
|
||||
}
|
||||
// paginate the options
|
||||
|
||||
// TODO if we have started filtering and were looking at the end of a list
|
||||
// and we have modified the filter then we should move the page back!
|
||||
opts, idx := paginate(m.PageSize, options, m.selectedIndex)
|
||||
|
||||
// render the options
|
||||
m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Checked: m.checked,
|
||||
ShowHelp: m.showingHelp,
|
||||
PageEntries: opts,
|
||||
},
|
||||
)
|
||||
|
||||
// if we are not pressing ent
|
||||
return line, 0, true
|
||||
}
|
||||
|
||||
func (m *MultiSelect) filterOptions() []string {
|
||||
if m.filter == "" {
|
||||
return m.Options
|
||||
}
|
||||
if m.FilterFn != nil {
|
||||
return m.FilterFn(m.filter, m.Options)
|
||||
}
|
||||
return DefaultFilterFn(m.filter, m.Options)
|
||||
}
|
||||
|
||||
func (m *MultiSelect) Prompt() (interface{}, error) {
|
||||
// compute the default state
|
||||
m.checked = make(map[string]bool)
|
||||
// if there is a default
|
||||
if len(m.Default) > 0 {
|
||||
for _, dflt := range m.Default {
|
||||
for _, opt := range m.Options {
|
||||
// if the option corresponds to the default
|
||||
if opt == dflt {
|
||||
// we found our initial value
|
||||
m.checked[opt] = true
|
||||
// stop looking
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if there are no options to render
|
||||
if len(m.Options) == 0 {
|
||||
// we failed
|
||||
return "", errors.New("please provide options to select from")
|
||||
}
|
||||
|
||||
// paginate the options
|
||||
opts, idx := paginate(m.PageSize, m.Options, m.selectedIndex)
|
||||
|
||||
cursor := m.NewCursor()
|
||||
cursor.Hide() // hide the cursor
|
||||
defer cursor.Show() // show the cursor when we're done
|
||||
|
||||
// ask the question
|
||||
err := m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Checked: m.checked,
|
||||
PageEntries: opts,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := m.NewRuneReader()
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
// start waiting for input
|
||||
for {
|
||||
r, _, _ := rr.ReadRune()
|
||||
if r == '\r' || r == '\n' {
|
||||
break
|
||||
}
|
||||
if r == terminal.KeyInterrupt {
|
||||
return "", terminal.InterruptErr
|
||||
}
|
||||
if r == terminal.KeyEndTransmission {
|
||||
break
|
||||
}
|
||||
m.OnChange(nil, 0, r)
|
||||
}
|
||||
m.filter = ""
|
||||
m.FilterMessage = ""
|
||||
|
||||
answers := []string{}
|
||||
for _, option := range m.Options {
|
||||
if val, ok := m.checked[option]; ok && val {
|
||||
answers = append(answers, option)
|
||||
}
|
||||
}
|
||||
|
||||
return answers, nil
|
||||
}
|
||||
|
||||
// Cleanup removes the options section, and renders the ask like a normal question.
|
||||
func (m *MultiSelect) Cleanup(val interface{}) error {
|
||||
// execute the output summary template with the answer
|
||||
return m.Render(
|
||||
MultiSelectQuestionTemplate,
|
||||
MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: m.selectedIndex,
|
||||
Checked: m.checked,
|
||||
Answer: strings.Join(val.([]string), ", "),
|
||||
ShowAnswer: true,
|
||||
},
|
||||
)
|
||||
}
|
||||
86
vendor/gopkg.in/AlecAivazis/survey.v1/password.go
generated
vendored
86
vendor/gopkg.in/AlecAivazis/survey.v1/password.go
generated
vendored
@@ -1,86 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
Password is like a normal Input but the text shows up as *'s and there is no default. Response
|
||||
type is a string.
|
||||
|
||||
password := ""
|
||||
prompt := &survey.Password{ Message: "Please type your password" }
|
||||
survey.AskOne(prompt, &password, nil)
|
||||
*/
|
||||
type Password struct {
|
||||
core.Renderer
|
||||
Message string
|
||||
Help string
|
||||
}
|
||||
|
||||
type PasswordTemplateData struct {
|
||||
Password
|
||||
ShowHelp bool
|
||||
}
|
||||
|
||||
// Templates with Color formatting. See Documentation: https://github.com/mgutz/ansi#style-format
|
||||
var PasswordQuestionTemplate = `
|
||||
{{- if .ShowHelp }}{{- color "cyan"}}{{ HelpIcon }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}}
|
||||
{{- color "green+hb"}}{{ QuestionIcon }} {{color "reset"}}
|
||||
{{- color "default+hb"}}{{ .Message }} {{color "reset"}}
|
||||
{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ HelpInputRune }} for help]{{color "reset"}} {{end}}`
|
||||
|
||||
func (p *Password) Prompt() (line interface{}, err error) {
|
||||
// render the question template
|
||||
out, err := core.RunTemplate(
|
||||
PasswordQuestionTemplate,
|
||||
PasswordTemplateData{Password: *p},
|
||||
)
|
||||
fmt.Fprint(terminal.NewAnsiStdout(p.Stdio().Out), out)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := p.NewRuneReader()
|
||||
rr.SetTermMode()
|
||||
defer rr.RestoreTermMode()
|
||||
|
||||
// no help msg? Just return any response
|
||||
if p.Help == "" {
|
||||
line, err := rr.ReadLine('*')
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
cursor := p.NewCursor()
|
||||
|
||||
// process answers looking for help prompt answer
|
||||
for {
|
||||
line, err := rr.ReadLine('*')
|
||||
if err != nil {
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
if string(line) == string(core.HelpInputRune) {
|
||||
// terminal will echo the \n so we need to jump back up one row
|
||||
cursor.PreviousLine(1)
|
||||
|
||||
err = p.Render(
|
||||
PasswordQuestionTemplate,
|
||||
PasswordTemplateData{Password: *p, ShowHelp: true},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return string(line), err
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup hides the string with a fixed number of characters.
|
||||
func (prompt *Password) Cleanup(val interface{}) error {
|
||||
return nil
|
||||
}
|
||||
255
vendor/gopkg.in/AlecAivazis/survey.v1/survey.go
generated
vendored
255
vendor/gopkg.in/AlecAivazis/survey.v1/survey.go
generated
vendored
@@ -1,255 +0,0 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"gopkg.in/AlecAivazis/survey.v1/core"
|
||||
"gopkg.in/AlecAivazis/survey.v1/terminal"
|
||||
)
|
||||
|
||||
// PageSize is the default maximum number of items to show in select/multiselect prompts
|
||||
var PageSize = 7
|
||||
|
||||
// DefaultAskOptions is the default options on ask, using the OS stdio.
|
||||
var DefaultAskOptions = AskOptions{
|
||||
Stdio: terminal.Stdio{
|
||||
In: os.Stdin,
|
||||
Out: os.Stdout,
|
||||
Err: os.Stderr,
|
||||
},
|
||||
}
|
||||
|
||||
// Validator is a function passed to a Question after a user has provided a response.
|
||||
// If the function returns an error, then the user will be prompted again for another
|
||||
// response.
|
||||
type Validator func(ans interface{}) error
|
||||
|
||||
// Transformer is a function passed to a Question after a user has provided a response.
|
||||
// The function can be used to implement a custom logic that will result to return
|
||||
// a different representation of the given answer.
|
||||
//
|
||||
// Look `TransformString`, `ToLower` `Title` and `ComposeTransformers` for more.
|
||||
type Transformer func(ans interface{}) (newAns interface{})
|
||||
|
||||
// Question is the core data structure for a survey questionnaire.
|
||||
type Question struct {
|
||||
Name string
|
||||
Prompt Prompt
|
||||
Validate Validator
|
||||
Transform Transformer
|
||||
}
|
||||
|
||||
// Prompt is the primary interface for the objects that can take user input
|
||||
// and return a response.
|
||||
type Prompt interface {
|
||||
Prompt() (interface{}, error)
|
||||
Cleanup(interface{}) error
|
||||
Error(error) error
|
||||
}
|
||||
|
||||
// PromptAgainer Interface for Prompts that support prompting again after invalid input
|
||||
type PromptAgainer interface {
|
||||
PromptAgain(invalid interface{}, err error) (interface{}, error)
|
||||
}
|
||||
|
||||
// AskOpt allows setting optional ask options.
|
||||
type AskOpt func(options *AskOptions) error
|
||||
|
||||
// AskOptions provides additional options on ask.
|
||||
type AskOptions struct {
|
||||
Stdio terminal.Stdio
|
||||
}
|
||||
|
||||
// WithStdio specifies the standard input, output and error files survey
|
||||
// interacts with. By default, these are os.Stdin, os.Stdout, and os.Stderr.
|
||||
func WithStdio(in terminal.FileReader, out terminal.FileWriter, err io.Writer) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
options.Stdio.In = in
|
||||
options.Stdio.Out = out
|
||||
options.Stdio.Err = err
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type wantsStdio interface {
|
||||
WithStdio(terminal.Stdio)
|
||||
}
|
||||
|
||||
/*
|
||||
AskOne performs the prompt for a single prompt and asks for validation if required.
|
||||
Response types should be something that can be casted from the response type designated
|
||||
in the documentation. For example:
|
||||
|
||||
name := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "name",
|
||||
}
|
||||
|
||||
survey.AskOne(prompt, &name, nil)
|
||||
|
||||
*/
|
||||
func AskOne(p Prompt, response interface{}, v Validator, opts ...AskOpt) error {
|
||||
err := Ask([]*Question{{Prompt: p, Validate: v}}, response, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Ask performs the prompt loop, asking for validation when appropriate. The response
|
||||
type can be one of two options. If a struct is passed, the answer will be written to
|
||||
the field whose name matches the Name field on the corresponding question. Field types
|
||||
should be something that can be casted from the response type designated in the
|
||||
documentation. Note, a survey tag can also be used to identify a Otherwise, a
|
||||
map[string]interface{} can be passed, responses will be written to the key with the
|
||||
matching name. For example:
|
||||
|
||||
qs := []*survey.Question{
|
||||
{
|
||||
Name: "name",
|
||||
Prompt: &survey.Input{Message: "What is your name?"},
|
||||
Validate: survey.Required,
|
||||
Transform: survey.Title,
|
||||
},
|
||||
}
|
||||
|
||||
answers := struct{ Name string }{}
|
||||
|
||||
|
||||
err := survey.Ask(qs, &answers)
|
||||
*/
|
||||
func Ask(qs []*Question, response interface{}, opts ...AskOpt) error {
|
||||
|
||||
options := DefaultAskOptions
|
||||
for _, opt := range opts {
|
||||
if err := opt(&options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// if we weren't passed a place to record the answers
|
||||
if response == nil {
|
||||
// we can't go any further
|
||||
return errors.New("cannot call Ask() with a nil reference to record the answers")
|
||||
}
|
||||
|
||||
// go over every question
|
||||
for _, q := range qs {
|
||||
// If Prompt implements controllable stdio, pass in specified stdio.
|
||||
if p, ok := q.Prompt.(wantsStdio); ok {
|
||||
p.WithStdio(options.Stdio)
|
||||
}
|
||||
|
||||
// grab the user input and save it
|
||||
ans, err := q.Prompt.Prompt()
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if there is a validate handler for this question
|
||||
if q.Validate != nil {
|
||||
// wait for a valid response
|
||||
for invalid := q.Validate(ans); invalid != nil; invalid = q.Validate(ans) {
|
||||
err := q.Prompt.Error(invalid)
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ask for more input
|
||||
if promptAgainer, ok := q.Prompt.(PromptAgainer); ok {
|
||||
ans, err = promptAgainer.PromptAgain(ans, invalid)
|
||||
} else {
|
||||
ans, err = q.Prompt.Prompt()
|
||||
}
|
||||
// if there was a problem
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if q.Transform != nil {
|
||||
// check if we have a transformer available, if so
|
||||
// then try to acquire the new representation of the
|
||||
// answer, if the resulting answer is not nil.
|
||||
if newAns := q.Transform(ans); newAns != nil {
|
||||
ans = newAns
|
||||
}
|
||||
}
|
||||
|
||||
// tell the prompt to cleanup with the validated value
|
||||
q.Prompt.Cleanup(ans)
|
||||
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// stop listening
|
||||
return err
|
||||
}
|
||||
|
||||
// add it to the map
|
||||
err = core.WriteAnswer(response, q.Name, ans)
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// return the response
|
||||
return nil
|
||||
}
|
||||
|
||||
// paginate returns a single page of choices given the page size, the total list of
|
||||
// possible choices, and the current selected index in the total list.
|
||||
func paginate(page int, choices []string, sel int) ([]string, int) {
|
||||
// the number of elements to show in a single page
|
||||
var pageSize int
|
||||
// if the select has a specific page size
|
||||
if page != 0 {
|
||||
// use the specified one
|
||||
pageSize = page
|
||||
// otherwise the select does not have a page size
|
||||
} else {
|
||||
// use the package default
|
||||
pageSize = PageSize
|
||||
}
|
||||
|
||||
var start, end, cursor int
|
||||
|
||||
if len(choices) < pageSize {
|
||||
// if we dont have enough options to fill a page
|
||||
start = 0
|
||||
end = len(choices)
|
||||
cursor = sel
|
||||
|
||||
} else if sel < pageSize/2 {
|
||||
// if we are in the first half page
|
||||
start = 0
|
||||
end = pageSize
|
||||
cursor = sel
|
||||
|
||||
} else if len(choices)-sel-1 < pageSize/2 {
|
||||
// if we are in the last half page
|
||||
start = len(choices) - pageSize
|
||||
end = len(choices)
|
||||
cursor = sel - start
|
||||
|
||||
} else {
|
||||
// somewhere in the middle
|
||||
above := pageSize / 2
|
||||
below := pageSize - above
|
||||
|
||||
cursor = pageSize / 2
|
||||
start = sel - above
|
||||
end = sel + below
|
||||
}
|
||||
|
||||
// return the subset we care about and the index
|
||||
return choices[start:end], cursor
|
||||
}
|
||||
10
vendor/modules.txt
vendored
10
vendor/modules.txt
vendored
@@ -15,6 +15,11 @@ cloud.google.com/go/bigtable
|
||||
cloud.google.com/go/bigtable/internal/option
|
||||
# cloud.google.com/go/storage v1.11.0
|
||||
cloud.google.com/go/storage
|
||||
# github.com/AlecAivazis/survey/v2 v2.2.12
|
||||
## explicit
|
||||
github.com/AlecAivazis/survey/v2
|
||||
github.com/AlecAivazis/survey/v2/core
|
||||
github.com/AlecAivazis/survey/v2/terminal
|
||||
# github.com/Azure/azure-sdk-for-go v51.2.0+incompatible
|
||||
## explicit
|
||||
github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources
|
||||
@@ -2063,11 +2068,6 @@ google.golang.org/protobuf/types/known/structpb
|
||||
google.golang.org/protobuf/types/known/timestamppb
|
||||
google.golang.org/protobuf/types/known/wrapperspb
|
||||
google.golang.org/protobuf/types/pluginpb
|
||||
# gopkg.in/AlecAivazis/survey.v1 v1.8.9-0.20200217094205-6773bdf39b7f
|
||||
## explicit
|
||||
gopkg.in/AlecAivazis/survey.v1
|
||||
gopkg.in/AlecAivazis/survey.v1/core
|
||||
gopkg.in/AlecAivazis/survey.v1/terminal
|
||||
# gopkg.in/inf.v0 v0.9.1
|
||||
gopkg.in/inf.v0
|
||||
# gopkg.in/ini.v1 v1.61.0
|
||||
|
||||
Reference in New Issue
Block a user