forked from toolshed/abra
chore: vendor
This commit is contained in:
55
vendor/github.com/AlecAivazis/survey/v2/CONTRIBUTING.md
generated
vendored
Normal file
55
vendor/github.com/AlecAivazis/survey/v2/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
# Contributing to Survey
|
||||
|
||||
🎉🎉 First off, thanks for the interest in contributing to `survey`! 🎉🎉
|
||||
|
||||
The following is a set of guidelines to follow when contributing to this package. These are not hard rules, please use common sense and feel free to propose changes to this document in a pull request.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project and its contibutors are expected to uphold the [Go Community Code of Conduct](https://golang.org/conduct). By participating, you are expected to follow these guidelines.
|
||||
|
||||
## Getting help
|
||||
|
||||
* [Open an issue](https://github.com/AlecAivazis/survey/issues/new/choose)
|
||||
* Reach out to `@AlecAivazis` or `@mislav` in the Gophers slack (please use only when urgent)
|
||||
|
||||
## Submitting a contribution
|
||||
|
||||
When submitting a contribution,
|
||||
|
||||
- Try to make a series of smaller changes instead of one large change
|
||||
- Provide a description of each change that you are proposing
|
||||
- Reference the issue addressed by your pull request (if there is one)
|
||||
- Document all new exported Go APIs
|
||||
- Update the project's README when applicable
|
||||
- Include unit tests if possible
|
||||
- Contributions with visual ramifications or interaction changes should be accompanied with an integration test—see below for details.
|
||||
|
||||
## Writing and running tests
|
||||
|
||||
When submitting features, please add as many units tests as necessary to test both positive and negative cases.
|
||||
|
||||
Integration tests for survey uses [go-expect](https://github.com/Netflix/go-expect) to expect a match on stdout and respond on stdin. Since `os.Stdout` in a `go test` process is not a TTY, you need a way to interpret terminal / ANSI escape sequences for things like `CursorLocation`. The stdin/stdout handled by `go-expect` is also multiplexed to a [virtual terminal](https://github.com/hinshun/vt10x).
|
||||
|
||||
For example, you can extend the tests for Input by specifying the following test case:
|
||||
|
||||
```go
|
||||
{
|
||||
"Test Input prompt interaction", // Name of the test.
|
||||
&Input{ // An implementation of the survey.Prompt interface.
|
||||
Message: "What is your name?",
|
||||
},
|
||||
func(c *expect.Console) { // An expect procedure. You can expect strings / regexps and
|
||||
c.ExpectString("What is your name?") // write back strings / bytes to its psuedoterminal for survey.
|
||||
c.SendLine("Johnny Appleseed")
|
||||
c.ExpectEOF() // Nothing is read from the tty without an expect, and once an
|
||||
// expectation is met, no further bytes are read. End your
|
||||
// procedure with `c.ExpectEOF()` to read until survey finishes.
|
||||
},
|
||||
"Johnny Appleseed", // The expected result.
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
21
vendor/github.com/AlecAivazis/survey/v2/LICENSE
generated
vendored
Normal file
21
vendor/github.com/AlecAivazis/survey/v2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Alec Aivazis
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
510
vendor/github.com/AlecAivazis/survey/v2/README.md
generated
vendored
Normal file
510
vendor/github.com/AlecAivazis/survey/v2/README.md
generated
vendored
Normal file
@ -0,0 +1,510 @@
|
||||
# Survey
|
||||
|
||||
[](https://pkg.go.dev/github.com/AlecAivazis/survey/v2)
|
||||
|
||||
A library for building interactive and accessible 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)
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Examples can be found in the `examples/` directory. Run them
|
||||
to see basic behavior:
|
||||
|
||||
```bash
|
||||
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))
|
||||
```
|
||||
|
||||
#### Select options description
|
||||
|
||||
The optional description text can be used to add extra information to each option listed in the select prompt:
|
||||
|
||||
```golang
|
||||
color := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
Description: func(value string, index int) string {
|
||||
if value == "red" {
|
||||
return "My favorite color"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
survey.AskOne(prompt, &color)
|
||||
|
||||
// Assuming that the user chose "red - My favorite color":
|
||||
fmt.Println(color) //=> "red"
|
||||
```
|
||||
|
||||
### MultiSelect
|
||||
|
||||

|
||||
|
||||
```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 | |
|
||||
| MaxItems(n) | []OptionAnswer | Enforces that a response has no more selections of the indicated | |
|
||||
| MinItems(n) | []OptionAnswer | Enforces that a response has no less selections of the indicated | |
|
||||
|
||||
## Help Text
|
||||
|
||||
All of the prompts have a `Help` field which can be defined to provide more information to your users:
|
||||
|
||||
<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",
|
||||
}
|
||||
```
|
||||
|
||||
## Removing the "Select All" and "Select None" options
|
||||
|
||||
By default, users can select all of the multi-select options using the right arrow key. To prevent users from being able to do this (and remove the `<right> to all` message from the prompt), use the option `WithRemoveSelectAll`:
|
||||
|
||||
```golang
|
||||
import (
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
number := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "This question has the select all option removed",
|
||||
}
|
||||
|
||||
survey.AskOne(prompt, &number, survey.WithRemoveSelectAll())
|
||||
```
|
||||
|
||||
Also by default, users can use the left arrow key to unselect all of the options. To prevent users from being able to do this (and remove the `<left> to none` message from the prompt), use the option `WithRemoveSelectNone`:
|
||||
|
||||
```golang
|
||||
import (
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
)
|
||||
|
||||
number := ""
|
||||
prompt := &survey.Input{
|
||||
Message: "This question has the select all option removed",
|
||||
}
|
||||
|
||||
survey.AskOne(prompt, &number, survey.WithRemoveSelectNone())
|
||||
```
|
||||
|
||||
|
||||
### Changing the input rune
|
||||
|
||||
In some situations, `?` is a perfectly valid response. To handle this, you can change the rune that survey
|
||||
looks for with `WithHelpInput`:
|
||||
|
||||
```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 Ctrl-C working?
|
||||
|
||||
Ordinarily, when you type Ctrl-C, the terminal recognizes this as the QUIT button and delivers a SIGINT signal to the process, which terminates it.
|
||||
However, Survey temporarily configures the terminal to deliver control codes as ordinary input bytes.
|
||||
When Survey reads a ^C byte (ASCII \x03, "end of text"), it interrupts the current survey and returns a
|
||||
`github.com/AlecAivazis/survey/v2/terminal.InterruptErr` from `Ask` or `AskOne`.
|
||||
If you want to stop the process, handle the returned error in your code:
|
||||
|
||||
```go
|
||||
err := survey.AskOne(prompt, &myVar)
|
||||
if err != nil {
|
||||
if err == terminal.InterruptErr {
|
||||
log.Fatal("interrupted")
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
154
vendor/github.com/AlecAivazis/survey/v2/confirm.go
generated
vendored
Normal file
154
vendor/github.com/AlecAivazis/survey/v2/confirm.go
generated
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Confirm is a regular text input that accept yes/no answers. Response type is a bool.
|
||||
type Confirm struct {
|
||||
Renderer
|
||||
Message string
|
||||
Default bool
|
||||
Help string
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
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 .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"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}
|
||||
{{- color "white"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}}
|
||||
{{- end}}`
|
||||
|
||||
// the regex for answers
|
||||
var (
|
||||
yesRx = regexp.MustCompile("^(?i:y(?:es)?)$")
|
||||
noRx = regexp.MustCompile("^(?i:n(?:o)?)$")
|
||||
)
|
||||
|
||||
func yesNo(t bool) string {
|
||||
if t {
|
||||
return "Yes"
|
||||
}
|
||||
return "No"
|
||||
}
|
||||
|
||||
func (c *Confirm) getBool(showHelp bool, config *PromptConfig) (bool, error) {
|
||||
cursor := c.NewCursor()
|
||||
rr := c.NewRuneReader()
|
||||
_ = rr.SetTermMode()
|
||||
defer func() {
|
||||
_ = rr.RestoreTermMode()
|
||||
}()
|
||||
|
||||
// start waiting for input
|
||||
for {
|
||||
line, err := rr.ReadLine(0)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// move back up a line to compensate for the \n echoed from terminal
|
||||
cursor.PreviousLine(1)
|
||||
val := string(line)
|
||||
|
||||
// get the answer that matches the
|
||||
var answer bool
|
||||
switch {
|
||||
case yesRx.Match([]byte(val)):
|
||||
answer = true
|
||||
case noRx.Match([]byte(val)):
|
||||
answer = false
|
||||
case val == "":
|
||||
answer = c.Default
|
||||
case val == config.HelpInput && c.Help != "":
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{
|
||||
Confirm: *c,
|
||||
ShowHelp: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// use the default value and bubble up
|
||||
return c.Default, err
|
||||
}
|
||||
showHelp = true
|
||||
continue
|
||||
default:
|
||||
// we didnt get a valid answer, so print error and prompt again
|
||||
//lint:ignore ST1005 it should be fine for this error message to have punctuation
|
||||
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,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
// use the default value and bubble up
|
||||
return c.Default, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return answer, nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Prompt prompts the user with a simple text field and expects a reply followed
|
||||
by a carriage return.
|
||||
|
||||
likesPie := false
|
||||
prompt := &survey.Confirm{ Message: "What is your name?" }
|
||||
survey.AskOne(prompt, &likesPie)
|
||||
*/
|
||||
func (c *Confirm) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// render the question template
|
||||
err := c.Render(
|
||||
ConfirmQuestionTemplate,
|
||||
ConfirmTemplateData{
|
||||
Confirm: *c,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// get input and return
|
||||
return c.getBool(false, config)
|
||||
}
|
||||
|
||||
// Cleanup overwrite the line with the finalized formatted version
|
||||
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,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
104
vendor/github.com/AlecAivazis/survey/v2/core/template.go
generated
vendored
Normal file
104
vendor/github.com/AlecAivazis/survey/v2/core/template.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"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 ""
|
||||
},
|
||||
}
|
||||
|
||||
// envColorDisabled returns if output colors are forbid by environment variables
|
||||
func envColorDisabled() bool {
|
||||
return os.Getenv("NO_COLOR") != "" || os.Getenv("CLICOLOR") == "0"
|
||||
}
|
||||
|
||||
// envColorForced returns if output colors are forced from environment variables
|
||||
func envColorForced() bool {
|
||||
val, ok := os.LookupEnv("CLICOLOR_FORCE")
|
||||
return ok && val != "0"
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
envColorHide := envColorDisabled() && !envColorForced()
|
||||
if DisableColor || envColorHide {
|
||||
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
|
||||
}
|
376
vendor/github.com/AlecAivazis/survey/v2/core/write.go
generated
vendored
Normal file
376
vendor/github.com/AlecAivazis/survey/v2/core/write.go
generated
vendored
Normal file
@ -0,0 +1,376 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// the tag used to denote the name of the question
|
||||
const tagName = "survey"
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
type reflectField struct {
|
||||
value reflect.Value
|
||||
fieldType reflect.StructField
|
||||
}
|
||||
|
||||
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 {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
|
||||
// the target to write to
|
||||
target := reflect.ValueOf(t)
|
||||
// the value to write from
|
||||
value := reflect.ValueOf(v)
|
||||
|
||||
// make sure we are writing to a pointer
|
||||
if target.Kind() != reflect.Ptr {
|
||||
return errors.New("you must pass a pointer as the target of a Write operation")
|
||||
}
|
||||
// the object "inside" of the target pointer
|
||||
elem := target.Elem()
|
||||
|
||||
// handle the special types
|
||||
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
|
||||
field, _, err := findField(elem, name)
|
||||
// if something went wrong
|
||||
if err != nil {
|
||||
// bubble up
|
||||
return err
|
||||
}
|
||||
// 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 {
|
||||
// use the interface method
|
||||
return s.WriteAnswer(name, v)
|
||||
}
|
||||
}
|
||||
|
||||
// copy the value over to the normal struct
|
||||
return copy(field, value)
|
||||
case reflect.Map:
|
||||
mapType := reflect.TypeOf(t).Elem()
|
||||
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{})
|
||||
mt[name] = value.Interface()
|
||||
return nil
|
||||
}
|
||||
// otherwise just copy the value to the target
|
||||
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:
|
||||
//
|
||||
// if err := survey.Ask(qs, &v); err != nil {
|
||||
// if name, ok := core.IsFieldNotMatch(err); ok {
|
||||
// // name is the question name that did not match a field
|
||||
// }
|
||||
// }
|
||||
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 findField(s reflect.Value, name string) (reflect.Value, reflect.StructField, error) {
|
||||
|
||||
fields := flattenFields(s)
|
||||
|
||||
// first look for matching tags so we can overwrite matching field names
|
||||
for _, f := range fields {
|
||||
// the value of the survey tag
|
||||
tag := f.fieldType.Tag.Get(tagName)
|
||||
// if the tag matches the name we are looking for
|
||||
if tag != "" && tag == name {
|
||||
// then we found our index
|
||||
return f.value, f.fieldType, nil
|
||||
}
|
||||
}
|
||||
|
||||
// then look for matching names
|
||||
for _, f := range fields {
|
||||
// if the name of the field matches what we're looking for
|
||||
if strings.EqualFold(f.fieldType.Name, name) {
|
||||
return f.value, f.fieldType, nil
|
||||
}
|
||||
}
|
||||
|
||||
// we didn't find the field
|
||||
return reflect.Value{}, reflect.StructField{}, errFieldNotMatch{name}
|
||||
}
|
||||
|
||||
func flattenFields(s reflect.Value) []reflectField {
|
||||
sType := s.Type()
|
||||
numField := sType.NumField()
|
||||
fields := make([]reflectField, 0, numField)
|
||||
for i := 0; i < numField; i++ {
|
||||
fieldType := sType.Field(i)
|
||||
field := s.Field(i)
|
||||
|
||||
if field.Kind() == reflect.Struct && fieldType.Anonymous {
|
||||
// field is a promoted structure
|
||||
fields = append(fields, flattenFields(field)...)
|
||||
continue
|
||||
}
|
||||
fields = append(fields, reflectField{field, fieldType})
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// isList returns true if the element is something we can Len()
|
||||
func isList(v reflect.Value) bool {
|
||||
switch v.Type().Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Write takes a value and copies it to the target
|
||||
func copy(t reflect.Value, v reflect.Value) (err error) {
|
||||
// if something ends up panicing we need to catch it in a deferred func
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
// if we paniced with an error
|
||||
if _, ok := r.(error); ok {
|
||||
// cast the result to an error object
|
||||
err = r.(error)
|
||||
} else if _, ok := r.(string); ok {
|
||||
// otherwise we could have paniced with a string so wrap it in an error
|
||||
err = errors.New(r.(string))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// if we are copying from a string result to something else
|
||||
if v.Kind() == reflect.String && v.Type() != t.Type() {
|
||||
var castVal interface{}
|
||||
var casterr error
|
||||
vString := v.Interface().(string)
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
castVal, casterr = strconv.ParseBool(vString)
|
||||
case reflect.Int:
|
||||
castVal, casterr = strconv.Atoi(vString)
|
||||
case reflect.Int8:
|
||||
var val64 int64
|
||||
val64, casterr = strconv.ParseInt(vString, 10, 8)
|
||||
if casterr == nil {
|
||||
castVal = int8(val64)
|
||||
}
|
||||
case reflect.Int16:
|
||||
var val64 int64
|
||||
val64, casterr = strconv.ParseInt(vString, 10, 16)
|
||||
if casterr == nil {
|
||||
castVal = int16(val64)
|
||||
}
|
||||
case reflect.Int32:
|
||||
var val64 int64
|
||||
val64, casterr = strconv.ParseInt(vString, 10, 32)
|
||||
if casterr == nil {
|
||||
castVal = int32(val64)
|
||||
}
|
||||
case reflect.Int64:
|
||||
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)
|
||||
if casterr == nil {
|
||||
castVal = uint(val64)
|
||||
}
|
||||
case reflect.Uint8:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 8)
|
||||
if casterr == nil {
|
||||
castVal = uint8(val64)
|
||||
}
|
||||
case reflect.Uint16:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 16)
|
||||
if casterr == nil {
|
||||
castVal = uint16(val64)
|
||||
}
|
||||
case reflect.Uint32:
|
||||
var val64 uint64
|
||||
val64, casterr = strconv.ParseUint(vString, 10, 32)
|
||||
if casterr == nil {
|
||||
castVal = uint32(val64)
|
||||
}
|
||||
case reflect.Uint64:
|
||||
castVal, casterr = strconv.ParseUint(vString, 10, 64)
|
||||
case reflect.Float32:
|
||||
var val64 float64
|
||||
val64, casterr = strconv.ParseFloat(vString, 32)
|
||||
if casterr == nil {
|
||||
castVal = float32(val64)
|
||||
}
|
||||
case reflect.Float64:
|
||||
castVal, casterr = strconv.ParseFloat(vString, 64)
|
||||
default:
|
||||
//lint:ignore ST1005 allow this error message to be capitalized
|
||||
return fmt.Errorf("Unable to convert from string to type %s", t.Kind())
|
||||
}
|
||||
|
||||
if casterr != nil {
|
||||
return casterr
|
||||
}
|
||||
|
||||
t.Set(reflect.ValueOf(castVal))
|
||||
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
|
||||
//lint:ignore ST1005 allow this error message to be capitalized
|
||||
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
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
// write to the target given its kind
|
||||
switch t.Kind() {
|
||||
// if its a slice
|
||||
case reflect.Slice:
|
||||
// an object of the correct type
|
||||
obj := reflect.Indirect(reflect.New(t.Type().Elem()))
|
||||
|
||||
// write the appropriate value to the obj and catch any errors
|
||||
if err := copy(obj, v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// just append the value to the end
|
||||
t.Set(reflect.Append(t, obj))
|
||||
// otherwise it could be an array
|
||||
case reflect.Array:
|
||||
// set the index to the appropriate value
|
||||
if err := copy(t.Slice(i, i+1).Index(0), v.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// set the value to the target
|
||||
t.Set(v)
|
||||
}
|
||||
|
||||
// we're done
|
||||
return
|
||||
}
|
226
vendor/github.com/AlecAivazis/survey/v2/editor.go
generated
vendored
Normal file
226
vendor/github.com/AlecAivazis/survey/v2/editor.go
generated
vendored
Normal file
@ -0,0 +1,226 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
shellquote "github.com/kballard/go-shellquote"
|
||||
)
|
||||
|
||||
/*
|
||||
Editor launches an instance of the users preferred editor on a temporary file.
|
||||
The editor to use is determined by reading the $VISUAL or $EDITOR environment
|
||||
variables. If neither of those are present, notepad (on Windows) or vim
|
||||
(others) is used.
|
||||
The launch of the editor is triggered by the enter key. Since the response may
|
||||
be long, it will not be echoed as Input does, instead, it print <Received>.
|
||||
Response type is a string.
|
||||
|
||||
message := ""
|
||||
prompt := &survey.Editor{ Message: "What is your commit message?" }
|
||||
survey.AskOne(prompt, &message)
|
||||
*/
|
||||
type Editor struct {
|
||||
Renderer
|
||||
Message string
|
||||
Default string
|
||||
Help string
|
||||
Editor string
|
||||
HideDefault bool
|
||||
AppendDefault bool
|
||||
FileName string
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type EditorTemplateData struct {
|
||||
Editor
|
||||
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 .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"}}[{{ .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}}`
|
||||
|
||||
var (
|
||||
bom = []byte{0xef, 0xbb, 0xbf}
|
||||
editor = "vim"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if runtime.GOOS == "windows" {
|
||||
editor = "notepad"
|
||||
}
|
||||
if v := os.Getenv("VISUAL"); v != "" {
|
||||
editor = v
|
||||
} else if e := os.Getenv("EDITOR"); e != "" {
|
||||
editor = e
|
||||
}
|
||||
}
|
||||
|
||||
func (e *Editor) PromptAgain(config *PromptConfig, invalid interface{}, err error) (interface{}, error) {
|
||||
initialValue := invalid.(string)
|
||||
return e.prompt(initialValue, config)
|
||||
}
|
||||
|
||||
func (e *Editor) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
initialValue := ""
|
||||
if e.Default != "" && e.AppendDefault {
|
||||
initialValue = e.Default
|
||||
}
|
||||
return e.prompt(initialValue, config)
|
||||
}
|
||||
|
||||
func (e *Editor) prompt(initialValue string, config *PromptConfig) (interface{}, error) {
|
||||
// render the template
|
||||
err := e.Render(
|
||||
EditorQuestionTemplate,
|
||||
EditorTemplateData{
|
||||
Editor: *e,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// start reading runes from the standard in
|
||||
rr := e.NewRuneReader()
|
||||
_ = rr.SetTermMode()
|
||||
defer func() {
|
||||
_ = rr.RestoreTermMode()
|
||||
}()
|
||||
|
||||
cursor := e.NewCursor()
|
||||
cursor.Hide()
|
||||
defer cursor.Show()
|
||||
|
||||
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
|
||||
}
|
||||
if string(r) == config.HelpInput && e.Help != "" {
|
||||
err = e.Render(
|
||||
EditorQuestionTemplate,
|
||||
EditorTemplateData{
|
||||
Editor: *e,
|
||||
ShowHelp: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// prepare the temp file
|
||||
pattern := e.FileName
|
||||
if pattern == "" {
|
||||
pattern = "survey*.txt"
|
||||
}
|
||||
f, err := ioutil.TempFile("", pattern)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer func() {
|
||||
_ = os.Remove(f.Name())
|
||||
}()
|
||||
|
||||
// write utf8 BOM header
|
||||
// The reason why we do this is because notepad.exe on Windows determines the
|
||||
// encoding of an "empty" text file by the locale, for example, GBK in China,
|
||||
// while golang string only handles utf8 well. However, a text file with utf8
|
||||
// BOM header is not considered "empty" on Windows, and the encoding will then
|
||||
// be determined utf8 by notepad.exe, instead of GBK or other encodings.
|
||||
if _, err := f.Write(bom); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// write initial value
|
||||
if _, err := f.WriteString(initialValue); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// close the fd to prevent the editor unable to save file
|
||||
if err := f.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// check is input editor exist
|
||||
if e.Editor != "" {
|
||||
editor = e.Editor
|
||||
}
|
||||
|
||||
stdio := e.Stdio()
|
||||
|
||||
args, err := shellquote.Split(editor)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
args = append(args, f.Name())
|
||||
|
||||
// open the editor
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stdin = stdio.In
|
||||
cmd.Stdout = stdio.Out
|
||||
cmd.Stderr = stdio.Err
|
||||
cursor.Show()
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// raw is a BOM-unstripped UTF8 byte slice
|
||||
raw, err := ioutil.ReadFile(f.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// strip BOM header
|
||||
text := string(bytes.TrimPrefix(raw, bom))
|
||||
|
||||
// check length, return default value on empty
|
||||
if len(text) == 0 && !e.AppendDefault {
|
||||
return e.Default, nil
|
||||
}
|
||||
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func (e *Editor) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
return e.Render(
|
||||
EditorQuestionTemplate,
|
||||
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
|
219
vendor/github.com/AlecAivazis/survey/v2/input.go
generated
vendored
Normal file
219
vendor/github.com/AlecAivazis/survey/v2/input.go
generated
vendored
Normal file
@ -0,0 +1,219 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"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
|
||||
answer string
|
||||
typedAnswer 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}}
|
||||
{{- end}}`
|
||||
|
||||
func (i *Input) onRune(config *PromptConfig) terminal.OnRuneFn {
|
||||
return terminal.OnRuneFn(func(key rune, line []rune) ([]rune, bool, error) {
|
||||
if i.options != nil && (key == terminal.KeyEnter || key == '\n') {
|
||||
return []rune(i.answer), true, nil
|
||||
} else if i.options != nil && key == terminal.KeyEscape {
|
||||
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 {
|
||||
i.answer = string(line)
|
||||
i.typedAnswer = i.answer
|
||||
options := i.Suggest(i.answer)
|
||||
i.selectedIndex = 0
|
||||
if len(options) == 0 {
|
||||
return line, false, nil
|
||||
}
|
||||
|
||||
i.answer = options[0]
|
||||
if len(options) == 1 {
|
||||
i.typedAnswer = i.answer
|
||||
i.options = nil
|
||||
} else {
|
||||
i.options = core.OptionAnswerList(options)
|
||||
}
|
||||
} else {
|
||||
if i.options == nil {
|
||||
return line, false, nil
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
)
|
||||
|
||||
if err == nil {
|
||||
err = errReadLineAgain
|
||||
}
|
||||
|
||||
return []rune(i.typedAnswer), true, err
|
||||
})
|
||||
}
|
||||
|
||||
var errReadLineAgain = errors.New("read line again")
|
||||
|
||||
func (i *Input) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// render the template
|
||||
err := i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{
|
||||
Input: *i,
|
||||
Config: config,
|
||||
ShowHelp: i.showingHelp,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// start reading runes from the standard in
|
||||
rr := i.NewRuneReader()
|
||||
_ = rr.SetTermMode()
|
||||
defer func() {
|
||||
_ = rr.RestoreTermMode()
|
||||
}()
|
||||
cursor := i.NewCursor()
|
||||
if !config.ShowCursor {
|
||||
cursor.Hide() // hide the cursor
|
||||
defer cursor.Show() // show the cursor when we're done
|
||||
}
|
||||
|
||||
var line []rune
|
||||
|
||||
for {
|
||||
if i.options != nil {
|
||||
line = []rune{}
|
||||
}
|
||||
|
||||
line, err = rr.ReadLineWithDefault(0, line, i.onRune(config))
|
||||
if err == errReadLineAgain {
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
i.answer = string(line)
|
||||
// readline print an empty line, go up before we render the follow up
|
||||
cursor.Up(1)
|
||||
|
||||
// if we ran into the help string
|
||||
if i.answer == config.HelpInput && i.Help != "" {
|
||||
// show the help and prompt again
|
||||
i.showingHelp = true
|
||||
return i.Prompt(config)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return i.Render(
|
||||
InputQuestionTemplate,
|
||||
InputTemplateData{
|
||||
Input: *i,
|
||||
ShowAnswer: true,
|
||||
Config: config,
|
||||
Answer: val.(string),
|
||||
},
|
||||
)
|
||||
}
|
112
vendor/github.com/AlecAivazis/survey/v2/multiline.go
generated
vendored
Normal file
112
vendor/github.com/AlecAivazis/survey/v2/multiline.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
type Multiline struct {
|
||||
Renderer
|
||||
Message string
|
||||
Default string
|
||||
Help string
|
||||
}
|
||||
|
||||
// data available to the templates when processing
|
||||
type MultilineTemplateData struct {
|
||||
Multiline
|
||||
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 .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"}}
|
||||
{{- if .Answer }}{{ "\n" }}{{ end }}
|
||||
{{- else }}
|
||||
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
|
||||
{{- color "cyan"}}[Enter 2 empty lines to finish]{{color "reset"}}
|
||||
{{- end}}`
|
||||
|
||||
func (i *Multiline) Prompt(config *PromptConfig) (interface{}, error) {
|
||||
// render the template
|
||||
err := i.Render(
|
||||
MultilineQuestionTemplate,
|
||||
MultilineTemplateData{
|
||||
Multiline: *i,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// start reading runes from the standard in
|
||||
rr := i.NewRuneReader()
|
||||
_ = rr.SetTermMode()
|
||||
defer func() {
|
||||
_ = rr.RestoreTermMode()
|
||||
}()
|
||||
|
||||
cursor := i.NewCursor()
|
||||
|
||||
multiline := make([]string, 0)
|
||||
|
||||
emptyOnce := false
|
||||
// get the next line
|
||||
for {
|
||||
var line []rune
|
||||
line, err = rr.ReadLine(0)
|
||||
if err != nil {
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
if string(line) == "" {
|
||||
if emptyOnce {
|
||||
numLines := len(multiline) + 2
|
||||
cursor.PreviousLine(numLines)
|
||||
for j := 0; j < numLines; j++ {
|
||||
terminal.EraseLine(i.Stdio().Out, terminal.ERASE_LINE_ALL)
|
||||
cursor.NextLine(1)
|
||||
}
|
||||
cursor.PreviousLine(numLines)
|
||||
break
|
||||
}
|
||||
emptyOnce = true
|
||||
} else {
|
||||
emptyOnce = false
|
||||
}
|
||||
multiline = append(multiline, string(line))
|
||||
}
|
||||
|
||||
val := strings.Join(multiline, "\n")
|
||||
val = strings.TrimSpace(val)
|
||||
|
||||
// if the line is empty
|
||||
if len(val) == 0 {
|
||||
// use the default value
|
||||
return i.Default, err
|
||||
}
|
||||
|
||||
i.AppendRenderedText(val)
|
||||
return val, err
|
||||
}
|
||||
|
||||
func (i *Multiline) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
return i.Render(
|
||||
MultilineQuestionTemplate,
|
||||
MultilineTemplateData{
|
||||
Multiline: *i,
|
||||
Answer: val.(string),
|
||||
ShowAnswer: true,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
360
vendor/github.com/AlecAivazis/survey/v2/multiselect.go
generated
vendored
Normal file
360
vendor/github.com/AlecAivazis/survey/v2/multiselect.go
generated
vendored
Normal file
@ -0,0 +1,360 @@
|
||||
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
|
||||
Description func(value string, index int) string
|
||||
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
|
||||
Description func(value string, index int) string
|
||||
PageEntries []core.OptionAnswer
|
||||
Config *PromptConfig
|
||||
|
||||
// These fields are used when rendering an individual option
|
||||
CurrentOpt core.OptionAnswer
|
||||
CurrentIndex int
|
||||
}
|
||||
|
||||
// IterateOption sets CurrentOpt and CurrentIndex appropriately so a multiselect option can be rendered individually
|
||||
func (m MultiSelectTemplateData) IterateOption(ix int, opt core.OptionAnswer) interface{} {
|
||||
copy := m
|
||||
copy.CurrentIndex = ix
|
||||
copy.CurrentOpt = opt
|
||||
return copy
|
||||
}
|
||||
|
||||
func (m MultiSelectTemplateData) GetDescription(opt core.OptionAnswer) string {
|
||||
if m.Description == nil {
|
||||
return ""
|
||||
}
|
||||
return m.Description(opt.Value, opt.Index)
|
||||
}
|
||||
|
||||
var MultiSelectQuestionTemplate = `
|
||||
{{- define "option"}}
|
||||
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }}{{color "reset"}}{{else}} {{end}}
|
||||
{{- if index .Checked .CurrentOpt.Index }}{{color .Config.Icons.MarkedOption.Format }} {{ .Config.Icons.MarkedOption.Text }} {{else}}{{color .Config.Icons.UnmarkedOption.Format }} {{ .Config.Icons.UnmarkedOption.Text }} {{end}}
|
||||
{{- color "reset"}}
|
||||
{{- " "}}{{- .CurrentOpt.Value}}{{ if ne ($.GetDescription .CurrentOpt) "" }} - {{color "cyan"}}{{ $.GetDescription .CurrentOpt }}{{color "reset"}}{{end}}
|
||||
{{end}}
|
||||
{{- 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,{{- if not .Config.RemoveSelectAll }} <right> to all,{{end}}{{- if not .Config.RemoveSelectNone }} <left> to none,{{end}} type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $option := .PageEntries}}
|
||||
{{- template "option" $.IterateOption $ix $option}}
|
||||
{{- 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 !config.RemoveSelectAll && key == terminal.KeyArrowRight {
|
||||
for _, v := range options {
|
||||
m.checked[v.Index] = true
|
||||
}
|
||||
if !config.KeepFilter {
|
||||
m.filter = ""
|
||||
}
|
||||
} else if !config.RemoveSelectNone && 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)
|
||||
|
||||
tmplData := MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Checked: m.checked,
|
||||
ShowHelp: m.showingHelp,
|
||||
Description: m.Description,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
// render the options
|
||||
_ = m.RenderWithCursorOffset(MultiSelectQuestionTemplate, tmplData, opts, idx)
|
||||
}
|
||||
|
||||
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.Save() // for proper cursor placement during selection
|
||||
cursor.Hide() // hide the cursor
|
||||
defer cursor.Show() // show the cursor when we're done
|
||||
defer cursor.Restore() // clear any accessibility offsetting on exit
|
||||
|
||||
tmplData := MultiSelectTemplateData{
|
||||
MultiSelect: *m,
|
||||
SelectedIndex: idx,
|
||||
Description: m.Description,
|
||||
Checked: m.checked,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
// ask the question
|
||||
err := m.RenderWithCursorOffset(MultiSelectQuestionTemplate, tmplData, opts, idx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := m.NewRuneReader()
|
||||
_ = rr.SetTermMode()
|
||||
defer func() {
|
||||
_ = 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,
|
||||
Description: m.Description,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
106
vendor/github.com/AlecAivazis/survey/v2/password.go
generated
vendored
Normal file
106
vendor/github.com/AlecAivazis/survey/v2/password.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
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,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if _, err := fmt.Fprint(terminal.NewAnsiStdout(p.Stdio().Out), userOut); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := p.NewRuneReader()
|
||||
_ = rr.SetTermMode()
|
||||
defer func() {
|
||||
_ = rr.RestoreTermMode()
|
||||
}()
|
||||
|
||||
// no help msg? Just return any response
|
||||
if p.Help == "" {
|
||||
line, err := rr.ReadLine(config.HideCharacter)
|
||||
return string(line), err
|
||||
}
|
||||
|
||||
cursor := p.NewCursor()
|
||||
|
||||
var line []rune
|
||||
// process answers looking for help prompt answer
|
||||
for {
|
||||
line, err = rr.ReadLine(config.HideCharacter)
|
||||
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(string(config.HideCharacter), 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
|
||||
}
|
195
vendor/github.com/AlecAivazis/survey/v2/renderer.go
generated
vendored
Normal file
195
vendor/github.com/AlecAivazis/survey/v2/renderer.go
generated
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
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
|
||||
if _, err := fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add the printed text to the rendered error buffer so we can cleanup later
|
||||
r.appendRenderedError(layoutOut)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) OffsetCursor(offset int) {
|
||||
cursor := r.NewCursor()
|
||||
for offset > 0 {
|
||||
cursor.PreviousLine(1)
|
||||
offset--
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if _, err := fmt.Fprint(terminal.NewAnsiStdout(r.stdio.Out), userOut); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add the printed text to the rendered text buffer so we can cleanup later
|
||||
r.AppendRenderedText(layoutOut)
|
||||
|
||||
// nothing went wrong
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Renderer) RenderWithCursorOffset(tmpl string, data IterableOpts, opts []core.OptionAnswer, idx int) error {
|
||||
cursor := r.NewCursor()
|
||||
cursor.Restore() // clear any accessibility offsetting
|
||||
|
||||
if err := r.Render(tmpl, data); err != nil {
|
||||
return err
|
||||
}
|
||||
cursor.Save()
|
||||
|
||||
offset := computeCursorOffset(MultiSelectQuestionTemplate, data, opts, idx, r.termWidthSafe())
|
||||
r.OffsetCursor(offset)
|
||||
|
||||
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 := term.GetSize(fd)
|
||||
return termWidth, err
|
||||
}
|
||||
|
||||
func (r *Renderer) termWidthSafe() 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
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// 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 := r.termWidthSafe()
|
||||
|
||||
bufBytes := buf.Bytes()
|
||||
|
||||
count := 0
|
||||
curr := 0
|
||||
for curr < len(bufBytes) {
|
||||
var delim int
|
||||
// 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
|
||||
}
|
||||
|
||||
str := string(bufBytes[curr:delim])
|
||||
if lineWidth := terminal.StringWidth(str); 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
|
||||
}
|
329
vendor/github.com/AlecAivazis/survey/v2/select.go
generated
vendored
Normal file
329
vendor/github.com/AlecAivazis/survey/v2/select.go
generated
vendored
Normal file
@ -0,0 +1,329 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
)
|
||||
|
||||
/*
|
||||
Select 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 string.
|
||||
|
||||
color := ""
|
||||
prompt := &survey.Select{
|
||||
Message: "Choose a color:",
|
||||
Options: []string{"red", "blue", "green"},
|
||||
}
|
||||
survey.AskOne(prompt, &color)
|
||||
*/
|
||||
type Select 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
|
||||
Description func(value string, index int) string
|
||||
filter string
|
||||
selectedIndex int
|
||||
showingHelp bool
|
||||
}
|
||||
|
||||
// SelectTemplateData is the data available to the templates when processing
|
||||
type SelectTemplateData struct {
|
||||
Select
|
||||
PageEntries []core.OptionAnswer
|
||||
SelectedIndex int
|
||||
Answer string
|
||||
ShowAnswer bool
|
||||
ShowHelp bool
|
||||
Description func(value string, index int) string
|
||||
Config *PromptConfig
|
||||
|
||||
// These fields are used when rendering an individual option
|
||||
CurrentOpt core.OptionAnswer
|
||||
CurrentIndex int
|
||||
}
|
||||
|
||||
// IterateOption sets CurrentOpt and CurrentIndex appropriately so a select option can be rendered individually
|
||||
func (s SelectTemplateData) IterateOption(ix int, opt core.OptionAnswer) interface{} {
|
||||
copy := s
|
||||
copy.CurrentIndex = ix
|
||||
copy.CurrentOpt = opt
|
||||
return copy
|
||||
}
|
||||
|
||||
func (s SelectTemplateData) GetDescription(opt core.OptionAnswer) string {
|
||||
if s.Description == nil {
|
||||
return ""
|
||||
}
|
||||
return s.Description(opt.Value, opt.Index)
|
||||
}
|
||||
|
||||
var SelectQuestionTemplate = `
|
||||
{{- define "option"}}
|
||||
{{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}}
|
||||
{{- .CurrentOpt.Value}}{{ if ne ($.GetDescription .CurrentOpt) "" }} - {{color "cyan"}}{{ $.GetDescription .CurrentOpt }}{{end}}
|
||||
{{- color "reset"}}
|
||||
{{end}}
|
||||
{{- 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, type to filter{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}}
|
||||
{{- "\n"}}
|
||||
{{- range $ix, $option := .PageEntries}}
|
||||
{{- template "option" $.IterateOption $ix $option}}
|
||||
{{- end}}
|
||||
{{- end}}`
|
||||
|
||||
// OnChange is called on every keypress.
|
||||
func (s *Select) OnChange(key rune, config *PromptConfig) bool {
|
||||
options := s.filterOptions(config)
|
||||
oldFilter := s.filter
|
||||
|
||||
// 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 {
|
||||
// if we are at the top of the list
|
||||
if s.selectedIndex == 0 {
|
||||
// start from the button
|
||||
s.selectedIndex = len(options) - 1
|
||||
} else {
|
||||
// otherwise we are not at the top of the list so decrement the selected index
|
||||
s.selectedIndex--
|
||||
}
|
||||
|
||||
// if the user pressed down or 'j' to emulate vim
|
||||
} else if (key == terminal.KeyTab || key == terminal.KeyArrowDown || (s.VimMode && key == 'j')) && len(options) > 0 {
|
||||
// if we are at the bottom of the list
|
||||
if s.selectedIndex == len(options)-1 {
|
||||
// start from the top
|
||||
s.selectedIndex = 0
|
||||
} else {
|
||||
// increment the selected index
|
||||
s.selectedIndex++
|
||||
}
|
||||
// only show the help message if we have one
|
||||
} 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 {
|
||||
s.VimMode = !s.VimMode
|
||||
// if the user hits any of the keys that clear the filter
|
||||
} else if key == terminal.KeyDeleteWord || key == terminal.KeyDeleteLine {
|
||||
s.filter = ""
|
||||
// if the user is deleting a character in the filter
|
||||
} 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 = string(runeFilter[0 : len(runeFilter)-1])
|
||||
// we removed the last value in the filter
|
||||
}
|
||||
} else if key >= terminal.KeySpace {
|
||||
s.filter += string(key)
|
||||
// make sure vim mode is disabled
|
||||
s.VimMode = false
|
||||
}
|
||||
|
||||
s.FilterMessage = ""
|
||||
if s.filter != "" {
|
||||
s.FilterMessage = " " + s.filter
|
||||
}
|
||||
if oldFilter != s.filter {
|
||||
// filter changed
|
||||
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(pageSize, options, s.selectedIndex)
|
||||
|
||||
tmplData := SelectTemplateData{
|
||||
Select: *s,
|
||||
SelectedIndex: idx,
|
||||
ShowHelp: s.showingHelp,
|
||||
Description: s.Description,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
// render the options
|
||||
_ = s.RenderWithCursorOffset(SelectQuestionTemplate, tmplData, opts, idx)
|
||||
|
||||
// keep prompting
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Select) filterOptions(config *PromptConfig) []core.OptionAnswer {
|
||||
// the filtered list
|
||||
answers := []core.OptionAnswer{}
|
||||
|
||||
// if there is no filter applied
|
||||
if s.filter == "" {
|
||||
return core.OptionAnswerList(s.Options)
|
||||
}
|
||||
|
||||
// the filter to apply
|
||||
filter := s.Filter
|
||||
if filter == nil {
|
||||
filter = config.Filter
|
||||
}
|
||||
|
||||
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(config *PromptConfig) (interface{}, error) {
|
||||
// if there are no options to render
|
||||
if len(s.Options) == 0 {
|
||||
// we failed
|
||||
return "", errors.New("please provide options to select from")
|
||||
}
|
||||
|
||||
s.selectedIndex = 0
|
||||
if s.Default != nil {
|
||||
switch defaultValue := s.Default.(type) {
|
||||
case string:
|
||||
var found bool
|
||||
for i, opt := range s.Options {
|
||||
if opt == defaultValue {
|
||||
s.selectedIndex = i
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return "", fmt.Errorf("default value %q not found in options", defaultValue)
|
||||
}
|
||||
case int:
|
||||
if defaultValue >= len(s.Options) {
|
||||
return "", fmt.Errorf("default index %d exceeds the number of options", defaultValue)
|
||||
}
|
||||
s.selectedIndex = defaultValue
|
||||
default:
|
||||
return "", errors.New("default value of select must be an int or string")
|
||||
}
|
||||
}
|
||||
|
||||
// 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(pageSize, core.OptionAnswerList(s.Options), s.selectedIndex)
|
||||
|
||||
cursor := s.NewCursor()
|
||||
cursor.Save() // for proper cursor placement during selection
|
||||
cursor.Hide() // hide the cursor
|
||||
defer cursor.Show() // show the cursor when we're done
|
||||
defer cursor.Restore() // clear any accessibility offsetting on exit
|
||||
|
||||
tmplData := SelectTemplateData{
|
||||
Select: *s,
|
||||
SelectedIndex: idx,
|
||||
Description: s.Description,
|
||||
ShowHelp: s.showingHelp,
|
||||
PageEntries: opts,
|
||||
Config: config,
|
||||
}
|
||||
|
||||
// ask the question
|
||||
err := s.RenderWithCursorOffset(SelectQuestionTemplate, tmplData, opts, idx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rr := s.NewRuneReader()
|
||||
_ = rr.SetTermMode()
|
||||
defer func() {
|
||||
_ = rr.RestoreTermMode()
|
||||
}()
|
||||
|
||||
// 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
|
||||
}
|
||||
if s.OnChange(r, config) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
options := s.filterOptions(config)
|
||||
s.filter = ""
|
||||
s.FilterMessage = ""
|
||||
|
||||
if s.selectedIndex < len(options) {
|
||||
return options[s.selectedIndex], err
|
||||
}
|
||||
|
||||
return options[0], err
|
||||
}
|
||||
|
||||
func (s *Select) Cleanup(config *PromptConfig, val interface{}) error {
|
||||
cursor := s.NewCursor()
|
||||
cursor.Restore()
|
||||
return s.Render(
|
||||
SelectQuestionTemplate,
|
||||
SelectTemplateData{
|
||||
Select: *s,
|
||||
Answer: val.(core.OptionAnswer).Value,
|
||||
ShowAnswer: true,
|
||||
Description: s.Description,
|
||||
Config: config,
|
||||
},
|
||||
)
|
||||
}
|
474
vendor/github.com/AlecAivazis/survey/v2/survey.go
generated
vendored
Normal file
474
vendor/github.com/AlecAivazis/survey/v2/survey.go
generated
vendored
Normal file
@ -0,0 +1,474 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"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,
|
||||
RemoveSelectAll: false,
|
||||
RemoveSelectNone: false,
|
||||
HideCharacter: '*',
|
||||
},
|
||||
}
|
||||
}
|
||||
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
|
||||
RemoveSelectAll bool
|
||||
RemoveSelectNone bool
|
||||
HideCharacter rune
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// WithRemoveSelectAll remove the select all option in Multiselect
|
||||
func WithRemoveSelectAll() AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
options.PromptConfig.RemoveSelectAll = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRemoveSelectNone remove the select none/unselect all in Multiselect
|
||||
func WithRemoveSelectNone() AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
options.PromptConfig.RemoveSelectNone = true
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// WithHideCharacter sets the default character shown instead of the password for password inputs
|
||||
func WithHideCharacter(char rune) AskOpt {
|
||||
return func(options *AskOptions) error {
|
||||
// set the hide character
|
||||
options.PromptConfig.HideCharacter = char
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
validate := func(q *Question, val interface{}) error {
|
||||
if q.Validate != nil {
|
||||
if err := q.Validate(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, v := range options.Validators {
|
||||
if err := v(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
var ans interface{}
|
||||
var validationErr error
|
||||
// prompt and validation loop
|
||||
for {
|
||||
if validationErr != nil {
|
||||
if err := q.Prompt.Error(&options.PromptConfig, validationErr); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
if promptAgainer, ok := q.Prompt.(PromptAgainer); ok && validationErr != nil {
|
||||
ans, err = promptAgainer.PromptAgain(&options.PromptConfig, ans, validationErr)
|
||||
} else {
|
||||
ans, err = q.Prompt.Prompt(&options.PromptConfig)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
validationErr = validate(q, ans)
|
||||
if validationErr == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if err := q.Prompt.Cleanup(&options.PromptConfig, ans); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// add it to the map
|
||||
if err := core.WriteAnswer(response, q.Name, ans); 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
|
||||
}
|
||||
|
||||
type IterableOpts interface {
|
||||
IterateOption(int, core.OptionAnswer) interface{}
|
||||
}
|
||||
|
||||
func computeCursorOffset(tmpl string, data IterableOpts, opts []core.OptionAnswer, idx, tWidth int) int {
|
||||
tmpls, err := core.GetTemplatePair(tmpl)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
t := tmpls[0]
|
||||
|
||||
renderOpt := func(ix int, opt core.OptionAnswer) string {
|
||||
var buf bytes.Buffer
|
||||
_ = t.ExecuteTemplate(&buf, "option", data.IterateOption(ix, opt))
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
offset := len(opts) - idx
|
||||
|
||||
for i, o := range opts {
|
||||
if i < idx {
|
||||
continue
|
||||
}
|
||||
renderedOpt := renderOpt(i, o)
|
||||
valWidth := utf8.RuneCount([]byte(renderedOpt))
|
||||
if valWidth > tWidth {
|
||||
splitCount := valWidth / tWidth
|
||||
if valWidth%tWidth == 0 {
|
||||
splitCount -= 1
|
||||
}
|
||||
offset += splitCount
|
||||
}
|
||||
}
|
||||
|
||||
return offset
|
||||
}
|
22
vendor/github.com/AlecAivazis/survey/v2/terminal/LICENSE.txt
generated
vendored
Normal file
22
vendor/github.com/AlecAivazis/survey/v2/terminal/LICENSE.txt
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
Copyright (c) 2014 Takashi Kokubun
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
3
vendor/github.com/AlecAivazis/survey/v2/terminal/README.md
generated
vendored
Normal file
3
vendor/github.com/AlecAivazis/survey/v2/terminal/README.md
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# survey/terminal
|
||||
|
||||
This package started as a copy of [kokuban/go-ansi](http://github.com/k0kubun/go-ansi) but has since been modified to fit survey's specific needs.
|
22
vendor/github.com/AlecAivazis/survey/v2/terminal/buffered_reader.go
generated
vendored
Normal file
22
vendor/github.com/AlecAivazis/survey/v2/terminal/buffered_reader.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type BufferedReader struct {
|
||||
In io.Reader
|
||||
Buffer *bytes.Buffer
|
||||
}
|
||||
|
||||
func (br *BufferedReader) Read(p []byte) (int, error) {
|
||||
n, err := br.Buffer.Read(p)
|
||||
if err != nil && err != io.EOF {
|
||||
return n, err
|
||||
} else if err == nil {
|
||||
return n, nil
|
||||
}
|
||||
|
||||
return br.In.Read(p[n:])
|
||||
}
|
209
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor.go
generated
vendored
Normal file
209
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor.go
generated
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var COORDINATE_SYSTEM_BEGIN Short = 1
|
||||
|
||||
var dsrPattern = regexp.MustCompile(`\x1b\[(\d+);(\d+)R$`)
|
||||
|
||||
type Cursor struct {
|
||||
In FileReader
|
||||
Out FileWriter
|
||||
}
|
||||
|
||||
// Up moves the cursor n cells to up.
|
||||
func (c *Cursor) Up(n int) error {
|
||||
_, err := fmt.Fprintf(c.Out, "\x1b[%dA", n)
|
||||
return err
|
||||
}
|
||||
|
||||
// Down moves the cursor n cells to down.
|
||||
func (c *Cursor) Down(n int) error {
|
||||
_, err := fmt.Fprintf(c.Out, "\x1b[%dB", n)
|
||||
return err
|
||||
}
|
||||
|
||||
// Forward moves the cursor n cells to right.
|
||||
func (c *Cursor) Forward(n int) error {
|
||||
_, err := fmt.Fprintf(c.Out, "\x1b[%dC", n)
|
||||
return err
|
||||
}
|
||||
|
||||
// Back moves the cursor n cells to left.
|
||||
func (c *Cursor) Back(n int) error {
|
||||
_, err := fmt.Fprintf(c.Out, "\x1b[%dD", n)
|
||||
return err
|
||||
}
|
||||
|
||||
// NextLine moves cursor to beginning of the line n lines down.
|
||||
func (c *Cursor) NextLine(n int) error {
|
||||
if err := c.Down(1); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
// PreviousLine moves cursor to beginning of the line n lines up.
|
||||
func (c *Cursor) PreviousLine(n int) error {
|
||||
if err := c.Up(1); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
// HorizontalAbsolute moves cursor horizontally to x.
|
||||
func (c *Cursor) HorizontalAbsolute(x int) error {
|
||||
_, err := fmt.Fprintf(c.Out, "\x1b[%dG", x)
|
||||
return err
|
||||
}
|
||||
|
||||
// Show shows the cursor.
|
||||
func (c *Cursor) Show() error {
|
||||
_, err := fmt.Fprint(c.Out, "\x1b[?25h")
|
||||
return err
|
||||
}
|
||||
|
||||
// Hide hide the cursor.
|
||||
func (c *Cursor) Hide() error {
|
||||
_, err := fmt.Fprint(c.Out, "\x1b[?25l")
|
||||
return err
|
||||
}
|
||||
|
||||
// move moves the cursor to a specific x,y location.
|
||||
func (c *Cursor) move(x int, y int) error {
|
||||
_, err := fmt.Fprintf(c.Out, "\x1b[%d;%df", x, y)
|
||||
return err
|
||||
}
|
||||
|
||||
// Save saves the current position
|
||||
func (c *Cursor) Save() error {
|
||||
_, err := fmt.Fprint(c.Out, "\x1b7")
|
||||
return err
|
||||
}
|
||||
|
||||
// Restore restores the saved position of the cursor
|
||||
func (c *Cursor) Restore() error {
|
||||
_, err := fmt.Fprint(c.Out, "\x1b8")
|
||||
return err
|
||||
}
|
||||
|
||||
// for comparability purposes between windows
|
||||
// in unix we need to print out a new line on some terminals
|
||||
func (c *Cursor) MoveNextLine(cur *Coord, terminalSize *Coord) error {
|
||||
if cur.Y == terminalSize.Y {
|
||||
if _, err := fmt.Fprintln(c.Out); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return c.NextLine(1)
|
||||
}
|
||||
|
||||
// Location returns the current location of the cursor in the terminal
|
||||
func (c *Cursor) Location(buf *bytes.Buffer) (*Coord, error) {
|
||||
// ANSI escape sequence for DSR - Device Status Report
|
||||
// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
|
||||
if _, err := fmt.Fprint(c.Out, "\x1b[6n"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// There may be input in Stdin prior to CursorLocation so make sure we don't
|
||||
// drop those bytes.
|
||||
var loc []int
|
||||
var match string
|
||||
for loc == nil {
|
||||
// Reports the cursor position (CPR) to the application as (as though typed at
|
||||
// the keyboard) ESC[n;mR, where n is the row and m is the column.
|
||||
reader := bufio.NewReader(c.In)
|
||||
text, err := reader.ReadSlice(byte('R'))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loc = dsrPattern.FindStringIndex(string(text))
|
||||
if loc == nil {
|
||||
// After reading slice to byte 'R', the bufio Reader may have read more
|
||||
// bytes into its internal buffer which will be discarded on next ReadSlice.
|
||||
// We create a temporary buffer to read the remaining buffered slice and
|
||||
// write them to output buffer.
|
||||
buffered := make([]byte, reader.Buffered())
|
||||
_, err = io.ReadFull(reader, buffered)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stdin contains R that doesn't match DSR, so pass the bytes along to
|
||||
// output buffer.
|
||||
buf.Write(text)
|
||||
buf.Write(buffered)
|
||||
} else {
|
||||
// Write the non-matching leading bytes to output buffer.
|
||||
buf.Write(text[:loc[0]])
|
||||
|
||||
// Save the matching bytes to extract the row and column of the cursor.
|
||||
match = string(text[loc[0]:loc[1]])
|
||||
}
|
||||
}
|
||||
|
||||
matches := dsrPattern.FindStringSubmatch(string(match))
|
||||
if len(matches) != 3 {
|
||||
return nil, fmt.Errorf("incorrect number of matches: %d", len(matches))
|
||||
}
|
||||
|
||||
col, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
row, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Coord{Short(col), Short(row)}, nil
|
||||
}
|
||||
|
||||
func (cur Coord) CursorIsAtLineEnd(size *Coord) bool {
|
||||
return cur.X == size.X
|
||||
}
|
||||
|
||||
func (cur Coord) CursorIsAtLineBegin() bool {
|
||||
return cur.X == COORDINATE_SYSTEM_BEGIN
|
||||
}
|
||||
|
||||
// Size returns the height and width of the terminal.
|
||||
func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
|
||||
// the general approach here is to move the cursor to the very bottom
|
||||
// of the terminal, ask for the current location and then move the
|
||||
// cursor back where we started
|
||||
|
||||
// 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)
|
||||
|
||||
// ask for the current location
|
||||
bottom, err := c.Location(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// since the bottom was calculated in the lower right corner, it
|
||||
// is the dimensions we are looking for
|
||||
return bottom, nil
|
||||
}
|
164
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor_windows.go
generated
vendored
Normal file
164
vendor/github.com/AlecAivazis/survey/v2/terminal/cursor_windows.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var COORDINATE_SYSTEM_BEGIN Short = 0
|
||||
|
||||
// shared variable to save the cursor location from CursorSave()
|
||||
var cursorLoc Coord
|
||||
|
||||
type Cursor struct {
|
||||
In FileReader
|
||||
Out FileWriter
|
||||
}
|
||||
|
||||
func (c *Cursor) Up(n int) error {
|
||||
return c.cursorMove(0, n)
|
||||
}
|
||||
|
||||
func (c *Cursor) Down(n int) error {
|
||||
return c.cursorMove(0, -1*n)
|
||||
}
|
||||
|
||||
func (c *Cursor) Forward(n int) error {
|
||||
return c.cursorMove(n, 0)
|
||||
}
|
||||
|
||||
func (c *Cursor) Back(n int) error {
|
||||
return c.cursorMove(-1*n, 0)
|
||||
}
|
||||
|
||||
// save the cursor location
|
||||
func (c *Cursor) Save() error {
|
||||
loc, err := c.Location(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cursorLoc = *loc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cursor) Restore() error {
|
||||
handle := syscall.Handle(c.Out.Fd())
|
||||
// restore it to the original position
|
||||
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursorLoc))))
|
||||
return normalizeError(err)
|
||||
}
|
||||
|
||||
func (cur Coord) CursorIsAtLineEnd(size *Coord) bool {
|
||||
return cur.X == size.X
|
||||
}
|
||||
|
||||
func (cur Coord) CursorIsAtLineBegin() bool {
|
||||
return cur.X == 0
|
||||
}
|
||||
|
||||
func (c *Cursor) cursorMove(x int, y int) error {
|
||||
handle := syscall.Handle(c.Out.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cursor Coord
|
||||
cursor.X = csbi.cursorPosition.X + Short(x)
|
||||
cursor.Y = csbi.cursorPosition.Y + Short(y)
|
||||
|
||||
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||
return normalizeError(err)
|
||||
}
|
||||
|
||||
func (c *Cursor) NextLine(n int) error {
|
||||
if err := c.Up(n); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
func (c *Cursor) PreviousLine(n int) error {
|
||||
if err := c.Down(n); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.HorizontalAbsolute(0)
|
||||
}
|
||||
|
||||
// for comparability purposes between windows
|
||||
// in windows we don't have to print out a new line
|
||||
func (c *Cursor) MoveNextLine(cur *Coord, terminalSize *Coord) error {
|
||||
return c.NextLine(1)
|
||||
}
|
||||
|
||||
func (c *Cursor) HorizontalAbsolute(x int) error {
|
||||
handle := syscall.Handle(c.Out.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cursor Coord
|
||||
cursor.X = Short(x)
|
||||
cursor.Y = csbi.cursorPosition.Y
|
||||
|
||||
if csbi.size.X < cursor.X {
|
||||
cursor.X = csbi.size.X
|
||||
}
|
||||
|
||||
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
|
||||
return normalizeError(err)
|
||||
}
|
||||
|
||||
func (c *Cursor) Show() error {
|
||||
handle := syscall.Handle(c.Out.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
if _, _, err := procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))); normalizeError(err) != nil {
|
||||
return err
|
||||
}
|
||||
cci.visible = 1
|
||||
|
||||
_, _, err := procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
return normalizeError(err)
|
||||
}
|
||||
|
||||
func (c *Cursor) Hide() error {
|
||||
handle := syscall.Handle(c.Out.Fd())
|
||||
|
||||
var cci consoleCursorInfo
|
||||
if _, _, err := procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))); normalizeError(err) != nil {
|
||||
return err
|
||||
}
|
||||
cci.visible = 0
|
||||
|
||||
_, _, err := procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
|
||||
return normalizeError(err)
|
||||
}
|
||||
|
||||
func (c *Cursor) Location(buf *bytes.Buffer) (*Coord, error) {
|
||||
handle := syscall.Handle(c.Out.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &csbi.cursorPosition, nil
|
||||
}
|
||||
|
||||
func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
|
||||
handle := syscall.Handle(c.Out.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||
return nil, err
|
||||
}
|
||||
// windows' coordinate system begins at (0, 0)
|
||||
csbi.size.X--
|
||||
csbi.size.Y--
|
||||
return &csbi.size, nil
|
||||
}
|
9
vendor/github.com/AlecAivazis/survey/v2/terminal/display.go
generated
vendored
Normal file
9
vendor/github.com/AlecAivazis/survey/v2/terminal/display.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package terminal
|
||||
|
||||
type EraseLineMode int
|
||||
|
||||
const (
|
||||
ERASE_LINE_END EraseLineMode = iota
|
||||
ERASE_LINE_START
|
||||
ERASE_LINE_ALL
|
||||
)
|
13
vendor/github.com/AlecAivazis/survey/v2/terminal/display_posix.go
generated
vendored
Normal file
13
vendor/github.com/AlecAivazis/survey/v2/terminal/display_posix.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func EraseLine(out FileWriter, mode EraseLineMode) error {
|
||||
_, err := fmt.Fprintf(out, "\x1b[%dK", mode)
|
||||
return err
|
||||
}
|
31
vendor/github.com/AlecAivazis/survey/v2/terminal/display_windows.go
generated
vendored
Normal file
31
vendor/github.com/AlecAivazis/survey/v2/terminal/display_windows.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func EraseLine(out FileWriter, mode EraseLineMode) error {
|
||||
handle := syscall.Handle(out.Fd())
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var w uint32
|
||||
var x Short
|
||||
cursor := csbi.cursorPosition
|
||||
switch mode {
|
||||
case ERASE_LINE_END:
|
||||
x = csbi.size.X
|
||||
case ERASE_LINE_START:
|
||||
x = 0
|
||||
case ERASE_LINE_ALL:
|
||||
cursor.X = 0
|
||||
x = csbi.size.X
|
||||
}
|
||||
|
||||
_, _, err := procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))
|
||||
return normalizeError(err)
|
||||
}
|
10
vendor/github.com/AlecAivazis/survey/v2/terminal/error.go
generated
vendored
Normal file
10
vendor/github.com/AlecAivazis/survey/v2/terminal/error.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
//lint:ignore ST1012 keeping old name for backwards compatibility
|
||||
InterruptErr = errors.New("interrupt")
|
||||
)
|
20
vendor/github.com/AlecAivazis/survey/v2/terminal/output.go
generated
vendored
Normal file
20
vendor/github.com/AlecAivazis/survey/v2/terminal/output.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// NewAnsiStdout returns special stdout, which converts escape sequences to Windows API calls
|
||||
// on Windows environment.
|
||||
func NewAnsiStdout(out FileWriter) io.Writer {
|
||||
return out
|
||||
}
|
||||
|
||||
// NewAnsiStderr returns special stderr, which converts escape sequences to Windows API calls
|
||||
// on Windows environment.
|
||||
func NewAnsiStderr(out FileWriter) io.Writer {
|
||||
return out
|
||||
}
|
253
vendor/github.com/AlecAivazis/survey/v2/terminal/output_windows.go
generated
vendored
Normal file
253
vendor/github.com/AlecAivazis/survey/v2/terminal/output_windows.go
generated
vendored
Normal file
@ -0,0 +1,253 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
const (
|
||||
foregroundBlue = 0x1
|
||||
foregroundGreen = 0x2
|
||||
foregroundRed = 0x4
|
||||
foregroundIntensity = 0x8
|
||||
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
|
||||
backgroundBlue = 0x10
|
||||
backgroundGreen = 0x20
|
||||
backgroundRed = 0x40
|
||||
backgroundIntensity = 0x80
|
||||
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
|
||||
)
|
||||
|
||||
type Writer struct {
|
||||
out FileWriter
|
||||
handle syscall.Handle
|
||||
orgAttr word
|
||||
}
|
||||
|
||||
func NewAnsiStdout(out FileWriter) io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
if !isatty.IsTerminal(out.Fd()) {
|
||||
return out
|
||||
}
|
||||
handle := syscall.Handle(out.Fd())
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
|
||||
}
|
||||
|
||||
func NewAnsiStderr(out FileWriter) io.Writer {
|
||||
var csbi consoleScreenBufferInfo
|
||||
if !isatty.IsTerminal(out.Fd()) {
|
||||
return out
|
||||
}
|
||||
handle := syscall.Handle(out.Fd())
|
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
|
||||
return &Writer{out: out, handle: handle, orgAttr: csbi.attributes}
|
||||
}
|
||||
|
||||
func (w *Writer) Write(data []byte) (n int, err error) {
|
||||
r := bytes.NewReader(data)
|
||||
|
||||
for {
|
||||
var ch rune
|
||||
var size int
|
||||
ch, size, err = r.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
n += size
|
||||
|
||||
switch ch {
|
||||
case '\x1b':
|
||||
size, err = w.handleEscape(r)
|
||||
n += size
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
_, err = fmt.Fprint(w.out, string(ch))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
|
||||
buf := make([]byte, 0, 10)
|
||||
buf = append(buf, "\x1b"...)
|
||||
|
||||
var ch rune
|
||||
var size int
|
||||
// Check '[' continues after \x1b
|
||||
ch, size, err = r.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
fmt.Fprint(w.out, string(buf))
|
||||
return
|
||||
}
|
||||
n += size
|
||||
if ch != '[' {
|
||||
fmt.Fprint(w.out, string(buf))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse escape code
|
||||
var code rune
|
||||
argBuf := make([]byte, 0, 10)
|
||||
for {
|
||||
ch, size, err = r.ReadRune()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
fmt.Fprint(w.out, string(buf))
|
||||
return
|
||||
}
|
||||
n += size
|
||||
if ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') {
|
||||
code = ch
|
||||
break
|
||||
}
|
||||
argBuf = append(argBuf, string(ch)...)
|
||||
}
|
||||
|
||||
err = w.applyEscapeCode(buf, string(argBuf), code)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) error {
|
||||
c := &Cursor{Out: w.out}
|
||||
|
||||
switch arg + string(code) {
|
||||
case "?25h":
|
||||
return c.Show()
|
||||
case "?25l":
|
||||
return c.Hide()
|
||||
}
|
||||
|
||||
if code >= 'A' && code <= 'G' {
|
||||
if n, err := strconv.Atoi(arg); err == nil {
|
||||
switch code {
|
||||
case 'A':
|
||||
return c.Up(n)
|
||||
case 'B':
|
||||
return c.Down(n)
|
||||
case 'C':
|
||||
return c.Forward(n)
|
||||
case 'D':
|
||||
return c.Back(n)
|
||||
case 'E':
|
||||
return c.NextLine(n)
|
||||
case 'F':
|
||||
return c.PreviousLine(n)
|
||||
case 'G':
|
||||
return c.HorizontalAbsolute(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch code {
|
||||
case 'm':
|
||||
return w.applySelectGraphicRendition(arg)
|
||||
default:
|
||||
buf = append(buf, string(code)...)
|
||||
_, err := fmt.Fprint(w.out, string(buf))
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Original implementation: https://github.com/mattn/go-colorable
|
||||
func (w *Writer) applySelectGraphicRendition(arg string) error {
|
||||
if arg == "" {
|
||||
_, _, err := procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr))
|
||||
return normalizeError(err)
|
||||
}
|
||||
|
||||
var csbi consoleScreenBufferInfo
|
||||
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
|
||||
return err
|
||||
}
|
||||
attr := csbi.attributes
|
||||
|
||||
for _, param := range strings.Split(arg, ";") {
|
||||
n, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case n == 0 || n == 100:
|
||||
attr = w.orgAttr
|
||||
case 1 <= n && n <= 5:
|
||||
attr |= foregroundIntensity
|
||||
case 30 <= n && n <= 37:
|
||||
attr = (attr & backgroundMask)
|
||||
if (n-30)&1 != 0 {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if (n-30)&2 != 0 {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if (n-30)&4 != 0 {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
case 40 <= n && n <= 47:
|
||||
attr = (attr & foregroundMask)
|
||||
if (n-40)&1 != 0 {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if (n-40)&2 != 0 {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if (n-40)&4 != 0 {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
case 90 <= n && n <= 97:
|
||||
attr = (attr & backgroundMask)
|
||||
attr |= foregroundIntensity
|
||||
if (n-90)&1 != 0 {
|
||||
attr |= foregroundRed
|
||||
}
|
||||
if (n-90)&2 != 0 {
|
||||
attr |= foregroundGreen
|
||||
}
|
||||
if (n-90)&4 != 0 {
|
||||
attr |= foregroundBlue
|
||||
}
|
||||
case 100 <= n && n <= 107:
|
||||
attr = (attr & foregroundMask)
|
||||
attr |= backgroundIntensity
|
||||
if (n-100)&1 != 0 {
|
||||
attr |= backgroundRed
|
||||
}
|
||||
if (n-100)&2 != 0 {
|
||||
attr |= backgroundGreen
|
||||
}
|
||||
if (n-100)&4 != 0 {
|
||||
attr |= backgroundBlue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err := procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
|
||||
return normalizeError(err)
|
||||
}
|
||||
|
||||
func normalizeError(err error) error {
|
||||
if syserr, ok := err.(syscall.Errno); ok && syserr == 0 {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
417
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader.go
generated
vendored
Normal file
417
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader.go
generated
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
)
|
||||
|
||||
type RuneReader struct {
|
||||
stdio Stdio
|
||||
state runeReaderState
|
||||
}
|
||||
|
||||
func NewRuneReader(stdio Stdio) *RuneReader {
|
||||
return &RuneReader{
|
||||
stdio: stdio,
|
||||
state: newRuneReaderState(stdio.In),
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RuneReader) printChar(char rune, mask rune) error {
|
||||
// if we don't need to mask the input
|
||||
if mask == 0 {
|
||||
// just print the character the user pressed
|
||||
_, err := fmt.Fprintf(rr.stdio.Out, "%c", char)
|
||||
return err
|
||||
}
|
||||
// otherwise print the mask we were given
|
||||
_, err := fmt.Fprintf(rr.stdio.Out, "%c", mask)
|
||||
return err
|
||||
}
|
||||
|
||||
type OnRuneFn func(rune, []rune) ([]rune, bool, error)
|
||||
|
||||
func (rr *RuneReader) ReadLine(mask rune, onRunes ...OnRuneFn) ([]rune, error) {
|
||||
return rr.ReadLineWithDefault(mask, []rune{}, onRunes...)
|
||||
}
|
||||
|
||||
func (rr *RuneReader) ReadLineWithDefault(mask rune, d []rune, onRunes ...OnRuneFn) ([]rune, error) {
|
||||
line := []rune{}
|
||||
// we only care about horizontal displacements from the origin so start counting at 0
|
||||
index := 0
|
||||
|
||||
cursor := &Cursor{
|
||||
In: rr.stdio.In,
|
||||
Out: rr.stdio.Out,
|
||||
}
|
||||
|
||||
onRune := func(r rune, line []rune) ([]rune, bool, error) {
|
||||
return line, false, nil
|
||||
}
|
||||
|
||||
// if the user pressed a key the caller was interested in capturing
|
||||
if len(onRunes) > 0 {
|
||||
onRune = onRunes[0]
|
||||
}
|
||||
|
||||
// we get the terminal width and height (if resized after this point the property might become invalid)
|
||||
terminalSize, _ := cursor.Size(rr.Buffer())
|
||||
// we set the current location of the cursor once
|
||||
cursorCurrent, _ := cursor.Location(rr.Buffer())
|
||||
|
||||
increment := func() {
|
||||
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||
cursorCurrent.X = COORDINATE_SYSTEM_BEGIN
|
||||
cursorCurrent.Y++
|
||||
} else {
|
||||
cursorCurrent.X++
|
||||
}
|
||||
}
|
||||
decrement := func() {
|
||||
if cursorCurrent.CursorIsAtLineBegin() {
|
||||
cursorCurrent.X = terminalSize.X
|
||||
cursorCurrent.Y--
|
||||
} else {
|
||||
cursorCurrent.X--
|
||||
}
|
||||
}
|
||||
|
||||
if len(d) > 0 {
|
||||
index = len(d)
|
||||
if _, err := fmt.Fprint(rr.stdio.Out, string(d)); err != nil {
|
||||
return d, err
|
||||
}
|
||||
line = d
|
||||
for range d {
|
||||
increment()
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
// wait for some input
|
||||
r, _, err := rr.ReadRune()
|
||||
if err != nil {
|
||||
return line, err
|
||||
}
|
||||
|
||||
if l, stop, err := onRune(r, line); stop || err != nil {
|
||||
return l, err
|
||||
}
|
||||
|
||||
// if the user pressed enter or some other newline/termination like ctrl+d
|
||||
if r == '\r' || r == '\n' || r == KeyEndTransmission {
|
||||
// delete what's printed out on the console screen (cleanup)
|
||||
for index > 0 {
|
||||
if cursorCurrent.CursorIsAtLineBegin() {
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
cursor.PreviousLine(1)
|
||||
cursor.Forward(int(terminalSize.X))
|
||||
} else {
|
||||
cursor.Back(1)
|
||||
}
|
||||
decrement()
|
||||
index--
|
||||
}
|
||||
// move the cursor the a new line
|
||||
cursor.MoveNextLine(cursorCurrent, terminalSize)
|
||||
|
||||
// we're done processing the input
|
||||
return line, nil
|
||||
}
|
||||
// if the user interrupts (ie with ctrl+c)
|
||||
if r == KeyInterrupt {
|
||||
// go to the beginning of the next line
|
||||
if _, err := fmt.Fprint(rr.stdio.Out, "\r\n"); err != nil {
|
||||
return line, err
|
||||
}
|
||||
|
||||
// we're done processing the input, and treat interrupt like an error
|
||||
return line, InterruptErr
|
||||
}
|
||||
|
||||
// allow for backspace/delete editing of inputs
|
||||
if r == KeyBackspace || r == KeyDelete {
|
||||
// and we're not at the beginning of the line
|
||||
if index > 0 && len(line) > 0 {
|
||||
// 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(cells)
|
||||
}
|
||||
|
||||
// clear the rest of the line
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
} 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:]...)
|
||||
|
||||
// save the current position of the cursor, as we have to move the cursor one back to erase the current symbol
|
||||
// and then move the cursor for each symbol in line[index-1:] to print it out, afterwards we want to restore
|
||||
// the cursor to its previous location.
|
||||
cursor.Save()
|
||||
|
||||
// clear the rest of the line
|
||||
cursor.Back(cells)
|
||||
|
||||
// print what comes after
|
||||
for _, char := range line[index-1:] {
|
||||
//Erase symbols which are left over from older print
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
// print characters to the new line appropriately
|
||||
if err := rr.printChar(char, mask); err != nil {
|
||||
return line, err
|
||||
}
|
||||
}
|
||||
// erase what's left over from last print
|
||||
if cursorCurrent.Y < terminalSize.Y {
|
||||
cursor.NextLine(1)
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
}
|
||||
// restore cursor
|
||||
cursor.Restore()
|
||||
if cursorCurrent.CursorIsAtLineBegin() {
|
||||
cursor.PreviousLine(1)
|
||||
cursor.Forward(int(terminalSize.X))
|
||||
} else {
|
||||
cursor.Back(cells)
|
||||
}
|
||||
}
|
||||
|
||||
// decrement the index
|
||||
index--
|
||||
decrement()
|
||||
} else {
|
||||
// otherwise the user pressed backspace while at the beginning of the line
|
||||
_ = soundBell(rr.stdio.Out)
|
||||
}
|
||||
|
||||
// we're done processing this key
|
||||
continue
|
||||
}
|
||||
|
||||
// if the left arrow is pressed
|
||||
if r == KeyArrowLeft {
|
||||
// if we have space to the left
|
||||
if index > 0 {
|
||||
//move the cursor to the prev line if necessary
|
||||
if cursorCurrent.CursorIsAtLineBegin() {
|
||||
cursor.PreviousLine(1)
|
||||
cursor.Forward(int(terminalSize.X))
|
||||
} else {
|
||||
cursor.Back(runeWidth(line[index-1]))
|
||||
}
|
||||
//decrement the index
|
||||
index--
|
||||
decrement()
|
||||
|
||||
} else {
|
||||
// otherwise we are at the beginning of where we started reading lines
|
||||
// sound the bell
|
||||
_ = soundBell(rr.stdio.Out)
|
||||
}
|
||||
|
||||
// we're done processing this key press
|
||||
continue
|
||||
}
|
||||
|
||||
// if the right arrow is pressed
|
||||
if r == KeyArrowRight {
|
||||
// if we have space to the right
|
||||
if index < len(line) {
|
||||
// move the cursor to the next line if necessary
|
||||
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||
cursor.NextLine(1)
|
||||
} else {
|
||||
cursor.Forward(runeWidth(line[index]))
|
||||
}
|
||||
index++
|
||||
increment()
|
||||
|
||||
} else {
|
||||
// otherwise we are at the end of the word and can't go past
|
||||
// sound the bell
|
||||
_ = soundBell(rr.stdio.Out)
|
||||
}
|
||||
|
||||
// we're done processing this key press
|
||||
continue
|
||||
}
|
||||
// the user pressed one of the special keys
|
||||
if r == SpecialKeyHome {
|
||||
for index > 0 {
|
||||
if cursorCurrent.CursorIsAtLineBegin() {
|
||||
cursor.PreviousLine(1)
|
||||
cursor.Forward(int(terminalSize.X))
|
||||
cursorCurrent.Y--
|
||||
cursorCurrent.X = terminalSize.X
|
||||
} else {
|
||||
cursor.Back(runeWidth(line[index-1]))
|
||||
cursorCurrent.X -= Short(runeWidth(line[index-1]))
|
||||
}
|
||||
index--
|
||||
}
|
||||
continue
|
||||
// user pressed end
|
||||
} else if r == SpecialKeyEnd {
|
||||
for index != len(line) {
|
||||
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||
cursor.NextLine(1)
|
||||
cursorCurrent.Y++
|
||||
cursorCurrent.X = COORDINATE_SYSTEM_BEGIN
|
||||
} else {
|
||||
cursor.Forward(runeWidth(line[index]))
|
||||
cursorCurrent.X += Short(runeWidth(line[index]))
|
||||
}
|
||||
index++
|
||||
}
|
||||
continue
|
||||
// user pressed forward delete key
|
||||
} else if r == SpecialKeyDelete {
|
||||
// if index at the end of the line nothing to delete
|
||||
if index != len(line) {
|
||||
// save the current position of the cursor, as we have to erase the current symbol
|
||||
// and then move the cursor for each symbol in line[index:] to print it out, afterwards we want to restore
|
||||
// the cursor to its previous location.
|
||||
cursor.Save()
|
||||
// remove the symbol after the cursor
|
||||
line = append(line[:index], line[index+1:]...)
|
||||
// print the updated line
|
||||
for _, char := range line[index:] {
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
// print out the character
|
||||
if err := rr.printChar(char, mask); err != nil {
|
||||
return line, err
|
||||
}
|
||||
}
|
||||
// erase what's left on last line
|
||||
if cursorCurrent.Y < terminalSize.Y {
|
||||
cursor.NextLine(1)
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
}
|
||||
// restore cursor
|
||||
cursor.Restore()
|
||||
if len(line) == 0 || index == len(line) {
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// if the letter is another escape sequence
|
||||
if unicode.IsControl(r) || r == IgnoreKey {
|
||||
// ignore it
|
||||
continue
|
||||
}
|
||||
|
||||
// the user pressed a regular key
|
||||
|
||||
// if we are at the end of the line
|
||||
if index == len(line) {
|
||||
// just append the character at the end of the line
|
||||
line = append(line, r)
|
||||
// save the location of the cursor
|
||||
index++
|
||||
increment()
|
||||
// print out the character
|
||||
if err := rr.printChar(r, mask); err != nil {
|
||||
return line, err
|
||||
}
|
||||
} else {
|
||||
// we are in the middle of the word so we need to insert the character the user pressed
|
||||
line = append(line[:index], append([]rune{r}, line[index:]...)...)
|
||||
// save the current position of the cursor, as we have to move the cursor back to erase the current symbol
|
||||
// and then move for each symbol in line[index:] to print it out, afterwards we want to restore
|
||||
// cursor's location to its previous one.
|
||||
cursor.Save()
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
// remove the symbol after the cursor
|
||||
// print the updated line
|
||||
for _, char := range line[index:] {
|
||||
EraseLine(rr.stdio.Out, ERASE_LINE_END)
|
||||
// print out the character
|
||||
if err := rr.printChar(char, mask); err != nil {
|
||||
return line, err
|
||||
}
|
||||
increment()
|
||||
}
|
||||
// if we are at the last line, we want to visually insert a new line and append to it.
|
||||
if cursorCurrent.CursorIsAtLineEnd(terminalSize) && cursorCurrent.Y == terminalSize.Y {
|
||||
// add a new line to the terminal
|
||||
if _, err := fmt.Fprintln(rr.stdio.Out); err != nil {
|
||||
return line, err
|
||||
}
|
||||
// restore the position of the cursor horizontally
|
||||
cursor.Restore()
|
||||
// restore the position of the cursor vertically
|
||||
cursor.PreviousLine(1)
|
||||
} else {
|
||||
// restore cursor
|
||||
cursor.Restore()
|
||||
}
|
||||
// check if cursor needs to move to next line
|
||||
cursorCurrent, _ = cursor.Location(rr.Buffer())
|
||||
if cursorCurrent.CursorIsAtLineEnd(terminalSize) {
|
||||
cursor.NextLine(1)
|
||||
} else {
|
||||
cursor.Forward(runeWidth(r))
|
||||
}
|
||||
// increment the index
|
||||
index++
|
||||
increment()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// runeWidth returns the number of columns spanned by a rune when printed to the terminal
|
||||
func runeWidth(r rune) int {
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
}
|
||||
|
||||
if !unicode.IsPrint(r) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// isAnsiMarker returns if a rune denotes the start of an ANSI sequence
|
||||
func isAnsiMarker(r rune) bool {
|
||||
return r == '\x1B'
|
||||
}
|
||||
|
||||
// isAnsiTerminator returns if a rune denotes the end of an ANSI sequence
|
||||
func isAnsiTerminator(r rune) bool {
|
||||
return (r >= 0x40 && r <= 0x5a) || (r == 0x5e) || (r >= 0x60 && r <= 0x7e)
|
||||
}
|
||||
|
||||
// StringWidth returns the visible width of a string when printed to the terminal
|
||||
func StringWidth(str string) int {
|
||||
w := 0
|
||||
ansi := false
|
||||
|
||||
for _, r := range str {
|
||||
// increase width only when outside of ANSI escape sequences
|
||||
if ansi || isAnsiMarker(r) {
|
||||
ansi = !isAnsiTerminator(r)
|
||||
} else {
|
||||
w += runeWidth(r)
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_bsd.go
generated
vendored
Normal file
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_bsd.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// copied from: https://github.com/golang/crypto/blob/master/ssh/terminal/util_bsd.go
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build darwin || dragonfly || freebsd || netbsd || openbsd
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package terminal
|
||||
|
||||
import "syscall"
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
const ioctlWriteTermios = syscall.TIOCSETA
|
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_linux.go
generated
vendored
Normal file
14
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_linux.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
// copied from https://github.com/golang/crypto/blob/master/ssh/terminal/util_linux.go
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
//go:build linux && !ppc64le
|
||||
// +build linux,!ppc64le
|
||||
|
||||
package terminal
|
||||
|
||||
// These constants are declared here, rather than importing
|
||||
// them from the syscall package as some syscall packages, even
|
||||
// on linux, for example gccgo, do not declare them.
|
||||
const ioctlReadTermios = 0x5401 // syscall.TCGETS
|
||||
const ioctlWriteTermios = 0x5402 // syscall.TCSETS
|
132
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_posix.go
generated
vendored
Normal file
132
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_posix.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
// The terminal mode manipulation code is derived heavily from:
|
||||
// https://github.com/golang/crypto/blob/master/ssh/terminal/util.go:
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
normalKeypad = '['
|
||||
applicationKeypad = 'O'
|
||||
)
|
||||
|
||||
type runeReaderState struct {
|
||||
term syscall.Termios
|
||||
reader *bufio.Reader
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func newRuneReaderState(input FileReader) runeReaderState {
|
||||
buf := new(bytes.Buffer)
|
||||
return runeReaderState{
|
||||
reader: bufio.NewReader(&BufferedReader{
|
||||
In: input,
|
||||
Buffer: buf,
|
||||
}),
|
||||
buf: buf,
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RuneReader) Buffer() *bytes.Buffer {
|
||||
return rr.state.buf
|
||||
}
|
||||
|
||||
// For reading runes we just want to disable echo.
|
||||
func (rr *RuneReader) SetTermMode() error {
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
newState := rr.state.term
|
||||
newState.Lflag &^= syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG
|
||||
// Because we are clearing canonical mode, we need to ensure VMIN & VTIME are
|
||||
// set to the values we expect. This combination puts things in standard
|
||||
// "blocking read" mode (see termios(3)).
|
||||
newState.Cc[syscall.VMIN] = 1
|
||||
newState.Cc[syscall.VTIME] = 0
|
||||
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) RestoreTermMode() error {
|
||||
if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(rr.stdio.In.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&rr.state.term)), 0, 0, 0); err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRune Parse escape sequences such as ESC [ A for arrow keys.
|
||||
// See https://vt100.net/docs/vt102-ug/appendixc.html
|
||||
func (rr *RuneReader) ReadRune() (rune, int, error) {
|
||||
r, size, err := rr.state.reader.ReadRune()
|
||||
if err != nil {
|
||||
return r, size, err
|
||||
}
|
||||
|
||||
if r != KeyEscape {
|
||||
return r, size, err
|
||||
}
|
||||
|
||||
if rr.state.reader.Buffered() == 0 {
|
||||
// no more characters so must be `Esc` key
|
||||
return KeyEscape, 1, nil
|
||||
}
|
||||
|
||||
r, size, err = rr.state.reader.ReadRune()
|
||||
if err != nil {
|
||||
return r, size, err
|
||||
}
|
||||
|
||||
// ESC O ... or ESC [ ...?
|
||||
if r != normalKeypad && r != applicationKeypad {
|
||||
return r, size, fmt.Errorf("unexpected escape sequence from terminal: %q", []rune{KeyEscape, r})
|
||||
}
|
||||
|
||||
keypad := r
|
||||
|
||||
r, size, err = rr.state.reader.ReadRune()
|
||||
if err != nil {
|
||||
return r, size, err
|
||||
}
|
||||
|
||||
switch r {
|
||||
case 'A': // ESC [ A or ESC O A
|
||||
return KeyArrowUp, 1, nil
|
||||
case 'B': // ESC [ B or ESC O B
|
||||
return KeyArrowDown, 1, nil
|
||||
case 'C': // ESC [ C or ESC O C
|
||||
return KeyArrowRight, 1, nil
|
||||
case 'D': // ESC [ D or ESC O D
|
||||
return KeyArrowLeft, 1, nil
|
||||
case 'F': // ESC [ F or ESC O F
|
||||
return SpecialKeyEnd, 1, nil
|
||||
case 'H': // ESC [ H or ESC O H
|
||||
return SpecialKeyHome, 1, nil
|
||||
case '3': // ESC [ 3
|
||||
if keypad == normalKeypad {
|
||||
// discard the following '~' key from buffer
|
||||
_, _ = rr.state.reader.Discard(1)
|
||||
return SpecialKeyDelete, 1, nil
|
||||
}
|
||||
}
|
||||
|
||||
// discard the following '~' key from buffer
|
||||
_, _ = rr.state.reader.Discard(1)
|
||||
return IgnoreKey, 1, nil
|
||||
}
|
8
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_ppc64le.go
generated
vendored
Normal file
8
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_ppc64le.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
//go:build ppc64le && linux
|
||||
// +build ppc64le,linux
|
||||
|
||||
package terminal
|
||||
|
||||
// Used syscall numbers from https://github.com/golang/go/blob/master/src/syscall/ztypes_linux_ppc64le.go
|
||||
const ioctlReadTermios = 0x402c7413 // syscall.TCGETS
|
||||
const ioctlWriteTermios = 0x802c7414 // syscall.TCSETS
|
142
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_windows.go
generated
vendored
Normal file
142
vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_windows.go
generated
vendored
Normal file
@ -0,0 +1,142 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
dll = syscall.NewLazyDLL("kernel32.dll")
|
||||
setConsoleMode = dll.NewProc("SetConsoleMode")
|
||||
getConsoleMode = dll.NewProc("GetConsoleMode")
|
||||
readConsoleInput = dll.NewProc("ReadConsoleInputW")
|
||||
)
|
||||
|
||||
const (
|
||||
EVENT_KEY = 0x0001
|
||||
|
||||
// key codes for arrow keys
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
|
||||
VK_DELETE = 0x2E
|
||||
VK_END = 0x23
|
||||
VK_HOME = 0x24
|
||||
VK_LEFT = 0x25
|
||||
VK_UP = 0x26
|
||||
VK_RIGHT = 0x27
|
||||
VK_DOWN = 0x28
|
||||
|
||||
RIGHT_CTRL_PRESSED = 0x0004
|
||||
LEFT_CTRL_PRESSED = 0x0008
|
||||
|
||||
ENABLE_ECHO_INPUT uint32 = 0x0004
|
||||
ENABLE_LINE_INPUT uint32 = 0x0002
|
||||
ENABLE_PROCESSED_INPUT uint32 = 0x0001
|
||||
)
|
||||
|
||||
type inputRecord struct {
|
||||
eventType uint16
|
||||
padding uint16
|
||||
event [16]byte
|
||||
}
|
||||
|
||||
type keyEventRecord struct {
|
||||
bKeyDown int32
|
||||
wRepeatCount uint16
|
||||
wVirtualKeyCode uint16
|
||||
wVirtualScanCode uint16
|
||||
unicodeChar uint16
|
||||
wdControlKeyState uint32
|
||||
}
|
||||
|
||||
type runeReaderState struct {
|
||||
term uint32
|
||||
}
|
||||
|
||||
func newRuneReaderState(input FileReader) runeReaderState {
|
||||
return runeReaderState{}
|
||||
}
|
||||
|
||||
func (rr *RuneReader) Buffer() *bytes.Buffer {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) SetTermMode() error {
|
||||
r, _, err := getConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(unsafe.Pointer(&rr.state.term)))
|
||||
// windows return 0 on error
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
newState := rr.state.term
|
||||
newState &^= ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT
|
||||
r, _, err = setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(newState))
|
||||
// windows return 0 on error
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) RestoreTermMode() error {
|
||||
r, _, err := setConsoleMode.Call(uintptr(rr.stdio.In.Fd()), uintptr(rr.state.term))
|
||||
// windows return 0 on error
|
||||
if r == 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rr *RuneReader) ReadRune() (rune, int, error) {
|
||||
ir := &inputRecord{}
|
||||
bytesRead := 0
|
||||
for {
|
||||
rv, _, e := readConsoleInput.Call(rr.stdio.In.Fd(), uintptr(unsafe.Pointer(ir)), 1, uintptr(unsafe.Pointer(&bytesRead)))
|
||||
// windows returns non-zero to indicate success
|
||||
if rv == 0 && e != nil {
|
||||
return 0, 0, e
|
||||
}
|
||||
|
||||
if ir.eventType != EVENT_KEY {
|
||||
continue
|
||||
}
|
||||
|
||||
// the event data is really a c struct union, so here we have to do an usafe
|
||||
// cast to put the data into the keyEventRecord (since we have already verified
|
||||
// above that this event does correspond to a key event
|
||||
key := (*keyEventRecord)(unsafe.Pointer(&ir.event[0]))
|
||||
// we only care about key down events
|
||||
if key.bKeyDown == 0 {
|
||||
continue
|
||||
}
|
||||
if key.wdControlKeyState&(LEFT_CTRL_PRESSED|RIGHT_CTRL_PRESSED) != 0 && key.unicodeChar == 'C' {
|
||||
return KeyInterrupt, bytesRead, nil
|
||||
}
|
||||
// not a normal character so look up the input sequence from the
|
||||
// virtual key code mappings (VK_*)
|
||||
if key.unicodeChar == 0 {
|
||||
switch key.wVirtualKeyCode {
|
||||
case VK_DOWN:
|
||||
return KeyArrowDown, bytesRead, nil
|
||||
case VK_LEFT:
|
||||
return KeyArrowLeft, bytesRead, nil
|
||||
case VK_RIGHT:
|
||||
return KeyArrowRight, bytesRead, nil
|
||||
case VK_UP:
|
||||
return KeyArrowUp, bytesRead, nil
|
||||
case VK_DELETE:
|
||||
return SpecialKeyDelete, bytesRead, nil
|
||||
case VK_HOME:
|
||||
return SpecialKeyHome, bytesRead, nil
|
||||
case VK_END:
|
||||
return SpecialKeyEnd, bytesRead, nil
|
||||
default:
|
||||
// not a virtual key that we care about so just continue on to
|
||||
// the next input key
|
||||
continue
|
||||
}
|
||||
}
|
||||
r := rune(key.unicodeChar)
|
||||
return r, bytesRead, nil
|
||||
}
|
||||
}
|
32
vendor/github.com/AlecAivazis/survey/v2/terminal/sequences.go
generated
vendored
Normal file
32
vendor/github.com/AlecAivazis/survey/v2/terminal/sequences.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
KeyArrowLeft = '\x02'
|
||||
KeyArrowRight = '\x06'
|
||||
KeyArrowUp = '\x10'
|
||||
KeyArrowDown = '\x0e'
|
||||
KeySpace = ' '
|
||||
KeyEnter = '\r'
|
||||
KeyBackspace = '\b'
|
||||
KeyDelete = '\x7f'
|
||||
KeyInterrupt = '\x03'
|
||||
KeyEndTransmission = '\x04'
|
||||
KeyEscape = '\x1b'
|
||||
KeyDeleteWord = '\x17' // Ctrl+W
|
||||
KeyDeleteLine = '\x18' // Ctrl+X
|
||||
SpecialKeyHome = '\x01'
|
||||
SpecialKeyEnd = '\x11'
|
||||
SpecialKeyDelete = '\x12'
|
||||
IgnoreKey = '\000'
|
||||
KeyTab = '\t'
|
||||
)
|
||||
|
||||
func soundBell(out io.Writer) error {
|
||||
_, err := fmt.Fprint(out, "\a")
|
||||
return err
|
||||
}
|
24
vendor/github.com/AlecAivazis/survey/v2/terminal/stdio.go
generated
vendored
Normal file
24
vendor/github.com/AlecAivazis/survey/v2/terminal/stdio.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Stdio is the standard input/output the terminal reads/writes with.
|
||||
type Stdio struct {
|
||||
In FileReader
|
||||
Out FileWriter
|
||||
Err io.Writer
|
||||
}
|
||||
|
||||
// FileWriter provides a minimal interface for Stdin.
|
||||
type FileWriter interface {
|
||||
io.Writer
|
||||
Fd() uintptr
|
||||
}
|
||||
|
||||
// FileReader provides a minimal interface for Stdout.
|
||||
type FileReader interface {
|
||||
io.Reader
|
||||
Fd() uintptr
|
||||
}
|
39
vendor/github.com/AlecAivazis/survey/v2/terminal/syscall_windows.go
generated
vendored
Normal file
39
vendor/github.com/AlecAivazis/survey/v2/terminal/syscall_windows.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
|
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
|
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
|
||||
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
|
||||
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
|
||||
)
|
||||
|
||||
type wchar uint16
|
||||
type dword uint32
|
||||
type word uint16
|
||||
|
||||
type smallRect struct {
|
||||
left Short
|
||||
top Short
|
||||
right Short
|
||||
bottom Short
|
||||
}
|
||||
|
||||
type consoleScreenBufferInfo struct {
|
||||
size Coord
|
||||
cursorPosition Coord
|
||||
attributes word
|
||||
window smallRect
|
||||
maximumWindowSize Coord
|
||||
}
|
||||
|
||||
type consoleCursorInfo struct {
|
||||
size dword
|
||||
visible int32
|
||||
}
|
8
vendor/github.com/AlecAivazis/survey/v2/terminal/terminal.go
generated
vendored
Normal file
8
vendor/github.com/AlecAivazis/survey/v2/terminal/terminal.go
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
package terminal
|
||||
|
||||
type Short int16
|
||||
|
||||
type Coord struct {
|
||||
X Short
|
||||
Y Short
|
||||
}
|
82
vendor/github.com/AlecAivazis/survey/v2/transform.go
generated
vendored
Normal file
82
vendor/github.com/AlecAivazis/survey/v2/transform.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
// TransformString returns a `Transformer` based on the "f"
|
||||
// function which accepts a string representation of the answer
|
||||
// and returns a new one, transformed, answer.
|
||||
// Take for example the functions inside the std `strings` package,
|
||||
// they can be converted to a compatible `Transformer` by using this function,
|
||||
// i.e: `TransformString(strings.Title)`, `TransformString(strings.ToUpper)`.
|
||||
//
|
||||
// Note that `TransformString` is just a helper, `Transformer` can be used
|
||||
// to transform any type of answer.
|
||||
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 zero value of string.
|
||||
// The original answer will be not affected,
|
||||
// see survey.go#L125.
|
||||
// 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#L338 for more.
|
||||
// Make sure that the the answer's value was a typeof string.
|
||||
s, ok := ans.(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return f(s)
|
||||
}
|
||||
}
|
||||
|
||||
// ToLower is a `Transformer`.
|
||||
// It receives an answer value
|
||||
// and returns a copy of the "ans"
|
||||
// with all Unicode letters mapped to their lower case.
|
||||
//
|
||||
// Note that if "ans" is not a string then it will
|
||||
// return a nil value, meaning that the above answer
|
||||
// will not be affected by this call at all.
|
||||
func ToLower(ans interface{}) interface{} {
|
||||
transformer := TransformString(strings.ToLower)
|
||||
return transformer(ans)
|
||||
}
|
||||
|
||||
// Title is a `Transformer`.
|
||||
// It receives an answer value
|
||||
// and returns a copy of the "ans"
|
||||
// with all Unicode letters that begin words
|
||||
// mapped to their title case.
|
||||
//
|
||||
// Note that if "ans" is not a string then it will
|
||||
// return a nil value, meaning that the above answer
|
||||
// will not be affected by this call at all.
|
||||
func Title(ans interface{}) interface{} {
|
||||
transformer := TransformString(cases.Title(language.English).String)
|
||||
return transformer(ans)
|
||||
}
|
||||
|
||||
// ComposeTransformers is a variadic function used to create one transformer from many.
|
||||
func ComposeTransformers(transformers ...Transformer) Transformer {
|
||||
// return a transformer that calls each one sequentially
|
||||
return func(ans interface{}) interface{} {
|
||||
// execute each transformer
|
||||
for _, t := range transformers {
|
||||
ans = t(ans)
|
||||
}
|
||||
return ans
|
||||
}
|
||||
}
|
128
vendor/github.com/AlecAivazis/survey/v2/validate.go
generated
vendored
Normal file
128
vendor/github.com/AlecAivazis/survey/v2/validate.go
generated
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
package survey
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2/core"
|
||||
)
|
||||
|
||||
// Required does not allow an empty value
|
||||
func Required(val interface{}) error {
|
||||
// the reflect value of the result
|
||||
value := reflect.ValueOf(val)
|
||||
|
||||
// if the value passed in is the zero value of the appropriate type
|
||||
if isZero(value) && value.Kind() != reflect.Bool {
|
||||
//lint:ignore ST1005 this error message should render as capitalized
|
||||
return errors.New("Value is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxLength requires that the string is no longer than the specified value
|
||||
func MaxLength(length int) Validator {
|
||||
// return a validator that checks the length of the string
|
||||
return func(val interface{}) error {
|
||||
if str, ok := val.(string); ok {
|
||||
// if the string is longer than the given value
|
||||
if len([]rune(str)) > length {
|
||||
// yell loudly
|
||||
return fmt.Errorf("value is too long. Max length is %v", length)
|
||||
}
|
||||
} else {
|
||||
// otherwise we cannot convert the value into a string and cannot enforce length
|
||||
return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name())
|
||||
}
|
||||
|
||||
// the input is fine
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MinLength requires that the string is longer or equal in length to the specified value
|
||||
func MinLength(length int) Validator {
|
||||
// return a validator that checks the length of the string
|
||||
return func(val interface{}) error {
|
||||
if str, ok := val.(string); ok {
|
||||
// if the string is shorter than the given value
|
||||
if len([]rune(str)) < length {
|
||||
// yell loudly
|
||||
return fmt.Errorf("value is too short. Min length is %v", length)
|
||||
}
|
||||
} else {
|
||||
// otherwise we cannot convert the value into a string and cannot enforce length
|
||||
return fmt.Errorf("cannot enforce length on response of type %v", reflect.TypeOf(val).Name())
|
||||
}
|
||||
|
||||
// the input is fine
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MaxItems requires that the list is no longer than the specified value
|
||||
func MaxItems(numberItems int) Validator {
|
||||
// return a validator that checks the length of the list
|
||||
return func(val interface{}) error {
|
||||
if list, ok := val.([]core.OptionAnswer); ok {
|
||||
// if the list is longer than the given value
|
||||
if len(list) > numberItems {
|
||||
// yell loudly
|
||||
return fmt.Errorf("value is too long. Max items is %v", numberItems)
|
||||
}
|
||||
} else {
|
||||
// otherwise we cannot convert the value into a list of answer and cannot enforce length
|
||||
return fmt.Errorf("cannot impose the length on something other than a list of answers")
|
||||
}
|
||||
// the input is fine
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// MinItems requires that the list is longer or equal in length to the specified value
|
||||
func MinItems(numberItems int) Validator {
|
||||
// return a validator that checks the length of the list
|
||||
return func(val interface{}) error {
|
||||
if list, ok := val.([]core.OptionAnswer); ok {
|
||||
// if the list is shorter than the given value
|
||||
if len(list) < numberItems {
|
||||
// yell loudly
|
||||
return fmt.Errorf("value is too short. Min items is %v", numberItems)
|
||||
}
|
||||
} else {
|
||||
// otherwise we cannot convert the value into a list of answer and cannot enforce length
|
||||
return fmt.Errorf("cannot impose the length on something other than a list of answers")
|
||||
}
|
||||
// the input is fine
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ComposeValidators is a variadic function used to create one validator from many.
|
||||
func ComposeValidators(validators ...Validator) Validator {
|
||||
// return a validator that calls each one sequentially
|
||||
return func(val interface{}) error {
|
||||
// execute each validator
|
||||
for _, validator := range validators {
|
||||
// if the answer's value is not valid
|
||||
if err := validator(val); err != nil {
|
||||
// return the error
|
||||
return err
|
||||
}
|
||||
}
|
||||
// we passed all validators, the answer is valid
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// isZero returns true if the passed value is the zero object
|
||||
func isZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
return v.Len() == 0
|
||||
}
|
||||
|
||||
// compare the types directly with more general coverage
|
||||
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
|
||||
}
|
Reference in New Issue
Block a user