mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-21 21:33:22 +01:00
chore: go mod vendor
This commit is contained in:
@@ -9,6 +9,7 @@ FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
||||
COPY --from=xx / /
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOFLAGS="-mod=vendor"
|
||||
RUN apk add --no-cache file git
|
||||
WORKDIR /src
|
||||
|
||||
|
||||
10
Makefile
Normal file
10
Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
.PHONY: all
|
||||
all:
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t diun-output.XXXXXXXXXX))
|
||||
docker buildx bake --set "*.output=type=local,dest=$($@_TMP_OUT)" vendor
|
||||
rm -rf ./vendor
|
||||
cp -R "$($@_TMP_OUT)"/* ./
|
||||
rm -rf "$($@_TMP_OUT)"/*
|
||||
@@ -5,38 +5,43 @@ ARG ALPINE_VERSION="3.18"
|
||||
ARG GOMOD_OUTDATED_VERSION="v0.8.0"
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS base
|
||||
RUN apk add --no-cache git linux-headers musl-dev
|
||||
ENV GOFLAGS="-mod=vendor"
|
||||
RUN apk add --no-cache git linux-headers musl-dev rsync
|
||||
WORKDIR /src
|
||||
|
||||
FROM base AS vendored
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=type=cache,target=/go/pkg/mod <<EOT
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs \
|
||||
--mount=target=/go/pkg/mod,type=cache <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
go mod tidy
|
||||
go mod download
|
||||
go mod vendor
|
||||
mkdir /out
|
||||
cp go.mod go.sum /out
|
||||
cp -r go.mod go.sum vendor /out
|
||||
EOT
|
||||
|
||||
FROM scratch AS update
|
||||
COPY --from=vendored /out /
|
||||
|
||||
FROM vendored AS validate
|
||||
RUN --mount=type=bind,target=.,rw <<EOT
|
||||
RUN --mount=target=/context \
|
||||
--mount=target=.,type=tmpfs <<EOT
|
||||
set -e
|
||||
rsync -a /context/. .
|
||||
git add -A
|
||||
rm -rf vendor
|
||||
cp -rf /out/* .
|
||||
diff=$(git status --porcelain -- go.mod go.sum)
|
||||
if [ -n "$diff" ]; then
|
||||
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "docker buildx bake vendor"'
|
||||
echo "$diff"
|
||||
if [ -n "$(git status --porcelain -- go.mod go.sum vendor)" ]; then
|
||||
echo >&2 'ERROR: Vendor result differs. Please vendor your package with "make vendor"'
|
||||
git status --porcelain -- go.mod go.sum vendor
|
||||
exit 1
|
||||
fi
|
||||
EOT
|
||||
|
||||
FROM psampaz/go-mod-outdated:${GOMOD_OUTDATED_VERSION} AS go-mod-outdated
|
||||
FROM base AS outdated
|
||||
RUN --mount=type=bind,target=. \
|
||||
RUN --mount=type=bind,target=.,rw \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=from=go-mod-outdated,source=/home/go-mod-outdated,target=/usr/bin/go-mod-outdated \
|
||||
go list -mod=readonly -u -m -json all | go-mod-outdated -update -direct
|
||||
go list -mod=mod -u -m -json all | go-mod-outdated -update -direct
|
||||
|
||||
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())
|
||||
}
|
||||
2
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
2
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/toml.test
|
||||
/toml-test
|
||||
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
21
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 TOML authors
|
||||
|
||||
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.
|
||||
120
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
120
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
||||
reflection interface similar to Go's standard library `json` and `xml` packages.
|
||||
|
||||
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
|
||||
|
||||
Documentation: https://godocs.io/github.com/BurntSushi/toml
|
||||
|
||||
See the [releases page](https://github.com/BurntSushi/toml/releases) for a
|
||||
changelog; this information is also in the git tag annotations (e.g. `git show
|
||||
v0.4.0`).
|
||||
|
||||
This library requires Go 1.13 or newer; add it to your go.mod with:
|
||||
|
||||
% go get github.com/BurntSushi/toml@latest
|
||||
|
||||
It also comes with a TOML validator CLI tool:
|
||||
|
||||
% go install github.com/BurntSushi/toml/cmd/tomlv@latest
|
||||
% tomlv some-toml-file.toml
|
||||
|
||||
### Examples
|
||||
For the simplest example, consider some TOML file as just a list of keys and
|
||||
values:
|
||||
|
||||
```toml
|
||||
Age = 25
|
||||
Cats = [ "Cauchy", "Plato" ]
|
||||
Pi = 3.14
|
||||
Perfection = [ 6, 28, 496, 8128 ]
|
||||
DOB = 1987-07-05T05:45:00Z
|
||||
```
|
||||
|
||||
Which can be decoded with:
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Age int
|
||||
Cats []string
|
||||
Pi float64
|
||||
Perfection []int
|
||||
DOB time.Time
|
||||
}
|
||||
|
||||
var conf Config
|
||||
_, err := toml.Decode(tomlData, &conf)
|
||||
```
|
||||
|
||||
You can also use struct tags if your struct field name doesn't map to a TOML key
|
||||
value directly:
|
||||
|
||||
```toml
|
||||
some_key_NAME = "wat"
|
||||
```
|
||||
|
||||
```go
|
||||
type TOML struct {
|
||||
ObscureKey string `toml:"some_key_NAME"`
|
||||
}
|
||||
```
|
||||
|
||||
Beware that like other decoders **only exported fields** are considered when
|
||||
encoding and decoding; private fields are silently ignored.
|
||||
|
||||
### Using the `Marshaler` and `encoding.TextUnmarshaler` interfaces
|
||||
Here's an example that automatically parses values in a `mail.Address`:
|
||||
|
||||
```toml
|
||||
contacts = [
|
||||
"Donald Duck <donald@duckburg.com>",
|
||||
"Scrooge McDuck <scrooge@duckburg.com>",
|
||||
]
|
||||
```
|
||||
|
||||
Can be decoded with:
|
||||
|
||||
```go
|
||||
// Create address type which satisfies the encoding.TextUnmarshaler interface.
|
||||
type address struct {
|
||||
*mail.Address
|
||||
}
|
||||
|
||||
func (a *address) UnmarshalText(text []byte) error {
|
||||
var err error
|
||||
a.Address, err = mail.ParseAddress(string(text))
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode it.
|
||||
func decode() {
|
||||
blob := `
|
||||
contacts = [
|
||||
"Donald Duck <donald@duckburg.com>",
|
||||
"Scrooge McDuck <scrooge@duckburg.com>",
|
||||
]
|
||||
`
|
||||
|
||||
var contacts struct {
|
||||
Contacts []address
|
||||
}
|
||||
|
||||
_, err := toml.Decode(blob, &contacts)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for _, c := range contacts.Contacts {
|
||||
fmt.Printf("%#v\n", c.Address)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// &mail.Address{Name:"Donald Duck", Address:"donald@duckburg.com"}
|
||||
// &mail.Address{Name:"Scrooge McDuck", Address:"scrooge@duckburg.com"}
|
||||
}
|
||||
```
|
||||
|
||||
To target TOML specifically you can implement `UnmarshalTOML` TOML interface in
|
||||
a similar way.
|
||||
|
||||
### More complex usage
|
||||
See the [`_example/`](/_example) directory for a more complex example.
|
||||
602
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
602
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
Normal file
@@ -0,0 +1,602 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
||||
// TOML description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(interface{}) error
|
||||
}
|
||||
|
||||
// Unmarshal decodes the contents of data in TOML format into a pointer v.
|
||||
//
|
||||
// See [Decoder] for a description of the decoding process.
|
||||
func Unmarshal(data []byte, v interface{}) error {
|
||||
_, err := NewDecoder(bytes.NewReader(data)).Decode(v)
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode the TOML data in to the pointer v.
|
||||
//
|
||||
// See [Decoder] for a description of the decoding process.
|
||||
func Decode(data string, v interface{}) (MetaData, error) {
|
||||
return NewDecoder(strings.NewReader(data)).Decode(v)
|
||||
}
|
||||
|
||||
// DecodeFile reads the contents of a file and decodes it with [Decode].
|
||||
func DecodeFile(path string, v interface{}) (MetaData, error) {
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
defer fp.Close()
|
||||
return NewDecoder(fp).Decode(v)
|
||||
}
|
||||
|
||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
||||
//
|
||||
// This type can be used for any value, which will cause decoding to be delayed.
|
||||
// You can use [PrimitiveDecode] to "manually" decode these values.
|
||||
//
|
||||
// NOTE: The underlying representation of a `Primitive` value is subject to
|
||||
// change. Do not rely on it.
|
||||
//
|
||||
// NOTE: Primitive values are still parsed, so using them will only avoid the
|
||||
// overhead of reflection. They can be useful when you don't know the exact type
|
||||
// of TOML data until runtime.
|
||||
type Primitive struct {
|
||||
undecoded interface{}
|
||||
context Key
|
||||
}
|
||||
|
||||
// The significand precision for float32 and float64 is 24 and 53 bits; this is
|
||||
// the range a natural number can be stored in a float without loss of data.
|
||||
const (
|
||||
maxSafeFloat32Int = 16777215 // 2^24-1
|
||||
maxSafeFloat64Int = int64(9007199254740991) // 2^53-1
|
||||
)
|
||||
|
||||
// Decoder decodes TOML data.
|
||||
//
|
||||
// TOML tables correspond to Go structs or maps; they can be used
|
||||
// interchangeably, but structs offer better type safety.
|
||||
//
|
||||
// TOML table arrays correspond to either a slice of structs or a slice of maps.
|
||||
//
|
||||
// TOML datetimes correspond to [time.Time]. Local datetimes are parsed in the
|
||||
// local timezone.
|
||||
//
|
||||
// [time.Duration] types are treated as nanoseconds if the TOML value is an
|
||||
// integer, or they're parsed with time.ParseDuration() if they're strings.
|
||||
//
|
||||
// All other TOML types (float, string, int, bool and array) correspond to the
|
||||
// obvious Go types.
|
||||
//
|
||||
// An exception to the above rules is if a type implements the TextUnmarshaler
|
||||
// interface, in which case any primitive TOML value (floats, strings, integers,
|
||||
// booleans, datetimes) will be converted to a []byte and given to the value's
|
||||
// UnmarshalText method. See the Unmarshaler example for a demonstration with
|
||||
// email addresses.
|
||||
//
|
||||
// # Key mapping
|
||||
//
|
||||
// TOML keys can map to either keys in a Go map or field names in a Go struct.
|
||||
// The special `toml` struct tag can be used to map TOML keys to struct fields
|
||||
// that don't match the key name exactly (see the example). A case insensitive
|
||||
// match to struct names will be tried if an exact match can't be found.
|
||||
//
|
||||
// The mapping between TOML values and Go values is loose. That is, there may
|
||||
// exist TOML values that cannot be placed into your representation, and there
|
||||
// may be parts of your representation that do not correspond to TOML values.
|
||||
// This loose mapping can be made stricter by using the IsDefined and/or
|
||||
// Undecoded methods on the MetaData returned.
|
||||
//
|
||||
// This decoder does not handle cyclic types. Decode will not terminate if a
|
||||
// cyclic type is passed.
|
||||
type Decoder struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
// NewDecoder creates a new Decoder.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{r: r}
|
||||
}
|
||||
|
||||
var (
|
||||
unmarshalToml = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||
unmarshalText = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
primitiveType = reflect.TypeOf((*Primitive)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Decode TOML data in to the pointer `v`.
|
||||
func (dec *Decoder) Decode(v interface{}) (MetaData, error) {
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
s := "%q"
|
||||
if reflect.TypeOf(v) == nil {
|
||||
s = "%v"
|
||||
}
|
||||
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to non-pointer "+s, reflect.TypeOf(v))
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to nil value of %q", reflect.TypeOf(v))
|
||||
}
|
||||
|
||||
// Check if this is a supported type: struct, map, interface{}, or something
|
||||
// that implements UnmarshalTOML or UnmarshalText.
|
||||
rv = indirect(rv)
|
||||
rt := rv.Type()
|
||||
if rv.Kind() != reflect.Struct && rv.Kind() != reflect.Map &&
|
||||
!(rv.Kind() == reflect.Interface && rv.NumMethod() == 0) &&
|
||||
!rt.Implements(unmarshalToml) && !rt.Implements(unmarshalText) {
|
||||
return MetaData{}, fmt.Errorf("toml: cannot decode to type %s", rt)
|
||||
}
|
||||
|
||||
// TODO: parser should read from io.Reader? Or at the very least, make it
|
||||
// read from []byte rather than string
|
||||
data, err := ioutil.ReadAll(dec.r)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
|
||||
p, err := parse(string(data))
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
|
||||
md := MetaData{
|
||||
mapping: p.mapping,
|
||||
keyInfo: p.keyInfo,
|
||||
keys: p.ordered,
|
||||
decoded: make(map[string]struct{}, len(p.ordered)),
|
||||
context: nil,
|
||||
data: data,
|
||||
}
|
||||
return md, md.unify(p.mapping, rv)
|
||||
}
|
||||
|
||||
// PrimitiveDecode is just like the other Decode* functions, except it decodes a
|
||||
// TOML value that has already been parsed. Valid primitive values can *only* be
|
||||
// obtained from values filled by the decoder functions, including this method.
|
||||
// (i.e., v may contain more [Primitive] values.)
|
||||
//
|
||||
// Meta data for primitive values is included in the meta data returned by the
|
||||
// Decode* functions with one exception: keys returned by the Undecoded method
|
||||
// will only reflect keys that were decoded. Namely, any keys hidden behind a
|
||||
// Primitive will be considered undecoded. Executing this method will update the
|
||||
// undecoded keys in the meta data. (See the example.)
|
||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md.context = primValue.context
|
||||
defer func() { md.context = nil }()
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// unify performs a sort of type unification based on the structure of `rv`,
|
||||
// which is the client representation.
|
||||
//
|
||||
// Any type mismatch produces an error. Finding a type that we don't know
|
||||
// how to handle produces an unsupported type error.
|
||||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
||||
// Special case. Look for a `Primitive` value.
|
||||
// TODO: #76 would make this superfluous after implemented.
|
||||
if rv.Type() == primitiveType {
|
||||
// Save the undecoded data and the key context into the primitive
|
||||
// value.
|
||||
context := make(Key, len(md.context))
|
||||
copy(context, md.context)
|
||||
rv.Set(reflect.ValueOf(Primitive{
|
||||
undecoded: data,
|
||||
context: context,
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
rvi := rv.Interface()
|
||||
if v, ok := rvi.(Unmarshaler); ok {
|
||||
return v.UnmarshalTOML(data)
|
||||
}
|
||||
if v, ok := rvi.(encoding.TextUnmarshaler); ok {
|
||||
return md.unifyText(data, v)
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// The behavior here is incorrect whenever a Go type satisfies the
|
||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML hash or
|
||||
// array. In particular, the unmarshaler should only be applied to primitive
|
||||
// TOML values. But at this point, it will be applied to all kinds of values
|
||||
// and produce an incorrect error whenever those values are hashes or arrays
|
||||
// (including arrays of tables).
|
||||
|
||||
k := rv.Kind()
|
||||
|
||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
||||
return md.unifyInt(data, rv)
|
||||
}
|
||||
switch k {
|
||||
case reflect.Ptr:
|
||||
elem := reflect.New(rv.Type().Elem())
|
||||
err := md.unify(data, reflect.Indirect(elem))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rv.Set(elem)
|
||||
return nil
|
||||
case reflect.Struct:
|
||||
return md.unifyStruct(data, rv)
|
||||
case reflect.Map:
|
||||
return md.unifyMap(data, rv)
|
||||
case reflect.Array:
|
||||
return md.unifyArray(data, rv)
|
||||
case reflect.Slice:
|
||||
return md.unifySlice(data, rv)
|
||||
case reflect.String:
|
||||
return md.unifyString(data, rv)
|
||||
case reflect.Bool:
|
||||
return md.unifyBool(data, rv)
|
||||
case reflect.Interface:
|
||||
if rv.NumMethod() > 0 { /// Only empty interfaces are supported.
|
||||
return md.e("unsupported type %s", rv.Type())
|
||||
}
|
||||
return md.unifyAnything(data, rv)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return md.unifyFloat64(data, rv)
|
||||
}
|
||||
return md.e("unsupported type %s", rv.Kind())
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if mapping == nil {
|
||||
return nil
|
||||
}
|
||||
return md.e("type mismatch for %s: expected table but found %T",
|
||||
rv.Type().String(), mapping)
|
||||
}
|
||||
|
||||
for key, datum := range tmap {
|
||||
var f *field
|
||||
fields := cachedTypeFields(rv.Type())
|
||||
for i := range fields {
|
||||
ff := &fields[i]
|
||||
if ff.name == key {
|
||||
f = ff
|
||||
break
|
||||
}
|
||||
if f == nil && strings.EqualFold(ff.name, key) {
|
||||
f = ff
|
||||
}
|
||||
}
|
||||
if f != nil {
|
||||
subv := rv
|
||||
for _, i := range f.index {
|
||||
subv = indirect(subv.Field(i))
|
||||
}
|
||||
|
||||
if isUnifiable(subv) {
|
||||
md.decoded[md.context.add(key).String()] = struct{}{}
|
||||
md.context = append(md.context, key)
|
||||
|
||||
err := md.unify(datum, subv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
} else if f.name != "" {
|
||||
return md.e("cannot write unexported field %s.%s", rv.Type().String(), f.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
||||
keyType := rv.Type().Key().Kind()
|
||||
if keyType != reflect.String && keyType != reflect.Interface {
|
||||
return fmt.Errorf("toml: cannot decode to a map with non-string key type (%s in %q)",
|
||||
keyType, rv.Type())
|
||||
}
|
||||
|
||||
tmap, ok := mapping.(map[string]interface{})
|
||||
if !ok {
|
||||
if tmap == nil {
|
||||
return nil
|
||||
}
|
||||
return md.badtype("map", mapping)
|
||||
}
|
||||
if rv.IsNil() {
|
||||
rv.Set(reflect.MakeMap(rv.Type()))
|
||||
}
|
||||
for k, v := range tmap {
|
||||
md.decoded[md.context.add(k).String()] = struct{}{}
|
||||
md.context = append(md.context, k)
|
||||
|
||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
||||
|
||||
err := md.unify(v, indirect(rvval))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
md.context = md.context[0 : len(md.context)-1]
|
||||
|
||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
||||
|
||||
switch keyType {
|
||||
case reflect.Interface:
|
||||
rvkey.Set(reflect.ValueOf(k))
|
||||
case reflect.String:
|
||||
rvkey.SetString(k)
|
||||
}
|
||||
|
||||
rv.SetMapIndex(rvkey, rvval)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
if l := datav.Len(); l != rv.Len() {
|
||||
return md.e("expected array length %d; got TOML array of length %d", rv.Len(), l)
|
||||
}
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
||||
datav := reflect.ValueOf(data)
|
||||
if datav.Kind() != reflect.Slice {
|
||||
if !datav.IsValid() {
|
||||
return nil
|
||||
}
|
||||
return md.badtype("slice", data)
|
||||
}
|
||||
n := datav.Len()
|
||||
if rv.IsNil() || rv.Cap() < n {
|
||||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
||||
}
|
||||
rv.SetLen(n)
|
||||
return md.unifySliceArray(datav, rv)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
||||
l := data.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
err := md.unify(data.Index(i).Interface(), indirect(rv.Index(i)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
||||
_, ok := rv.Interface().(json.Number)
|
||||
if ok {
|
||||
if i, ok := data.(int64); ok {
|
||||
rv.SetString(strconv.FormatInt(i, 10))
|
||||
} else if f, ok := data.(float64); ok {
|
||||
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
|
||||
} else {
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if s, ok := data.(string); ok {
|
||||
rv.SetString(s)
|
||||
return nil
|
||||
}
|
||||
return md.badtype("string", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
||||
rvk := rv.Kind()
|
||||
|
||||
if num, ok := data.(float64); ok {
|
||||
switch rvk {
|
||||
case reflect.Float32:
|
||||
if num < -math.MaxFloat32 || num > math.MaxFloat32 {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
fallthrough
|
||||
case reflect.Float64:
|
||||
rv.SetFloat(num)
|
||||
default:
|
||||
panic("bug")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if num, ok := data.(int64); ok {
|
||||
if (rvk == reflect.Float32 && (num < -maxSafeFloat32Int || num > maxSafeFloat32Int)) ||
|
||||
(rvk == reflect.Float64 && (num < -maxSafeFloat64Int || num > maxSafeFloat64Int)) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetFloat(float64(num))
|
||||
return nil
|
||||
}
|
||||
|
||||
return md.badtype("float", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
||||
_, ok := rv.Interface().(time.Duration)
|
||||
if ok {
|
||||
// Parse as string duration, and fall back to regular integer parsing
|
||||
// (as nanosecond) if this is not a string.
|
||||
if s, ok := data.(string); ok {
|
||||
dur, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return md.parseErr(errParseDuration{s})
|
||||
}
|
||||
rv.SetInt(int64(dur))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
num, ok := data.(int64)
|
||||
if !ok {
|
||||
return md.badtype("integer", data)
|
||||
}
|
||||
|
||||
rvk := rv.Kind()
|
||||
switch {
|
||||
case rvk >= reflect.Int && rvk <= reflect.Int64:
|
||||
if (rvk == reflect.Int8 && (num < math.MinInt8 || num > math.MaxInt8)) ||
|
||||
(rvk == reflect.Int16 && (num < math.MinInt16 || num > math.MaxInt16)) ||
|
||||
(rvk == reflect.Int32 && (num < math.MinInt32 || num > math.MaxInt32)) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetInt(num)
|
||||
case rvk >= reflect.Uint && rvk <= reflect.Uint64:
|
||||
unum := uint64(num)
|
||||
if rvk == reflect.Uint8 && (num < 0 || unum > math.MaxUint8) ||
|
||||
rvk == reflect.Uint16 && (num < 0 || unum > math.MaxUint16) ||
|
||||
rvk == reflect.Uint32 && (num < 0 || unum > math.MaxUint32) {
|
||||
return md.parseErr(errParseRange{i: num, size: rvk.String()})
|
||||
}
|
||||
rv.SetUint(unum)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
||||
if b, ok := data.(bool); ok {
|
||||
rv.SetBool(b)
|
||||
return nil
|
||||
}
|
||||
return md.badtype("boolean", data)
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
||||
rv.Set(reflect.ValueOf(data))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) unifyText(data interface{}, v encoding.TextUnmarshaler) error {
|
||||
var s string
|
||||
switch sdata := data.(type) {
|
||||
case Marshaler:
|
||||
text, err := sdata.MarshalTOML()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case encoding.TextMarshaler:
|
||||
text, err := sdata.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s = string(text)
|
||||
case fmt.Stringer:
|
||||
s = sdata.String()
|
||||
case string:
|
||||
s = sdata
|
||||
case bool:
|
||||
s = fmt.Sprintf("%v", sdata)
|
||||
case int64:
|
||||
s = fmt.Sprintf("%d", sdata)
|
||||
case float64:
|
||||
s = fmt.Sprintf("%f", sdata)
|
||||
default:
|
||||
return md.badtype("primitive (string-like)", data)
|
||||
}
|
||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (md *MetaData) badtype(dst string, data interface{}) error {
|
||||
return md.e("incompatible types: TOML value has type %T; destination has type %s", data, dst)
|
||||
}
|
||||
|
||||
func (md *MetaData) parseErr(err error) error {
|
||||
k := md.context.String()
|
||||
return ParseError{
|
||||
LastKey: k,
|
||||
Position: md.keyInfo[k].pos,
|
||||
Line: md.keyInfo[k].pos.Line,
|
||||
err: err,
|
||||
input: string(md.data),
|
||||
}
|
||||
}
|
||||
|
||||
func (md *MetaData) e(format string, args ...interface{}) error {
|
||||
f := "toml: "
|
||||
if len(md.context) > 0 {
|
||||
f = fmt.Sprintf("toml: (last key %q): ", md.context)
|
||||
p := md.keyInfo[md.context.String()].pos
|
||||
if p.Line > 0 {
|
||||
f = fmt.Sprintf("toml: line %d (last key %q): ", p.Line, md.context)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf(f+format, args...)
|
||||
}
|
||||
|
||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
||||
func rvalue(v interface{}) reflect.Value {
|
||||
return indirect(reflect.ValueOf(v))
|
||||
}
|
||||
|
||||
// indirect returns the value pointed to by a pointer.
|
||||
//
|
||||
// Pointers are followed until the value is not a pointer. New values are
|
||||
// allocated for each nil pointer.
|
||||
//
|
||||
// An exception to this rule is if the value satisfies an interface of interest
|
||||
// to us (like encoding.TextUnmarshaler).
|
||||
func indirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
if v.CanSet() {
|
||||
pv := v.Addr()
|
||||
pvi := pv.Interface()
|
||||
if _, ok := pvi.(encoding.TextUnmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
if _, ok := pvi.(Unmarshaler); ok {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
return indirect(reflect.Indirect(v))
|
||||
}
|
||||
|
||||
func isUnifiable(rv reflect.Value) bool {
|
||||
if rv.CanSet() {
|
||||
return true
|
||||
}
|
||||
rvi := rv.Interface()
|
||||
if _, ok := rvi.(encoding.TextUnmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := rvi.(Unmarshaler); ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
19
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
Normal file
19
vendor/github.com/BurntSushi/toml/decode_go116.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
//go:build go1.16
|
||||
// +build go1.16
|
||||
|
||||
package toml
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
// DecodeFS reads the contents of a file from [fs.FS] and decodes it with
|
||||
// [Decode].
|
||||
func DecodeFS(fsys fs.FS, path string, v interface{}) (MetaData, error) {
|
||||
fp, err := fsys.Open(path)
|
||||
if err != nil {
|
||||
return MetaData{}, err
|
||||
}
|
||||
defer fp.Close()
|
||||
return NewDecoder(fp).Decode(v)
|
||||
}
|
||||
29
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
29
vendor/github.com/BurntSushi/toml/deprecated.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"io"
|
||||
)
|
||||
|
||||
// TextMarshaler is an alias for encoding.TextMarshaler.
|
||||
//
|
||||
// Deprecated: use encoding.TextMarshaler
|
||||
type TextMarshaler encoding.TextMarshaler
|
||||
|
||||
// TextUnmarshaler is an alias for encoding.TextUnmarshaler.
|
||||
//
|
||||
// Deprecated: use encoding.TextUnmarshaler
|
||||
type TextUnmarshaler encoding.TextUnmarshaler
|
||||
|
||||
// PrimitiveDecode is an alias for MetaData.PrimitiveDecode().
|
||||
//
|
||||
// Deprecated: use MetaData.PrimitiveDecode.
|
||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
||||
md := MetaData{decoded: make(map[string]struct{})}
|
||||
return md.unify(primValue.undecoded, rvalue(v))
|
||||
}
|
||||
|
||||
// DecodeReader is an alias for NewDecoder(r).Decode(v).
|
||||
//
|
||||
// Deprecated: use NewDecoder(reader).Decode(&value).
|
||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) { return NewDecoder(r).Decode(v) }
|
||||
11
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
11
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// Package toml implements decoding and encoding of TOML files.
|
||||
//
|
||||
// This package supports TOML v1.0.0, as specified at https://toml.io
|
||||
//
|
||||
// There is also support for delaying decoding with the Primitive type, and
|
||||
// querying the set of keys in a TOML document with the MetaData type.
|
||||
//
|
||||
// The github.com/BurntSushi/toml/cmd/tomlv package implements a TOML validator,
|
||||
// and can be used to verify if TOML document is valid. It can also be used to
|
||||
// print the type of each key.
|
||||
package toml
|
||||
759
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
759
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
Normal file
@@ -0,0 +1,759 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml/internal"
|
||||
)
|
||||
|
||||
type tomlEncodeError struct{ error }
|
||||
|
||||
var (
|
||||
errArrayNilElement = errors.New("toml: cannot encode array with nil element")
|
||||
errNonString = errors.New("toml: cannot encode a map with non-string key type")
|
||||
errNoKey = errors.New("toml: top-level values must be Go maps or structs")
|
||||
errAnything = errors.New("") // used in testing
|
||||
)
|
||||
|
||||
var dblQuotedReplacer = strings.NewReplacer(
|
||||
"\"", "\\\"",
|
||||
"\\", "\\\\",
|
||||
"\x00", `\u0000`,
|
||||
"\x01", `\u0001`,
|
||||
"\x02", `\u0002`,
|
||||
"\x03", `\u0003`,
|
||||
"\x04", `\u0004`,
|
||||
"\x05", `\u0005`,
|
||||
"\x06", `\u0006`,
|
||||
"\x07", `\u0007`,
|
||||
"\b", `\b`,
|
||||
"\t", `\t`,
|
||||
"\n", `\n`,
|
||||
"\x0b", `\u000b`,
|
||||
"\f", `\f`,
|
||||
"\r", `\r`,
|
||||
"\x0e", `\u000e`,
|
||||
"\x0f", `\u000f`,
|
||||
"\x10", `\u0010`,
|
||||
"\x11", `\u0011`,
|
||||
"\x12", `\u0012`,
|
||||
"\x13", `\u0013`,
|
||||
"\x14", `\u0014`,
|
||||
"\x15", `\u0015`,
|
||||
"\x16", `\u0016`,
|
||||
"\x17", `\u0017`,
|
||||
"\x18", `\u0018`,
|
||||
"\x19", `\u0019`,
|
||||
"\x1a", `\u001a`,
|
||||
"\x1b", `\u001b`,
|
||||
"\x1c", `\u001c`,
|
||||
"\x1d", `\u001d`,
|
||||
"\x1e", `\u001e`,
|
||||
"\x1f", `\u001f`,
|
||||
"\x7f", `\u007f`,
|
||||
)
|
||||
|
||||
var (
|
||||
marshalToml = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||
marshalText = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
|
||||
timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Marshaler is the interface implemented by types that can marshal themselves
|
||||
// into valid TOML.
|
||||
type Marshaler interface {
|
||||
MarshalTOML() ([]byte, error)
|
||||
}
|
||||
|
||||
// Encoder encodes a Go to a TOML document.
|
||||
//
|
||||
// The mapping between Go values and TOML values should be precisely the same as
|
||||
// for [Decode].
|
||||
//
|
||||
// time.Time is encoded as a RFC 3339 string, and time.Duration as its string
|
||||
// representation.
|
||||
//
|
||||
// The [Marshaler] and [encoding.TextMarshaler] interfaces are supported to
|
||||
// encoding the value as custom TOML.
|
||||
//
|
||||
// If you want to write arbitrary binary data then you will need to use
|
||||
// something like base64 since TOML does not have any binary types.
|
||||
//
|
||||
// When encoding TOML hashes (Go maps or structs), keys without any sub-hashes
|
||||
// are encoded first.
|
||||
//
|
||||
// Go maps will be sorted alphabetically by key for deterministic output.
|
||||
//
|
||||
// The toml struct tag can be used to provide the key name; if omitted the
|
||||
// struct field name will be used. If the "omitempty" option is present the
|
||||
// following value will be skipped:
|
||||
//
|
||||
// - arrays, slices, maps, and string with len of 0
|
||||
// - struct with all zero values
|
||||
// - bool false
|
||||
//
|
||||
// If omitzero is given all int and float types with a value of 0 will be
|
||||
// skipped.
|
||||
//
|
||||
// Encoding Go values without a corresponding TOML representation will return an
|
||||
// error. Examples of this includes maps with non-string keys, slices with nil
|
||||
// elements, embedded non-struct types, and nested slices containing maps or
|
||||
// structs. (e.g. [][]map[string]string is not allowed but []map[string]string
|
||||
// is okay, as is []map[string][]string).
|
||||
//
|
||||
// NOTE: only exported keys are encoded due to the use of reflection. Unexported
|
||||
// keys are silently discarded.
|
||||
type Encoder struct {
|
||||
// String to use for a single indentation level; default is two spaces.
|
||||
Indent string
|
||||
|
||||
w *bufio.Writer
|
||||
hasWritten bool // written any output to w yet?
|
||||
}
|
||||
|
||||
// NewEncoder create a new Encoder.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: bufio.NewWriter(w),
|
||||
Indent: " ",
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes a TOML representation of the Go value to the [Encoder]'s writer.
|
||||
//
|
||||
// An error is returned if the value given cannot be encoded to a valid TOML
|
||||
// document.
|
||||
func (enc *Encoder) Encode(v interface{}) error {
|
||||
rv := eindirect(reflect.ValueOf(v))
|
||||
err := enc.safeEncode(Key([]string{}), rv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return enc.w.Flush()
|
||||
}
|
||||
|
||||
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if terr, ok := r.(tomlEncodeError); ok {
|
||||
err = terr.error
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
enc.encode(key, rv)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
||||
// If we can marshal the type to text, then we use that. This prevents the
|
||||
// encoder for handling these types as generic structs (or whatever the
|
||||
// underlying type of a TextMarshaler is).
|
||||
switch {
|
||||
case isMarshaler(rv):
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
return
|
||||
case rv.Type() == primitiveType: // TODO: #76 would make this superfluous after implemented.
|
||||
enc.encode(key, reflect.ValueOf(rv.Interface().(Primitive).undecoded))
|
||||
return
|
||||
}
|
||||
|
||||
k := rv.Kind()
|
||||
switch k {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64,
|
||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
case reflect.Array, reflect.Slice:
|
||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
||||
enc.eArrayOfTables(key, rv)
|
||||
} else {
|
||||
enc.writeKeyValue(key, rv, false)
|
||||
}
|
||||
case reflect.Interface:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Map:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.eTable(key, rv)
|
||||
case reflect.Ptr:
|
||||
if rv.IsNil() {
|
||||
return
|
||||
}
|
||||
enc.encode(key, rv.Elem())
|
||||
case reflect.Struct:
|
||||
enc.eTable(key, rv)
|
||||
default:
|
||||
encPanic(fmt.Errorf("unsupported type for key '%s': %s", key, k))
|
||||
}
|
||||
}
|
||||
|
||||
// eElement encodes any value that can be an array element.
|
||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
||||
switch v := rv.Interface().(type) {
|
||||
case time.Time: // Using TextMarshaler adds extra quotes, which we don't want.
|
||||
format := time.RFC3339Nano
|
||||
switch v.Location() {
|
||||
case internal.LocalDatetime:
|
||||
format = "2006-01-02T15:04:05.999999999"
|
||||
case internal.LocalDate:
|
||||
format = "2006-01-02"
|
||||
case internal.LocalTime:
|
||||
format = "15:04:05.999999999"
|
||||
}
|
||||
switch v.Location() {
|
||||
default:
|
||||
enc.wf(v.Format(format))
|
||||
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
|
||||
enc.wf(v.In(time.UTC).Format(format))
|
||||
}
|
||||
return
|
||||
case Marshaler:
|
||||
s, err := v.MarshalTOML()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
if s == nil {
|
||||
encPanic(errors.New("MarshalTOML returned nil and no error"))
|
||||
}
|
||||
enc.w.Write(s)
|
||||
return
|
||||
case encoding.TextMarshaler:
|
||||
s, err := v.MarshalText()
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
if s == nil {
|
||||
encPanic(errors.New("MarshalText returned nil and no error"))
|
||||
}
|
||||
enc.writeQuoted(string(s))
|
||||
return
|
||||
case time.Duration:
|
||||
enc.writeQuoted(v.String())
|
||||
return
|
||||
case json.Number:
|
||||
n, _ := rv.Interface().(json.Number)
|
||||
|
||||
if n == "" { /// Useful zero value.
|
||||
enc.w.WriteByte('0')
|
||||
return
|
||||
} else if v, err := n.Int64(); err == nil {
|
||||
enc.eElement(reflect.ValueOf(v))
|
||||
return
|
||||
} else if v, err := n.Float64(); err == nil {
|
||||
enc.eElement(reflect.ValueOf(v))
|
||||
return
|
||||
}
|
||||
encPanic(fmt.Errorf("unable to convert %q to int64 or float64", n))
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Ptr:
|
||||
enc.eElement(rv.Elem())
|
||||
return
|
||||
case reflect.String:
|
||||
enc.writeQuoted(rv.String())
|
||||
case reflect.Bool:
|
||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
||||
case reflect.Float32:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
|
||||
} else {
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
|
||||
}
|
||||
case reflect.Float64:
|
||||
f := rv.Float()
|
||||
if math.IsNaN(f) {
|
||||
enc.wf("nan")
|
||||
} else if math.IsInf(f, 0) {
|
||||
enc.wf("%cinf", map[bool]byte{true: '-', false: '+'}[math.Signbit(f)])
|
||||
} else {
|
||||
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
|
||||
}
|
||||
case reflect.Array, reflect.Slice:
|
||||
enc.eArrayOrSliceElement(rv)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(nil, rv, true)
|
||||
case reflect.Map:
|
||||
enc.eMap(nil, rv, true)
|
||||
case reflect.Interface:
|
||||
enc.eElement(rv.Elem())
|
||||
default:
|
||||
encPanic(fmt.Errorf("unexpected type: %T", rv.Interface()))
|
||||
}
|
||||
}
|
||||
|
||||
// By the TOML spec, all floats must have a decimal with at least one number on
|
||||
// either side.
|
||||
func floatAddDecimal(fstr string) string {
|
||||
if !strings.Contains(fstr, ".") {
|
||||
return fstr + ".0"
|
||||
}
|
||||
return fstr
|
||||
}
|
||||
|
||||
func (enc *Encoder) writeQuoted(s string) {
|
||||
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
||||
length := rv.Len()
|
||||
enc.wf("[")
|
||||
for i := 0; i < length; i++ {
|
||||
elem := eindirect(rv.Index(i))
|
||||
enc.eElement(elem)
|
||||
if i != length-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
}
|
||||
enc.wf("]")
|
||||
}
|
||||
|
||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
||||
if len(key) == 0 {
|
||||
encPanic(errNoKey)
|
||||
}
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
trv := eindirect(rv.Index(i))
|
||||
if isNil(trv) {
|
||||
continue
|
||||
}
|
||||
enc.newline()
|
||||
enc.wf("%s[[%s]]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
enc.eMapOrStruct(key, trv, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
||||
if len(key) == 1 {
|
||||
// Output an extra newline between top-level tables.
|
||||
// (The newline isn't written if nothing else has been written though.)
|
||||
enc.newline()
|
||||
}
|
||||
if len(key) > 0 {
|
||||
enc.wf("%s[%s]", enc.indentStr(key), key)
|
||||
enc.newline()
|
||||
}
|
||||
enc.eMapOrStruct(key, rv, false)
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value, inline bool) {
|
||||
switch rv.Kind() {
|
||||
case reflect.Map:
|
||||
enc.eMap(key, rv, inline)
|
||||
case reflect.Struct:
|
||||
enc.eStruct(key, rv, inline)
|
||||
default:
|
||||
// Should never happen?
|
||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
|
||||
rt := rv.Type()
|
||||
if rt.Key().Kind() != reflect.String {
|
||||
encPanic(errNonString)
|
||||
}
|
||||
|
||||
// Sort keys so that we have deterministic output. And write keys directly
|
||||
// underneath this key first, before writing sub-structs or sub-maps.
|
||||
var mapKeysDirect, mapKeysSub []string
|
||||
for _, mapKey := range rv.MapKeys() {
|
||||
k := mapKey.String()
|
||||
if typeIsTable(tomlTypeOfGo(eindirect(rv.MapIndex(mapKey)))) {
|
||||
mapKeysSub = append(mapKeysSub, k)
|
||||
} else {
|
||||
mapKeysDirect = append(mapKeysDirect, k)
|
||||
}
|
||||
}
|
||||
|
||||
var writeMapKeys = func(mapKeys []string, trailC bool) {
|
||||
sort.Strings(mapKeys)
|
||||
for i, mapKey := range mapKeys {
|
||||
val := eindirect(rv.MapIndex(reflect.ValueOf(mapKey)))
|
||||
if isNil(val) {
|
||||
continue
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{mapKey}, val, true)
|
||||
if trailC || i != len(mapKeys)-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(mapKey), val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.wf("{")
|
||||
}
|
||||
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
|
||||
writeMapKeys(mapKeysSub, false)
|
||||
if inline {
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
const is32Bit = (32 << (^uint(0) >> 63)) == 32
|
||||
|
||||
func pointerTo(t reflect.Type) reflect.Type {
|
||||
if t.Kind() == reflect.Ptr {
|
||||
return pointerTo(t.Elem())
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
|
||||
// Write keys for fields directly under this key first, because if we write
|
||||
// a field that creates a new table then all keys under it will be in that
|
||||
// table (not the one we're writing here).
|
||||
//
|
||||
// Fields is a [][]int: for fieldsDirect this always has one entry (the
|
||||
// struct index). For fieldsSub it contains two entries: the parent field
|
||||
// index from tv, and the field indexes for the fields of the sub.
|
||||
var (
|
||||
rt = rv.Type()
|
||||
fieldsDirect, fieldsSub [][]int
|
||||
addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
||||
)
|
||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
||||
for i := 0; i < rt.NumField(); i++ {
|
||||
f := rt.Field(i)
|
||||
isEmbed := f.Anonymous && pointerTo(f.Type).Kind() == reflect.Struct
|
||||
if f.PkgPath != "" && !isEmbed { /// Skip unexported fields.
|
||||
continue
|
||||
}
|
||||
opts := getOptions(f.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
|
||||
frv := eindirect(rv.Field(i))
|
||||
|
||||
if is32Bit {
|
||||
// Copy so it works correct on 32bit archs; not clear why this
|
||||
// is needed. See #314, and https://www.reddit.com/r/golang/comments/pnx8v4
|
||||
// This also works fine on 64bit, but 32bit archs are somewhat
|
||||
// rare and this is a wee bit faster.
|
||||
copyStart := make([]int, len(start))
|
||||
copy(copyStart, start)
|
||||
start = copyStart
|
||||
}
|
||||
|
||||
// Treat anonymous struct fields with tag names as though they are
|
||||
// not anonymous, like encoding/json does.
|
||||
//
|
||||
// Non-struct anonymous fields use the normal encoding logic.
|
||||
if isEmbed {
|
||||
if getOptions(f.Tag).name == "" && frv.Kind() == reflect.Struct {
|
||||
addFields(frv.Type(), frv, append(start, f.Index...))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if typeIsTable(tomlTypeOfGo(frv)) {
|
||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
||||
} else {
|
||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
||||
}
|
||||
}
|
||||
}
|
||||
addFields(rt, rv, nil)
|
||||
|
||||
writeFields := func(fields [][]int) {
|
||||
for _, fieldIndex := range fields {
|
||||
fieldType := rt.FieldByIndex(fieldIndex)
|
||||
fieldVal := rv.FieldByIndex(fieldIndex)
|
||||
|
||||
opts := getOptions(fieldType.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
if opts.omitempty && isEmpty(fieldVal) {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldVal = eindirect(fieldVal)
|
||||
|
||||
if isNil(fieldVal) { /// Don't write anything for nil fields.
|
||||
continue
|
||||
}
|
||||
|
||||
keyName := fieldType.Name
|
||||
if opts.name != "" {
|
||||
keyName = opts.name
|
||||
}
|
||||
|
||||
if opts.omitzero && isZero(fieldVal) {
|
||||
continue
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.writeKeyValue(Key{keyName}, fieldVal, true)
|
||||
if fieldIndex[0] != len(fields)-1 {
|
||||
enc.wf(", ")
|
||||
}
|
||||
} else {
|
||||
enc.encode(key.add(keyName), fieldVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if inline {
|
||||
enc.wf("{")
|
||||
}
|
||||
writeFields(fieldsDirect)
|
||||
writeFields(fieldsSub)
|
||||
if inline {
|
||||
enc.wf("}")
|
||||
}
|
||||
}
|
||||
|
||||
// tomlTypeOfGo returns the TOML type name of the Go value's type.
|
||||
//
|
||||
// It is used to determine whether the types of array elements are mixed (which
|
||||
// is forbidden). If the Go value is nil, then it is illegal for it to be an
|
||||
// array element, and valueIsNil is returned as true.
|
||||
//
|
||||
// The type may be `nil`, which means no concrete TOML type could be found.
|
||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
||||
if isNil(rv) || !rv.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if rv.Kind() == reflect.Struct {
|
||||
if rv.Type() == timeType {
|
||||
return tomlDatetime
|
||||
}
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
return tomlHash
|
||||
}
|
||||
|
||||
if isMarshaler(rv) {
|
||||
return tomlString
|
||||
}
|
||||
|
||||
switch rv.Kind() {
|
||||
case reflect.Bool:
|
||||
return tomlBool
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
||||
reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
||||
reflect.Uint64:
|
||||
return tomlInteger
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return tomlFloat
|
||||
case reflect.Array, reflect.Slice:
|
||||
if isTableArray(rv) {
|
||||
return tomlArrayHash
|
||||
}
|
||||
return tomlArray
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
return tomlTypeOfGo(rv.Elem())
|
||||
case reflect.String:
|
||||
return tomlString
|
||||
case reflect.Map:
|
||||
return tomlHash
|
||||
default:
|
||||
encPanic(errors.New("unsupported type: " + rv.Kind().String()))
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func isMarshaler(rv reflect.Value) bool {
|
||||
return rv.Type().Implements(marshalText) || rv.Type().Implements(marshalToml)
|
||||
}
|
||||
|
||||
// isTableArray reports if all entries in the array or slice are a table.
|
||||
func isTableArray(arr reflect.Value) bool {
|
||||
if isNil(arr) || !arr.IsValid() || arr.Len() == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
ret := true
|
||||
for i := 0; i < arr.Len(); i++ {
|
||||
tt := tomlTypeOfGo(eindirect(arr.Index(i)))
|
||||
// Don't allow nil.
|
||||
if tt == nil {
|
||||
encPanic(errArrayNilElement)
|
||||
}
|
||||
|
||||
if ret && !typeEqual(tomlHash, tt) {
|
||||
ret = false
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type tagOptions struct {
|
||||
skip bool // "-"
|
||||
name string
|
||||
omitempty bool
|
||||
omitzero bool
|
||||
}
|
||||
|
||||
func getOptions(tag reflect.StructTag) tagOptions {
|
||||
t := tag.Get("toml")
|
||||
if t == "-" {
|
||||
return tagOptions{skip: true}
|
||||
}
|
||||
var opts tagOptions
|
||||
parts := strings.Split(t, ",")
|
||||
opts.name = parts[0]
|
||||
for _, s := range parts[1:] {
|
||||
switch s {
|
||||
case "omitempty":
|
||||
opts.omitempty = true
|
||||
case "omitzero":
|
||||
opts.omitzero = true
|
||||
}
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func isZero(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return rv.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
return rv.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return rv.Float() == 0.0
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isEmpty(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return rv.Len() == 0
|
||||
case reflect.Struct:
|
||||
if rv.Type().Comparable() {
|
||||
return reflect.Zero(rv.Type()).Interface() == rv.Interface()
|
||||
}
|
||||
// Need to also check if all the fields are empty, otherwise something
|
||||
// like this with uncomparable types will always return true:
|
||||
//
|
||||
// type a struct{ field b }
|
||||
// type b struct{ s []string }
|
||||
// s := a{field: b{s: []string{"AAA"}}}
|
||||
for i := 0; i < rv.NumField(); i++ {
|
||||
if !isEmpty(rv.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Bool:
|
||||
return !rv.Bool()
|
||||
case reflect.Ptr:
|
||||
return rv.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (enc *Encoder) newline() {
|
||||
if enc.hasWritten {
|
||||
enc.wf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Write a key/value pair:
|
||||
//
|
||||
// key = <any value>
|
||||
//
|
||||
// This is also used for "k = v" in inline tables; so something like this will
|
||||
// be written in three calls:
|
||||
//
|
||||
// ┌───────────────────┐
|
||||
// │ ┌───┐ ┌────┐│
|
||||
// v v v v vv
|
||||
// key = {k = 1, k2 = 2}
|
||||
func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
|
||||
/// Marshaler used on top-level document; call eElement() to just call
|
||||
/// Marshal{TOML,Text}.
|
||||
if len(key) == 0 {
|
||||
enc.eElement(val)
|
||||
return
|
||||
}
|
||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
||||
enc.eElement(val)
|
||||
if !inline {
|
||||
enc.newline()
|
||||
}
|
||||
}
|
||||
|
||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
||||
_, err := fmt.Fprintf(enc.w, format, v...)
|
||||
if err != nil {
|
||||
encPanic(err)
|
||||
}
|
||||
enc.hasWritten = true
|
||||
}
|
||||
|
||||
func (enc *Encoder) indentStr(key Key) string {
|
||||
return strings.Repeat(enc.Indent, len(key)-1)
|
||||
}
|
||||
|
||||
func encPanic(err error) {
|
||||
panic(tomlEncodeError{err})
|
||||
}
|
||||
|
||||
// Resolve any level of pointers to the actual value (e.g. **string → string).
|
||||
func eindirect(v reflect.Value) reflect.Value {
|
||||
if v.Kind() != reflect.Ptr && v.Kind() != reflect.Interface {
|
||||
if isMarshaler(v) {
|
||||
return v
|
||||
}
|
||||
if v.CanAddr() { /// Special case for marshalers; see #358.
|
||||
if pv := v.Addr(); isMarshaler(pv) {
|
||||
return pv
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
if v.IsNil() {
|
||||
return v
|
||||
}
|
||||
|
||||
return eindirect(v.Elem())
|
||||
}
|
||||
|
||||
func isNil(rv reflect.Value) bool {
|
||||
switch rv.Kind() {
|
||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return rv.IsNil()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
279
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
279
vendor/github.com/BurntSushi/toml/error.go
generated
vendored
Normal file
@@ -0,0 +1,279 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseError is returned when there is an error parsing the TOML syntax such as
|
||||
// invalid syntax, duplicate keys, etc.
|
||||
//
|
||||
// In addition to the error message itself, you can also print detailed location
|
||||
// information with context by using [ErrorWithPosition]:
|
||||
//
|
||||
// toml: error: Key 'fruit' was already created and cannot be used as an array.
|
||||
//
|
||||
// At line 4, column 2-7:
|
||||
//
|
||||
// 2 | fruit = []
|
||||
// 3 |
|
||||
// 4 | [[fruit]] # Not allowed
|
||||
// ^^^^^
|
||||
//
|
||||
// [ErrorWithUsage] can be used to print the above with some more detailed usage
|
||||
// guidance:
|
||||
//
|
||||
// toml: error: newlines not allowed within inline tables
|
||||
//
|
||||
// At line 1, column 18:
|
||||
//
|
||||
// 1 | x = [{ key = 42 #
|
||||
// ^
|
||||
//
|
||||
// Error help:
|
||||
//
|
||||
// Inline tables must always be on a single line:
|
||||
//
|
||||
// table = {key = 42, second = 43}
|
||||
//
|
||||
// It is invalid to split them over multiple lines like so:
|
||||
//
|
||||
// # INVALID
|
||||
// table = {
|
||||
// key = 42,
|
||||
// second = 43
|
||||
// }
|
||||
//
|
||||
// Use regular for this:
|
||||
//
|
||||
// [table]
|
||||
// key = 42
|
||||
// second = 43
|
||||
type ParseError struct {
|
||||
Message string // Short technical message.
|
||||
Usage string // Longer message with usage guidance; may be blank.
|
||||
Position Position // Position of the error
|
||||
LastKey string // Last parsed key, may be blank.
|
||||
|
||||
// Line the error occurred.
|
||||
//
|
||||
// Deprecated: use [Position].
|
||||
Line int
|
||||
|
||||
err error
|
||||
input string
|
||||
}
|
||||
|
||||
// Position of an error.
|
||||
type Position struct {
|
||||
Line int // Line number, starting at 1.
|
||||
Start int // Start of error, as byte offset starting at 0.
|
||||
Len int // Lenght in bytes.
|
||||
}
|
||||
|
||||
func (pe ParseError) Error() string {
|
||||
msg := pe.Message
|
||||
if msg == "" { // Error from errorf()
|
||||
msg = pe.err.Error()
|
||||
}
|
||||
|
||||
if pe.LastKey == "" {
|
||||
return fmt.Sprintf("toml: line %d: %s", pe.Position.Line, msg)
|
||||
}
|
||||
return fmt.Sprintf("toml: line %d (last key %q): %s",
|
||||
pe.Position.Line, pe.LastKey, msg)
|
||||
}
|
||||
|
||||
// ErrorWithPosition returns the error with detailed location context.
|
||||
//
|
||||
// See the documentation on [ParseError].
|
||||
func (pe ParseError) ErrorWithPosition() string {
|
||||
if pe.input == "" { // Should never happen, but just in case.
|
||||
return pe.Error()
|
||||
}
|
||||
|
||||
var (
|
||||
lines = strings.Split(pe.input, "\n")
|
||||
col = pe.column(lines)
|
||||
b = new(strings.Builder)
|
||||
)
|
||||
|
||||
msg := pe.Message
|
||||
if msg == "" {
|
||||
msg = pe.err.Error()
|
||||
}
|
||||
|
||||
// TODO: don't show control characters as literals? This may not show up
|
||||
// well everywhere.
|
||||
|
||||
if pe.Position.Len == 1 {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d:\n\n",
|
||||
msg, pe.Position.Line, col+1)
|
||||
} else {
|
||||
fmt.Fprintf(b, "toml: error: %s\n\nAt line %d, column %d-%d:\n\n",
|
||||
msg, pe.Position.Line, col, col+pe.Position.Len)
|
||||
}
|
||||
if pe.Position.Line > 2 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-2, lines[pe.Position.Line-3])
|
||||
}
|
||||
if pe.Position.Line > 1 {
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line-1, lines[pe.Position.Line-2])
|
||||
}
|
||||
fmt.Fprintf(b, "% 7d | %s\n", pe.Position.Line, lines[pe.Position.Line-1])
|
||||
fmt.Fprintf(b, "% 10s%s%s\n", "", strings.Repeat(" ", col), strings.Repeat("^", pe.Position.Len))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ErrorWithUsage returns the error with detailed location context and usage
|
||||
// guidance.
|
||||
//
|
||||
// See the documentation on [ParseError].
|
||||
func (pe ParseError) ErrorWithUsage() string {
|
||||
m := pe.ErrorWithPosition()
|
||||
if u, ok := pe.err.(interface{ Usage() string }); ok && u.Usage() != "" {
|
||||
lines := strings.Split(strings.TrimSpace(u.Usage()), "\n")
|
||||
for i := range lines {
|
||||
if lines[i] != "" {
|
||||
lines[i] = " " + lines[i]
|
||||
}
|
||||
}
|
||||
return m + "Error help:\n\n" + strings.Join(lines, "\n") + "\n"
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (pe ParseError) column(lines []string) int {
|
||||
var pos, col int
|
||||
for i := range lines {
|
||||
ll := len(lines[i]) + 1 // +1 for the removed newline
|
||||
if pos+ll >= pe.Position.Start {
|
||||
col = pe.Position.Start - pos
|
||||
if col < 0 { // Should never happen, but just in case.
|
||||
col = 0
|
||||
}
|
||||
break
|
||||
}
|
||||
pos += ll
|
||||
}
|
||||
|
||||
return col
|
||||
}
|
||||
|
||||
type (
|
||||
errLexControl struct{ r rune }
|
||||
errLexEscape struct{ r rune }
|
||||
errLexUTF8 struct{ b byte }
|
||||
errLexInvalidNum struct{ v string }
|
||||
errLexInvalidDate struct{ v string }
|
||||
errLexInlineTableNL struct{}
|
||||
errLexStringNL struct{}
|
||||
errParseRange struct {
|
||||
i interface{} // int or float
|
||||
size string // "int64", "uint16", etc.
|
||||
}
|
||||
errParseDuration struct{ d string }
|
||||
)
|
||||
|
||||
func (e errLexControl) Error() string {
|
||||
return fmt.Sprintf("TOML files cannot contain control characters: '0x%02x'", e.r)
|
||||
}
|
||||
func (e errLexControl) Usage() string { return "" }
|
||||
|
||||
func (e errLexEscape) Error() string { return fmt.Sprintf(`invalid escape in string '\%c'`, e.r) }
|
||||
func (e errLexEscape) Usage() string { return usageEscape }
|
||||
func (e errLexUTF8) Error() string { return fmt.Sprintf("invalid UTF-8 byte: 0x%02x", e.b) }
|
||||
func (e errLexUTF8) Usage() string { return "" }
|
||||
func (e errLexInvalidNum) Error() string { return fmt.Sprintf("invalid number: %q", e.v) }
|
||||
func (e errLexInvalidNum) Usage() string { return "" }
|
||||
func (e errLexInvalidDate) Error() string { return fmt.Sprintf("invalid date: %q", e.v) }
|
||||
func (e errLexInvalidDate) Usage() string { return "" }
|
||||
func (e errLexInlineTableNL) Error() string { return "newlines not allowed within inline tables" }
|
||||
func (e errLexInlineTableNL) Usage() string { return usageInlineNewline }
|
||||
func (e errLexStringNL) Error() string { return "strings cannot contain newlines" }
|
||||
func (e errLexStringNL) Usage() string { return usageStringNewline }
|
||||
func (e errParseRange) Error() string { return fmt.Sprintf("%v is out of range for %s", e.i, e.size) }
|
||||
func (e errParseRange) Usage() string { return usageIntOverflow }
|
||||
func (e errParseDuration) Error() string { return fmt.Sprintf("invalid duration: %q", e.d) }
|
||||
func (e errParseDuration) Usage() string { return usageDuration }
|
||||
|
||||
const usageEscape = `
|
||||
A '\' inside a "-delimited string is interpreted as an escape character.
|
||||
|
||||
The following escape sequences are supported:
|
||||
\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX
|
||||
|
||||
To prevent a '\' from being recognized as an escape character, use either:
|
||||
|
||||
- a ' or '''-delimited string; escape characters aren't processed in them; or
|
||||
- write two backslashes to get a single backslash: '\\'.
|
||||
|
||||
If you're trying to add a Windows path (e.g. "C:\Users\martin") then using '/'
|
||||
instead of '\' will usually also work: "C:/Users/martin".
|
||||
`
|
||||
|
||||
const usageInlineNewline = `
|
||||
Inline tables must always be on a single line:
|
||||
|
||||
table = {key = 42, second = 43}
|
||||
|
||||
It is invalid to split them over multiple lines like so:
|
||||
|
||||
# INVALID
|
||||
table = {
|
||||
key = 42,
|
||||
second = 43
|
||||
}
|
||||
|
||||
Use regular for this:
|
||||
|
||||
[table]
|
||||
key = 42
|
||||
second = 43
|
||||
`
|
||||
|
||||
const usageStringNewline = `
|
||||
Strings must always be on a single line, and cannot span more than one line:
|
||||
|
||||
# INVALID
|
||||
string = "Hello,
|
||||
world!"
|
||||
|
||||
Instead use """ or ''' to split strings over multiple lines:
|
||||
|
||||
string = """Hello,
|
||||
world!"""
|
||||
`
|
||||
|
||||
const usageIntOverflow = `
|
||||
This number is too large; this may be an error in the TOML, but it can also be a
|
||||
bug in the program that uses too small of an integer.
|
||||
|
||||
The maximum and minimum values are:
|
||||
|
||||
size │ lowest │ highest
|
||||
───────┼────────────────┼──────────
|
||||
int8 │ -128 │ 127
|
||||
int16 │ -32,768 │ 32,767
|
||||
int32 │ -2,147,483,648 │ 2,147,483,647
|
||||
int64 │ -9.2 × 10¹⁷ │ 9.2 × 10¹⁷
|
||||
uint8 │ 0 │ 255
|
||||
uint16 │ 0 │ 65535
|
||||
uint32 │ 0 │ 4294967295
|
||||
uint64 │ 0 │ 1.8 × 10¹⁸
|
||||
|
||||
int refers to int32 on 32-bit systems and int64 on 64-bit systems.
|
||||
`
|
||||
|
||||
const usageDuration = `
|
||||
A duration must be as "number<unit>", without any spaces. Valid units are:
|
||||
|
||||
ns nanoseconds (billionth of a second)
|
||||
us, µs microseconds (millionth of a second)
|
||||
ms milliseconds (thousands of a second)
|
||||
s seconds
|
||||
m minutes
|
||||
h hours
|
||||
|
||||
You can combine multiple units; for example "5m10s" for 5 minutes and 10
|
||||
seconds.
|
||||
`
|
||||
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
36
vendor/github.com/BurntSushi/toml/internal/tz.go
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
package internal
|
||||
|
||||
import "time"
|
||||
|
||||
// Timezones used for local datetime, date, and time TOML types.
|
||||
//
|
||||
// The exact way times and dates without a timezone should be interpreted is not
|
||||
// well-defined in the TOML specification and left to the implementation. These
|
||||
// defaults to current local timezone offset of the computer, but this can be
|
||||
// changed by changing these variables before decoding.
|
||||
//
|
||||
// TODO:
|
||||
// Ideally we'd like to offer people the ability to configure the used timezone
|
||||
// by setting Decoder.Timezone and Encoder.Timezone; however, this is a bit
|
||||
// tricky: the reason we use three different variables for this is to support
|
||||
// round-tripping – without these specific TZ names we wouldn't know which
|
||||
// format to use.
|
||||
//
|
||||
// There isn't a good way to encode this right now though, and passing this sort
|
||||
// of information also ties in to various related issues such as string format
|
||||
// encoding, encoding of comments, etc.
|
||||
//
|
||||
// So, for the time being, just put this in internal until we can write a good
|
||||
// comprehensive API for doing all of this.
|
||||
//
|
||||
// The reason they're exported is because they're referred from in e.g.
|
||||
// internal/tag.
|
||||
//
|
||||
// Note that this behaviour is valid according to the TOML spec as the exact
|
||||
// behaviour is left up to implementations.
|
||||
var (
|
||||
localOffset = func() int { _, o := time.Now().Zone(); return o }()
|
||||
LocalDatetime = time.FixedZone("datetime-local", localOffset)
|
||||
LocalDate = time.FixedZone("date-local", localOffset)
|
||||
LocalTime = time.FixedZone("time-local", localOffset)
|
||||
)
|
||||
1283
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
1283
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
121
vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
Normal file
121
vendor/github.com/BurntSushi/toml/meta.go
generated
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MetaData allows access to meta information about TOML data that's not
|
||||
// accessible otherwise.
|
||||
//
|
||||
// It allows checking if a key is defined in the TOML data, whether any keys
|
||||
// were undecoded, and the TOML type of a key.
|
||||
type MetaData struct {
|
||||
context Key // Used only during decoding.
|
||||
|
||||
keyInfo map[string]keyInfo
|
||||
mapping map[string]interface{}
|
||||
keys []Key
|
||||
decoded map[string]struct{}
|
||||
data []byte // Input file; for errors.
|
||||
}
|
||||
|
||||
// IsDefined reports if the key exists in the TOML data.
|
||||
//
|
||||
// The key should be specified hierarchically, for example to access the TOML
|
||||
// key "a.b.c" you would use IsDefined("a", "b", "c"). Keys are case sensitive.
|
||||
//
|
||||
// Returns false for an empty key.
|
||||
func (md *MetaData) IsDefined(key ...string) bool {
|
||||
if len(key) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
hash map[string]interface{}
|
||||
ok bool
|
||||
hashOrVal interface{} = md.mapping
|
||||
)
|
||||
for _, k := range key {
|
||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
||||
return false
|
||||
}
|
||||
if hashOrVal, ok = hash[k]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Type returns a string representation of the type of the key specified.
|
||||
//
|
||||
// Type will return the empty string if given an empty key or a key that does
|
||||
// not exist. Keys are case sensitive.
|
||||
func (md *MetaData) Type(key ...string) string {
|
||||
if ki, ok := md.keyInfo[Key(key).String()]; ok {
|
||||
return ki.tomlType.typeString()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
||||
//
|
||||
// Each key is itself a slice, where the first element is the top of the
|
||||
// hierarchy and the last is the most specific. The list will have the same
|
||||
// order as the keys appeared in the TOML data.
|
||||
//
|
||||
// All keys returned are non-empty.
|
||||
func (md *MetaData) Keys() []Key {
|
||||
return md.keys
|
||||
}
|
||||
|
||||
// Undecoded returns all keys that have not been decoded in the order in which
|
||||
// they appear in the original TOML document.
|
||||
//
|
||||
// This includes keys that haven't been decoded because of a [Primitive] value.
|
||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
||||
//
|
||||
// Also note that decoding into an empty interface will result in no decoding,
|
||||
// and so no keys will be considered decoded.
|
||||
//
|
||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
||||
// that do not have a concrete type in your representation.
|
||||
func (md *MetaData) Undecoded() []Key {
|
||||
undecoded := make([]Key, 0, len(md.keys))
|
||||
for _, key := range md.keys {
|
||||
if _, ok := md.decoded[key.String()]; !ok {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
}
|
||||
return undecoded
|
||||
}
|
||||
|
||||
// Key represents any TOML key, including key groups. Use [MetaData.Keys] to get
|
||||
// values of this type.
|
||||
type Key []string
|
||||
|
||||
func (k Key) String() string {
|
||||
ss := make([]string, len(k))
|
||||
for i := range k {
|
||||
ss[i] = k.maybeQuoted(i)
|
||||
}
|
||||
return strings.Join(ss, ".")
|
||||
}
|
||||
|
||||
func (k Key) maybeQuoted(i int) string {
|
||||
if k[i] == "" {
|
||||
return `""`
|
||||
}
|
||||
for _, c := range k[i] {
|
||||
if !isBareKeyChar(c, false) {
|
||||
return `"` + dblQuotedReplacer.Replace(k[i]) + `"`
|
||||
}
|
||||
}
|
||||
return k[i]
|
||||
}
|
||||
|
||||
func (k Key) add(piece string) Key {
|
||||
newKey := make(Key, len(k)+1)
|
||||
copy(newKey, k)
|
||||
newKey[len(k)] = piece
|
||||
return newKey
|
||||
}
|
||||
811
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
811
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
Normal file
@@ -0,0 +1,811 @@
|
||||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/BurntSushi/toml/internal"
|
||||
)
|
||||
|
||||
type parser struct {
|
||||
lx *lexer
|
||||
context Key // Full key for the current hash in scope.
|
||||
currentKey string // Base key name for everything except hashes.
|
||||
pos Position // Current position in the TOML file.
|
||||
tomlNext bool
|
||||
|
||||
ordered []Key // List of keys in the order that they appear in the TOML data.
|
||||
|
||||
keyInfo map[string]keyInfo // Map keyname → info about the TOML key.
|
||||
mapping map[string]interface{} // Map keyname → key value.
|
||||
implicits map[string]struct{} // Record implicit keys (e.g. "key.group.names").
|
||||
}
|
||||
|
||||
type keyInfo struct {
|
||||
pos Position
|
||||
tomlType tomlType
|
||||
}
|
||||
|
||||
func parse(data string) (p *parser, err error) {
|
||||
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if pErr, ok := r.(ParseError); ok {
|
||||
pErr.input = data
|
||||
err = pErr
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Read over BOM; do this here as the lexer calls utf8.DecodeRuneInString()
|
||||
// which mangles stuff. UTF-16 BOM isn't strictly valid, but some tools add
|
||||
// it anyway.
|
||||
if strings.HasPrefix(data, "\xff\xfe") || strings.HasPrefix(data, "\xfe\xff") { // UTF-16
|
||||
data = data[2:]
|
||||
} else if strings.HasPrefix(data, "\xef\xbb\xbf") { // UTF-8
|
||||
data = data[3:]
|
||||
}
|
||||
|
||||
// Examine first few bytes for NULL bytes; this probably means it's a UTF-16
|
||||
// file (second byte in surrogate pair being NULL). Again, do this here to
|
||||
// avoid having to deal with UTF-8/16 stuff in the lexer.
|
||||
ex := 6
|
||||
if len(data) < 6 {
|
||||
ex = len(data)
|
||||
}
|
||||
if i := strings.IndexRune(data[:ex], 0); i > -1 {
|
||||
return nil, ParseError{
|
||||
Message: "files cannot contain NULL bytes; probably using UTF-16; TOML files must be UTF-8",
|
||||
Position: Position{Line: 1, Start: i, Len: 1},
|
||||
Line: 1,
|
||||
input: data,
|
||||
}
|
||||
}
|
||||
|
||||
p = &parser{
|
||||
keyInfo: make(map[string]keyInfo),
|
||||
mapping: make(map[string]interface{}),
|
||||
lx: lex(data, tomlNext),
|
||||
ordered: make([]Key, 0),
|
||||
implicits: make(map[string]struct{}),
|
||||
tomlNext: tomlNext,
|
||||
}
|
||||
for {
|
||||
item := p.next()
|
||||
if item.typ == itemEOF {
|
||||
break
|
||||
}
|
||||
p.topLevel(item)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (p *parser) panicErr(it item, err error) {
|
||||
panic(ParseError{
|
||||
err: err,
|
||||
Position: it.pos,
|
||||
Line: it.pos.Len,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) panicItemf(it item, format string, v ...interface{}) {
|
||||
panic(ParseError{
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: it.pos,
|
||||
Line: it.pos.Len,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) panicf(format string, v ...interface{}) {
|
||||
panic(ParseError{
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Position: p.pos,
|
||||
Line: p.pos.Line,
|
||||
LastKey: p.current(),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *parser) next() item {
|
||||
it := p.lx.nextItem()
|
||||
//fmt.Printf("ITEM %-18s line %-3d │ %q\n", it.typ, it.pos.Line, it.val)
|
||||
if it.typ == itemError {
|
||||
if it.err != nil {
|
||||
panic(ParseError{
|
||||
Position: it.pos,
|
||||
Line: it.pos.Line,
|
||||
LastKey: p.current(),
|
||||
err: it.err,
|
||||
})
|
||||
}
|
||||
|
||||
p.panicItemf(it, "%s", it.val)
|
||||
}
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) nextPos() item {
|
||||
it := p.next()
|
||||
p.pos = it.pos
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) bug(format string, v ...interface{}) {
|
||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
||||
}
|
||||
|
||||
func (p *parser) expect(typ itemType) item {
|
||||
it := p.next()
|
||||
p.assertEqual(typ, it.typ)
|
||||
return it
|
||||
}
|
||||
|
||||
func (p *parser) assertEqual(expected, got itemType) {
|
||||
if expected != got {
|
||||
p.bug("Expected '%s' but got '%s'.", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) topLevel(item item) {
|
||||
switch item.typ {
|
||||
case itemCommentStart: // # ..
|
||||
p.expect(itemText)
|
||||
case itemTableStart: // [ .. ]
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; name.typ != itemTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
key = append(key, p.keyString(name))
|
||||
}
|
||||
p.assertEqual(itemTableEnd, name.typ)
|
||||
|
||||
p.addContext(key, false)
|
||||
p.setType("", tomlHash, item.pos)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemArrayTableStart: // [[ .. ]]
|
||||
name := p.nextPos()
|
||||
|
||||
var key Key
|
||||
for ; name.typ != itemArrayTableEnd && name.typ != itemEOF; name = p.next() {
|
||||
key = append(key, p.keyString(name))
|
||||
}
|
||||
p.assertEqual(itemArrayTableEnd, name.typ)
|
||||
|
||||
p.addContext(key, true)
|
||||
p.setType("", tomlArrayHash, item.pos)
|
||||
p.ordered = append(p.ordered, key)
|
||||
case itemKeyStart: // key = ..
|
||||
outerContext := p.context
|
||||
/// Read all the key parts (e.g. 'a' and 'b' in 'a.b')
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
}
|
||||
p.assertEqual(itemKeyEnd, k.typ)
|
||||
|
||||
/// The current key is the last part.
|
||||
p.currentKey = key[len(key)-1]
|
||||
|
||||
/// All the other parts (if any) are the context; need to set each part
|
||||
/// as implicit.
|
||||
context := key[:len(key)-1]
|
||||
for i := range context {
|
||||
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||
}
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
|
||||
/// Set value.
|
||||
vItem := p.next()
|
||||
val, typ := p.value(vItem, false)
|
||||
p.set(p.currentKey, val, typ, vItem.pos)
|
||||
|
||||
/// Remove the context we added (preserving any context from [tbl] lines).
|
||||
p.context = outerContext
|
||||
p.currentKey = ""
|
||||
default:
|
||||
p.bug("Unexpected type at top level: %s", item.typ)
|
||||
}
|
||||
}
|
||||
|
||||
// Gets a string for a key (or part of a key in a table name).
|
||||
func (p *parser) keyString(it item) string {
|
||||
switch it.typ {
|
||||
case itemText:
|
||||
return it.val
|
||||
case itemString, itemMultilineString,
|
||||
itemRawString, itemRawMultilineString:
|
||||
s, _ := p.value(it, false)
|
||||
return s.(string)
|
||||
default:
|
||||
p.bug("Unexpected key type: %s", it.typ)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var datetimeRepl = strings.NewReplacer(
|
||||
"z", "Z",
|
||||
"t", "T",
|
||||
" ", "T")
|
||||
|
||||
// value translates an expected value from the lexer into a Go value wrapped
|
||||
// as an empty interface.
|
||||
func (p *parser) value(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||
switch it.typ {
|
||||
case itemString:
|
||||
return p.replaceEscapes(it, it.val), p.typeOfPrimitive(it)
|
||||
case itemMultilineString:
|
||||
return p.replaceEscapes(it, p.stripEscapedNewlines(stripFirstNewline(it.val))), p.typeOfPrimitive(it)
|
||||
case itemRawString:
|
||||
return it.val, p.typeOfPrimitive(it)
|
||||
case itemRawMultilineString:
|
||||
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
||||
case itemInteger:
|
||||
return p.valueInteger(it)
|
||||
case itemFloat:
|
||||
return p.valueFloat(it)
|
||||
case itemBool:
|
||||
switch it.val {
|
||||
case "true":
|
||||
return true, p.typeOfPrimitive(it)
|
||||
case "false":
|
||||
return false, p.typeOfPrimitive(it)
|
||||
default:
|
||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
||||
}
|
||||
case itemDatetime:
|
||||
return p.valueDatetime(it)
|
||||
case itemArray:
|
||||
return p.valueArray(it)
|
||||
case itemInlineTableStart:
|
||||
return p.valueInlineTable(it, parentIsArray)
|
||||
default:
|
||||
p.bug("Unexpected value type: %s", it.typ)
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (p *parser) valueInteger(it item) (interface{}, tomlType) {
|
||||
if !numUnderscoresOK(it.val) {
|
||||
p.panicItemf(it, "Invalid integer %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
if numHasLeadingZero(it.val) {
|
||||
p.panicItemf(it, "Invalid integer %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
|
||||
num, err := strconv.ParseInt(it.val, 0, 64)
|
||||
if err != nil {
|
||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
||||
// provides an invalid integer, but it's possible that the number is
|
||||
// out of range of valid values (which the lexer cannot determine).
|
||||
// So mark the former as a bug but the latter as a legitimate user
|
||||
// error.
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicErr(it, errParseRange{i: it.val, size: "int64"})
|
||||
} else {
|
||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
func (p *parser) valueFloat(it item) (interface{}, tomlType) {
|
||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
||||
switch r {
|
||||
case '.', 'e', 'E':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
for _, part := range parts {
|
||||
if !numUnderscoresOK(part) {
|
||||
p.panicItemf(it, "Invalid float %q: underscores must be surrounded by digits", it.val)
|
||||
}
|
||||
}
|
||||
if len(parts) > 0 && numHasLeadingZero(parts[0]) {
|
||||
p.panicItemf(it, "Invalid float %q: cannot have leading zeroes", it.val)
|
||||
}
|
||||
if !numPeriodsOK(it.val) {
|
||||
// As a special case, numbers like '123.' or '1.e2',
|
||||
// which are valid as far as Go/strconv are concerned,
|
||||
// must be rejected because TOML says that a fractional
|
||||
// part consists of '.' followed by 1+ digits.
|
||||
p.panicItemf(it, "Invalid float %q: '.' must be followed by one or more digits", it.val)
|
||||
}
|
||||
val := strings.Replace(it.val, "_", "", -1)
|
||||
if val == "+nan" || val == "-nan" { // Go doesn't support this, but TOML spec does.
|
||||
val = "nan"
|
||||
}
|
||||
num, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
if e, ok := err.(*strconv.NumError); ok && e.Err == strconv.ErrRange {
|
||||
p.panicErr(it, errParseRange{i: it.val, size: "float64"})
|
||||
} else {
|
||||
p.panicItemf(it, "Invalid float value: %q", it.val)
|
||||
}
|
||||
}
|
||||
return num, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
var dtTypes = []struct {
|
||||
fmt string
|
||||
zone *time.Location
|
||||
next bool
|
||||
}{
|
||||
{time.RFC3339Nano, time.Local, false},
|
||||
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false},
|
||||
{"2006-01-02", internal.LocalDate, false},
|
||||
{"15:04:05.999999999", internal.LocalTime, false},
|
||||
|
||||
// tomlNext
|
||||
{"2006-01-02T15:04Z07:00", time.Local, true},
|
||||
{"2006-01-02T15:04", internal.LocalDatetime, true},
|
||||
{"15:04", internal.LocalTime, true},
|
||||
}
|
||||
|
||||
func (p *parser) valueDatetime(it item) (interface{}, tomlType) {
|
||||
it.val = datetimeRepl.Replace(it.val)
|
||||
var (
|
||||
t time.Time
|
||||
ok bool
|
||||
err error
|
||||
)
|
||||
for _, dt := range dtTypes {
|
||||
if dt.next && !p.tomlNext {
|
||||
continue
|
||||
}
|
||||
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
|
||||
if err == nil {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
p.panicItemf(it, "Invalid TOML Datetime: %q.", it.val)
|
||||
}
|
||||
return t, p.typeOfPrimitive(it)
|
||||
}
|
||||
|
||||
func (p *parser) valueArray(it item) (interface{}, tomlType) {
|
||||
p.setType(p.currentKey, tomlArray, it.pos)
|
||||
|
||||
var (
|
||||
types []tomlType
|
||||
|
||||
// Initialize to a non-nil empty slice. This makes it consistent with
|
||||
// how S = [] decodes into a non-nil slice inside something like struct
|
||||
// { S []string }. See #338
|
||||
array = []interface{}{}
|
||||
)
|
||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
val, typ := p.value(it, true)
|
||||
array = append(array, val)
|
||||
types = append(types, typ)
|
||||
|
||||
// XXX: types isn't used here, we need it to record the accurate type
|
||||
// information.
|
||||
//
|
||||
// Not entirely sure how to best store this; could use "key[0]",
|
||||
// "key[1]" notation, or maybe store it on the Array type?
|
||||
_ = types
|
||||
}
|
||||
return array, tomlArray
|
||||
}
|
||||
|
||||
func (p *parser) valueInlineTable(it item, parentIsArray bool) (interface{}, tomlType) {
|
||||
var (
|
||||
hash = make(map[string]interface{})
|
||||
outerContext = p.context
|
||||
outerKey = p.currentKey
|
||||
)
|
||||
|
||||
p.context = append(p.context, p.currentKey)
|
||||
prevContext := p.context
|
||||
p.currentKey = ""
|
||||
|
||||
p.addImplicit(p.context)
|
||||
p.addContext(p.context, parentIsArray)
|
||||
|
||||
/// Loop over all table key/value pairs.
|
||||
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
||||
if it.typ == itemCommentStart {
|
||||
p.expect(itemText)
|
||||
continue
|
||||
}
|
||||
|
||||
/// Read all key parts.
|
||||
k := p.nextPos()
|
||||
var key Key
|
||||
for ; k.typ != itemKeyEnd && k.typ != itemEOF; k = p.next() {
|
||||
key = append(key, p.keyString(k))
|
||||
}
|
||||
p.assertEqual(itemKeyEnd, k.typ)
|
||||
|
||||
/// The current key is the last part.
|
||||
p.currentKey = key[len(key)-1]
|
||||
|
||||
/// All the other parts (if any) are the context; need to set each part
|
||||
/// as implicit.
|
||||
context := key[:len(key)-1]
|
||||
for i := range context {
|
||||
p.addImplicitContext(append(p.context, context[i:i+1]...))
|
||||
}
|
||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
||||
|
||||
/// Set the value.
|
||||
val, typ := p.value(p.next(), false)
|
||||
p.set(p.currentKey, val, typ, it.pos)
|
||||
hash[p.currentKey] = val
|
||||
|
||||
/// Restore context.
|
||||
p.context = prevContext
|
||||
}
|
||||
p.context = outerContext
|
||||
p.currentKey = outerKey
|
||||
return hash, tomlHash
|
||||
}
|
||||
|
||||
// numHasLeadingZero checks if this number has leading zeroes, allowing for '0',
|
||||
// +/- signs, and base prefixes.
|
||||
func numHasLeadingZero(s string) bool {
|
||||
if len(s) > 1 && s[0] == '0' && !(s[1] == 'b' || s[1] == 'o' || s[1] == 'x') { // Allow 0b, 0o, 0x
|
||||
return true
|
||||
}
|
||||
if len(s) > 2 && (s[0] == '-' || s[0] == '+') && s[1] == '0' {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
||||
// characters that are not underscores.
|
||||
func numUnderscoresOK(s string) bool {
|
||||
switch s {
|
||||
case "nan", "+nan", "-nan", "inf", "-inf", "+inf":
|
||||
return true
|
||||
}
|
||||
accept := false
|
||||
for _, r := range s {
|
||||
if r == '_' {
|
||||
if !accept {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isHexadecimal is a superset of all the permissable characters
|
||||
// surrounding an underscore.
|
||||
accept = isHexadecimal(r)
|
||||
}
|
||||
return accept
|
||||
}
|
||||
|
||||
// numPeriodsOK checks whether every period in s is followed by a digit.
|
||||
func numPeriodsOK(s string) bool {
|
||||
period := false
|
||||
for _, r := range s {
|
||||
if period && !isDigit(r) {
|
||||
return false
|
||||
}
|
||||
period = r == '.'
|
||||
}
|
||||
return !period
|
||||
}
|
||||
|
||||
// Set the current context of the parser, where the context is either a hash or
|
||||
// an array of hashes, depending on the value of the `array` parameter.
|
||||
//
|
||||
// Establishing the context also makes sure that the key isn't a duplicate, and
|
||||
// will create implicit hashes automatically.
|
||||
func (p *parser) addContext(key Key, array bool) {
|
||||
var ok bool
|
||||
|
||||
// Always start at the top level and drill down for our context.
|
||||
hashContext := p.mapping
|
||||
keyContext := make(Key, 0)
|
||||
|
||||
// We only need implicit hashes for key[0:-1]
|
||||
for _, k := range key[0 : len(key)-1] {
|
||||
_, ok = hashContext[k]
|
||||
keyContext = append(keyContext, k)
|
||||
|
||||
// No key? Make an implicit hash and move on.
|
||||
if !ok {
|
||||
p.addImplicit(keyContext)
|
||||
hashContext[k] = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// If the hash context is actually an array of tables, then set
|
||||
// the hash context to the last element in that array.
|
||||
//
|
||||
// Otherwise, it better be a table, since this MUST be a key group (by
|
||||
// virtue of it not being the last element in a key).
|
||||
switch t := hashContext[k].(type) {
|
||||
case []map[string]interface{}:
|
||||
hashContext = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hashContext = t
|
||||
default:
|
||||
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
||||
}
|
||||
}
|
||||
|
||||
p.context = keyContext
|
||||
if array {
|
||||
// If this is the first element for this array, then allocate a new
|
||||
// list of tables for it.
|
||||
k := key[len(key)-1]
|
||||
if _, ok := hashContext[k]; !ok {
|
||||
hashContext[k] = make([]map[string]interface{}, 0, 4)
|
||||
}
|
||||
|
||||
// Add a new table. But make sure the key hasn't already been used
|
||||
// for something else.
|
||||
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
||||
hashContext[k] = append(hash, make(map[string]interface{}))
|
||||
} else {
|
||||
p.panicf("Key '%s' was already created and cannot be used as an array.", key)
|
||||
}
|
||||
} else {
|
||||
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
||||
}
|
||||
p.context = append(p.context, key[len(key)-1])
|
||||
}
|
||||
|
||||
// set calls setValue and setType.
|
||||
func (p *parser) set(key string, val interface{}, typ tomlType, pos Position) {
|
||||
p.setValue(key, val)
|
||||
p.setType(key, typ, pos)
|
||||
}
|
||||
|
||||
// setValue sets the given key to the given value in the current context.
|
||||
// It will make sure that the key hasn't already been defined, account for
|
||||
// implicit key groups.
|
||||
func (p *parser) setValue(key string, value interface{}) {
|
||||
var (
|
||||
tmpHash interface{}
|
||||
ok bool
|
||||
hash = p.mapping
|
||||
keyContext Key
|
||||
)
|
||||
for _, k := range p.context {
|
||||
keyContext = append(keyContext, k)
|
||||
if tmpHash, ok = hash[k]; !ok {
|
||||
p.bug("Context for key '%s' has not been established.", keyContext)
|
||||
}
|
||||
switch t := tmpHash.(type) {
|
||||
case []map[string]interface{}:
|
||||
// The context is a table of hashes. Pick the most recent table
|
||||
// defined as the current hash.
|
||||
hash = t[len(t)-1]
|
||||
case map[string]interface{}:
|
||||
hash = t
|
||||
default:
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
}
|
||||
keyContext = append(keyContext, key)
|
||||
|
||||
if _, ok := hash[key]; ok {
|
||||
// Normally redefining keys isn't allowed, but the key could have been
|
||||
// defined implicitly and it's allowed to be redefined concretely. (See
|
||||
// the `valid/implicit-and-explicit-after.toml` in toml-test)
|
||||
//
|
||||
// But we have to make sure to stop marking it as an implicit. (So that
|
||||
// another redefinition provokes an error.)
|
||||
//
|
||||
// Note that since it has already been defined (as a hash), we don't
|
||||
// want to overwrite it. So our business is done.
|
||||
if p.isArray(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
hash[key] = value
|
||||
return
|
||||
}
|
||||
if p.isImplicit(keyContext) {
|
||||
p.removeImplicit(keyContext)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we have a concrete key trying to override a previous
|
||||
// key, which is *always* wrong.
|
||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
||||
}
|
||||
|
||||
hash[key] = value
|
||||
}
|
||||
|
||||
// setType sets the type of a particular value at a given key. It should be
|
||||
// called immediately AFTER setValue.
|
||||
//
|
||||
// Note that if `key` is empty, then the type given will be applied to the
|
||||
// current context (which is either a table or an array of tables).
|
||||
func (p *parser) setType(key string, typ tomlType, pos Position) {
|
||||
keyContext := make(Key, 0, len(p.context)+1)
|
||||
keyContext = append(keyContext, p.context...)
|
||||
if len(key) > 0 { // allow type setting for hashes
|
||||
keyContext = append(keyContext, key)
|
||||
}
|
||||
// Special case to make empty keys ("" = 1) work.
|
||||
// Without it it will set "" rather than `""`.
|
||||
// TODO: why is this needed? And why is this only needed here?
|
||||
if len(keyContext) == 0 {
|
||||
keyContext = Key{""}
|
||||
}
|
||||
p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos}
|
||||
}
|
||||
|
||||
// Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and
|
||||
// "[a.b.c]" (the "a", "b", and "c" hashes are never created explicitly).
|
||||
func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struct{}{} }
|
||||
func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) }
|
||||
func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok }
|
||||
func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray }
|
||||
func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) }
|
||||
|
||||
// current returns the full key name of the current context.
|
||||
func (p *parser) current() string {
|
||||
if len(p.currentKey) == 0 {
|
||||
return p.context.String()
|
||||
}
|
||||
if len(p.context) == 0 {
|
||||
return p.currentKey
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
||||
}
|
||||
|
||||
func stripFirstNewline(s string) string {
|
||||
if len(s) > 0 && s[0] == '\n' {
|
||||
return s[1:]
|
||||
}
|
||||
if len(s) > 1 && s[0] == '\r' && s[1] == '\n' {
|
||||
return s[2:]
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// stripEscapedNewlines removes whitespace after line-ending backslashes in
|
||||
// multiline strings.
|
||||
//
|
||||
// A line-ending backslash is an unescaped \ followed only by whitespace until
|
||||
// the next newline. After a line-ending backslash, all whitespace is removed
|
||||
// until the next non-whitespace character.
|
||||
func (p *parser) stripEscapedNewlines(s string) string {
|
||||
var b strings.Builder
|
||||
var i int
|
||||
for {
|
||||
ix := strings.Index(s[i:], `\`)
|
||||
if ix < 0 {
|
||||
b.WriteString(s)
|
||||
return b.String()
|
||||
}
|
||||
i += ix
|
||||
|
||||
if len(s) > i+1 && s[i+1] == '\\' {
|
||||
// Escaped backslash.
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
// Scan until the next non-whitespace.
|
||||
j := i + 1
|
||||
whitespaceLoop:
|
||||
for ; j < len(s); j++ {
|
||||
switch s[j] {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
default:
|
||||
break whitespaceLoop
|
||||
}
|
||||
}
|
||||
if j == i+1 {
|
||||
// Not a whitespace escape.
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(s[i:j], "\n") {
|
||||
// This is not a line-ending backslash.
|
||||
// (It's a bad escape sequence, but we can let
|
||||
// replaceEscapes catch it.)
|
||||
i++
|
||||
continue
|
||||
}
|
||||
b.WriteString(s[:i])
|
||||
s = s[j:]
|
||||
i = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) replaceEscapes(it item, str string) string {
|
||||
replaced := make([]rune, 0, len(str))
|
||||
s := []byte(str)
|
||||
r := 0
|
||||
for r < len(s) {
|
||||
if s[r] != '\\' {
|
||||
c, size := utf8.DecodeRune(s[r:])
|
||||
r += size
|
||||
replaced = append(replaced, c)
|
||||
continue
|
||||
}
|
||||
r += 1
|
||||
if r >= len(s) {
|
||||
p.bug("Escape sequence at end of string.")
|
||||
return ""
|
||||
}
|
||||
switch s[r] {
|
||||
default:
|
||||
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
||||
case ' ', '\t':
|
||||
p.panicItemf(it, "invalid escape: '\\%c'", s[r])
|
||||
case 'b':
|
||||
replaced = append(replaced, rune(0x0008))
|
||||
r += 1
|
||||
case 't':
|
||||
replaced = append(replaced, rune(0x0009))
|
||||
r += 1
|
||||
case 'n':
|
||||
replaced = append(replaced, rune(0x000A))
|
||||
r += 1
|
||||
case 'f':
|
||||
replaced = append(replaced, rune(0x000C))
|
||||
r += 1
|
||||
case 'r':
|
||||
replaced = append(replaced, rune(0x000D))
|
||||
r += 1
|
||||
case 'e':
|
||||
if p.tomlNext {
|
||||
replaced = append(replaced, rune(0x001B))
|
||||
r += 1
|
||||
}
|
||||
case '"':
|
||||
replaced = append(replaced, rune(0x0022))
|
||||
r += 1
|
||||
case '\\':
|
||||
replaced = append(replaced, rune(0x005C))
|
||||
r += 1
|
||||
case 'x':
|
||||
if p.tomlNext {
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+3])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 3
|
||||
}
|
||||
case 'u':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+5])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 5
|
||||
case 'U':
|
||||
// At this point, we know we have a Unicode escape of the form
|
||||
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
||||
// for us.)
|
||||
escaped := p.asciiEscapeToUnicode(it, s[r+1:r+9])
|
||||
replaced = append(replaced, escaped)
|
||||
r += 9
|
||||
}
|
||||
}
|
||||
return string(replaced)
|
||||
}
|
||||
|
||||
func (p *parser) asciiEscapeToUnicode(it item, bs []byte) rune {
|
||||
s := string(bs)
|
||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
||||
if err != nil {
|
||||
p.bug("Could not parse '%s' as a hexadecimal number, but the lexer claims it's OK: %s", s, err)
|
||||
}
|
||||
if !utf8.ValidRune(rune(hex)) {
|
||||
p.panicItemf(it, "Escaped character '\\u%s' is not valid UTF-8.", s)
|
||||
}
|
||||
return rune(hex)
|
||||
}
|
||||
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
Normal file
@@ -0,0 +1,242 @@
|
||||
package toml
|
||||
|
||||
// Struct field handling is adapted from code in encoding/json:
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the Go distribution.
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A field represents a single field found in a struct.
|
||||
type field struct {
|
||||
name string // the name of the field (`toml` tag included)
|
||||
tag bool // whether field has a `toml` tag
|
||||
index []int // represents the depth of an anonymous field
|
||||
typ reflect.Type // the type of the field
|
||||
}
|
||||
|
||||
// byName sorts field by name, breaking ties with depth,
|
||||
// then breaking ties with "name came from toml tag", then
|
||||
// breaking ties with index sequence.
|
||||
type byName []field
|
||||
|
||||
func (x byName) Len() int { return len(x) }
|
||||
|
||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byName) Less(i, j int) bool {
|
||||
if x[i].name != x[j].name {
|
||||
return x[i].name < x[j].name
|
||||
}
|
||||
if len(x[i].index) != len(x[j].index) {
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
if x[i].tag != x[j].tag {
|
||||
return x[i].tag
|
||||
}
|
||||
return byIndex(x).Less(i, j)
|
||||
}
|
||||
|
||||
// byIndex sorts field by index sequence.
|
||||
type byIndex []field
|
||||
|
||||
func (x byIndex) Len() int { return len(x) }
|
||||
|
||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
func (x byIndex) Less(i, j int) bool {
|
||||
for k, xik := range x[i].index {
|
||||
if k >= len(x[j].index) {
|
||||
return false
|
||||
}
|
||||
if xik != x[j].index[k] {
|
||||
return xik < x[j].index[k]
|
||||
}
|
||||
}
|
||||
return len(x[i].index) < len(x[j].index)
|
||||
}
|
||||
|
||||
// typeFields returns a list of fields that TOML should recognize for the given
|
||||
// type. The algorithm is breadth-first search over the set of structs to
|
||||
// include - the top struct and then any reachable anonymous structs.
|
||||
func typeFields(t reflect.Type) []field {
|
||||
// Anonymous fields to explore at the current level and the next.
|
||||
current := []field{}
|
||||
next := []field{{typ: t}}
|
||||
|
||||
// Count of queued names for current level and the next.
|
||||
var count map[reflect.Type]int
|
||||
var nextCount map[reflect.Type]int
|
||||
|
||||
// Types already visited at an earlier level.
|
||||
visited := map[reflect.Type]bool{}
|
||||
|
||||
// Fields found.
|
||||
var fields []field
|
||||
|
||||
for len(next) > 0 {
|
||||
current, next = next, current[:0]
|
||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
||||
|
||||
for _, f := range current {
|
||||
if visited[f.typ] {
|
||||
continue
|
||||
}
|
||||
visited[f.typ] = true
|
||||
|
||||
// Scan f.typ for fields to include.
|
||||
for i := 0; i < f.typ.NumField(); i++ {
|
||||
sf := f.typ.Field(i)
|
||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
||||
continue
|
||||
}
|
||||
opts := getOptions(sf.Tag)
|
||||
if opts.skip {
|
||||
continue
|
||||
}
|
||||
index := make([]int, len(f.index)+1)
|
||||
copy(index, f.index)
|
||||
index[len(f.index)] = i
|
||||
|
||||
ft := sf.Type
|
||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
||||
// Follow pointer.
|
||||
ft = ft.Elem()
|
||||
}
|
||||
|
||||
// Record found field and index sequence.
|
||||
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
||||
tagged := opts.name != ""
|
||||
name := opts.name
|
||||
if name == "" {
|
||||
name = sf.Name
|
||||
}
|
||||
fields = append(fields, field{name, tagged, index, ft})
|
||||
if count[f.typ] > 1 {
|
||||
// If there were multiple instances, add a second,
|
||||
// so that the annihilation code will see a duplicate.
|
||||
// It only cares about the distinction between 1 or 2,
|
||||
// so don't bother generating any more copies.
|
||||
fields = append(fields, fields[len(fields)-1])
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Record new anonymous struct to explore in next round.
|
||||
nextCount[ft]++
|
||||
if nextCount[ft] == 1 {
|
||||
f := field{name: ft.Name(), index: index, typ: ft}
|
||||
next = append(next, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byName(fields))
|
||||
|
||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
||||
// except that fields with TOML tags are promoted.
|
||||
|
||||
// The fields are sorted in primary order of name, secondary order
|
||||
// of field index length. Loop over names; for each name, delete
|
||||
// hidden fields by choosing the one dominant field that survives.
|
||||
out := fields[:0]
|
||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
||||
// One iteration per name.
|
||||
// Find the sequence of fields with the name of this first field.
|
||||
fi := fields[i]
|
||||
name := fi.name
|
||||
for advance = 1; i+advance < len(fields); advance++ {
|
||||
fj := fields[i+advance]
|
||||
if fj.name != name {
|
||||
break
|
||||
}
|
||||
}
|
||||
if advance == 1 { // Only one field with this name
|
||||
out = append(out, fi)
|
||||
continue
|
||||
}
|
||||
dominant, ok := dominantField(fields[i : i+advance])
|
||||
if ok {
|
||||
out = append(out, dominant)
|
||||
}
|
||||
}
|
||||
|
||||
fields = out
|
||||
sort.Sort(byIndex(fields))
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// dominantField looks through the fields, all of which are known to
|
||||
// have the same name, to find the single field that dominates the
|
||||
// others using Go's embedding rules, modified by the presence of
|
||||
// TOML tags. If there are multiple top-level fields, the boolean
|
||||
// will be false: This condition is an error in Go and we skip all
|
||||
// the fields.
|
||||
func dominantField(fields []field) (field, bool) {
|
||||
// The fields are sorted in increasing index-length order. The winner
|
||||
// must therefore be one with the shortest index length. Drop all
|
||||
// longer entries, which is easy: just truncate the slice.
|
||||
length := len(fields[0].index)
|
||||
tagged := -1 // Index of first tagged field.
|
||||
for i, f := range fields {
|
||||
if len(f.index) > length {
|
||||
fields = fields[:i]
|
||||
break
|
||||
}
|
||||
if f.tag {
|
||||
if tagged >= 0 {
|
||||
// Multiple tagged fields at the same level: conflict.
|
||||
// Return no field.
|
||||
return field{}, false
|
||||
}
|
||||
tagged = i
|
||||
}
|
||||
}
|
||||
if tagged >= 0 {
|
||||
return fields[tagged], true
|
||||
}
|
||||
// All remaining fields have the same length. If there's more than one,
|
||||
// we have a conflict (two fields named "X" at the same level) and we
|
||||
// return no field.
|
||||
if len(fields) > 1 {
|
||||
return field{}, false
|
||||
}
|
||||
return fields[0], true
|
||||
}
|
||||
|
||||
var fieldCache struct {
|
||||
sync.RWMutex
|
||||
m map[reflect.Type][]field
|
||||
}
|
||||
|
||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
||||
func cachedTypeFields(t reflect.Type) []field {
|
||||
fieldCache.RLock()
|
||||
f := fieldCache.m[t]
|
||||
fieldCache.RUnlock()
|
||||
if f != nil {
|
||||
return f
|
||||
}
|
||||
|
||||
// Compute fields without lock.
|
||||
// Might duplicate effort but won't hold other computations back.
|
||||
f = typeFields(t)
|
||||
if f == nil {
|
||||
f = []field{}
|
||||
}
|
||||
|
||||
fieldCache.Lock()
|
||||
if fieldCache.m == nil {
|
||||
fieldCache.m = map[reflect.Type][]field{}
|
||||
}
|
||||
fieldCache.m[t] = f
|
||||
fieldCache.Unlock()
|
||||
return f
|
||||
}
|
||||
70
vendor/github.com/BurntSushi/toml/type_toml.go
generated
vendored
Normal file
70
vendor/github.com/BurntSushi/toml/type_toml.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package toml
|
||||
|
||||
// tomlType represents any Go type that corresponds to a TOML type.
|
||||
// While the first draft of the TOML spec has a simplistic type system that
|
||||
// probably doesn't need this level of sophistication, we seem to be militating
|
||||
// toward adding real composite types.
|
||||
type tomlType interface {
|
||||
typeString() string
|
||||
}
|
||||
|
||||
// typeEqual accepts any two types and returns true if they are equal.
|
||||
func typeEqual(t1, t2 tomlType) bool {
|
||||
if t1 == nil || t2 == nil {
|
||||
return false
|
||||
}
|
||||
return t1.typeString() == t2.typeString()
|
||||
}
|
||||
|
||||
func typeIsTable(t tomlType) bool {
|
||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
||||
}
|
||||
|
||||
type tomlBaseType string
|
||||
|
||||
func (btype tomlBaseType) typeString() string {
|
||||
return string(btype)
|
||||
}
|
||||
|
||||
func (btype tomlBaseType) String() string {
|
||||
return btype.typeString()
|
||||
}
|
||||
|
||||
var (
|
||||
tomlInteger tomlBaseType = "Integer"
|
||||
tomlFloat tomlBaseType = "Float"
|
||||
tomlDatetime tomlBaseType = "Datetime"
|
||||
tomlString tomlBaseType = "String"
|
||||
tomlBool tomlBaseType = "Bool"
|
||||
tomlArray tomlBaseType = "Array"
|
||||
tomlHash tomlBaseType = "Hash"
|
||||
tomlArrayHash tomlBaseType = "ArrayHash"
|
||||
)
|
||||
|
||||
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
||||
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
||||
//
|
||||
// Passing a lexer item other than the following will cause a BUG message
|
||||
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
||||
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
||||
switch lexItem.typ {
|
||||
case itemInteger:
|
||||
return tomlInteger
|
||||
case itemFloat:
|
||||
return tomlFloat
|
||||
case itemDatetime:
|
||||
return tomlDatetime
|
||||
case itemString:
|
||||
return tomlString
|
||||
case itemMultilineString:
|
||||
return tomlString
|
||||
case itemRawString:
|
||||
return tomlString
|
||||
case itemRawMultilineString:
|
||||
return tomlString
|
||||
case itemBool:
|
||||
return tomlBool
|
||||
}
|
||||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
||||
panic("unreachable")
|
||||
}
|
||||
29
vendor/github.com/Masterminds/semver/.travis.yml
generated
vendored
Normal file
29
vendor/github.com/Masterminds/semver/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- tip
|
||||
|
||||
# Setting sudo access to false will let Travis CI use containers rather than
|
||||
# VMs to run the tests. For more details see:
|
||||
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
|
||||
sudo: false
|
||||
|
||||
script:
|
||||
- make setup
|
||||
- make test
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: never # options: [always|never|change] default: always
|
||||
109
vendor/github.com/Masterminds/semver/CHANGELOG.md
generated
vendored
Normal file
109
vendor/github.com/Masterminds/semver/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
# 1.5.0 (2019-09-11)
|
||||
|
||||
## Added
|
||||
|
||||
- #103: Add basic fuzzing for `NewVersion()` (thanks @jesse-c)
|
||||
|
||||
## Changed
|
||||
|
||||
- #82: Clarify wildcard meaning in range constraints and update tests for it (thanks @greysteil)
|
||||
- #83: Clarify caret operator range for pre-1.0.0 dependencies (thanks @greysteil)
|
||||
- #72: Adding docs comment pointing to vert for a cli
|
||||
- #71: Update the docs on pre-release comparator handling
|
||||
- #89: Test with new go versions (thanks @thedevsaddam)
|
||||
- #87: Added $ to ValidPrerelease for better validation (thanks @jeremycarroll)
|
||||
|
||||
## Fixed
|
||||
|
||||
- #78: Fix unchecked error in example code (thanks @ravron)
|
||||
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
|
||||
- #97: Fixed copyright file for proper display on GitHub
|
||||
- #107: Fix handling prerelease when sorting alphanum and num
|
||||
- #109: Fixed where Validate sometimes returns wrong message on error
|
||||
|
||||
# 1.4.2 (2018-04-10)
|
||||
|
||||
## Changed
|
||||
- #72: Updated the docs to point to vert for a console appliaction
|
||||
- #71: Update the docs on pre-release comparator handling
|
||||
|
||||
## Fixed
|
||||
- #70: Fix the handling of pre-releases and the 0.0.0 release edge case
|
||||
|
||||
# 1.4.1 (2018-04-02)
|
||||
|
||||
## Fixed
|
||||
- Fixed #64: Fix pre-release precedence issue (thanks @uudashr)
|
||||
|
||||
# 1.4.0 (2017-10-04)
|
||||
|
||||
## Changed
|
||||
- #61: Update NewVersion to parse ints with a 64bit int size (thanks @zknill)
|
||||
|
||||
# 1.3.1 (2017-07-10)
|
||||
|
||||
## Fixed
|
||||
- Fixed #57: number comparisons in prerelease sometimes inaccurate
|
||||
|
||||
# 1.3.0 (2017-05-02)
|
||||
|
||||
## Added
|
||||
- #45: Added json (un)marshaling support (thanks @mh-cbon)
|
||||
- Stability marker. See https://masterminds.github.io/stability/
|
||||
|
||||
## Fixed
|
||||
- #51: Fix handling of single digit tilde constraint (thanks @dgodd)
|
||||
|
||||
## Changed
|
||||
- #55: The godoc icon moved from png to svg
|
||||
|
||||
# 1.2.3 (2017-04-03)
|
||||
|
||||
## Fixed
|
||||
- #46: Fixed 0.x.x and 0.0.x in constraints being treated as *
|
||||
|
||||
# Release 1.2.2 (2016-12-13)
|
||||
|
||||
## Fixed
|
||||
- #34: Fixed issue where hyphen range was not working with pre-release parsing.
|
||||
|
||||
# Release 1.2.1 (2016-11-28)
|
||||
|
||||
## Fixed
|
||||
- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha"
|
||||
properly.
|
||||
|
||||
# Release 1.2.0 (2016-11-04)
|
||||
|
||||
## Added
|
||||
- #20: Added MustParse function for versions (thanks @adamreese)
|
||||
- #15: Added increment methods on versions (thanks @mh-cbon)
|
||||
|
||||
## Fixed
|
||||
- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and
|
||||
might not satisfy the intended compatibility. The change here ignores pre-releases
|
||||
on constraint checks (e.g., ~ or ^) when a pre-release is not part of the
|
||||
constraint. For example, `^1.2.3` will ignore pre-releases while
|
||||
`^1.2.3-alpha` will include them.
|
||||
|
||||
# Release 1.1.1 (2016-06-30)
|
||||
|
||||
## Changed
|
||||
- Issue #9: Speed up version comparison performance (thanks @sdboyer)
|
||||
- Issue #8: Added benchmarks (thanks @sdboyer)
|
||||
- Updated Go Report Card URL to new location
|
||||
- Updated Readme to add code snippet formatting (thanks @mh-cbon)
|
||||
- Updating tagging to v[SemVer] structure for compatibility with other tools.
|
||||
|
||||
# Release 1.1.0 (2016-03-11)
|
||||
|
||||
- Issue #2: Implemented validation to provide reasons a versions failed a
|
||||
constraint.
|
||||
|
||||
# Release 1.0.1 (2015-12-31)
|
||||
|
||||
- Fixed #1: * constraint failing on valid versions.
|
||||
|
||||
# Release 1.0.0 (2015-10-20)
|
||||
|
||||
- Initial release
|
||||
19
vendor/github.com/Masterminds/semver/LICENSE.txt
generated
vendored
Normal file
19
vendor/github.com/Masterminds/semver/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2014-2019, Matt Butcher and Matt Farina
|
||||
|
||||
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.
|
||||
36
vendor/github.com/Masterminds/semver/Makefile
generated
vendored
Normal file
36
vendor/github.com/Masterminds/semver/Makefile
generated
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
.PHONY: setup
|
||||
setup:
|
||||
go get -u gopkg.in/alecthomas/gometalinter.v1
|
||||
gometalinter.v1 --install
|
||||
|
||||
.PHONY: test
|
||||
test: validate lint
|
||||
@echo "==> Running tests"
|
||||
go test -v
|
||||
|
||||
.PHONY: validate
|
||||
validate:
|
||||
@echo "==> Running static validations"
|
||||
@gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable deadcode \
|
||||
--severity deadcode:error \
|
||||
--enable gofmt \
|
||||
--enable gosimple \
|
||||
--enable ineffassign \
|
||||
--enable misspell \
|
||||
--enable vet \
|
||||
--tests \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || exit_code=1
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@echo "==> Running linters"
|
||||
@gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable golint \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || :
|
||||
194
vendor/github.com/Masterminds/semver/README.md
generated
vendored
Normal file
194
vendor/github.com/Masterminds/semver/README.md
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
# SemVer
|
||||
|
||||
The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to:
|
||||
|
||||
* Parse semantic versions
|
||||
* Sort semantic versions
|
||||
* Check if a semantic version fits within a set of constraints
|
||||
* Optionally work with a `v` prefix
|
||||
|
||||
[](https://masterminds.github.io/stability/active.html)
|
||||
[](https://travis-ci.org/Masterminds/semver) [](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [](https://godoc.org/github.com/Masterminds/semver) [](https://goreportcard.com/report/github.com/Masterminds/semver)
|
||||
|
||||
If you are looking for a command line tool for version comparisons please see
|
||||
[vert](https://github.com/Masterminds/vert) which uses this library.
|
||||
|
||||
## Parsing Semantic Versions
|
||||
|
||||
To parse a semantic version use the `NewVersion` function. For example,
|
||||
|
||||
```go
|
||||
v, err := semver.NewVersion("1.2.3-beta.1+build345")
|
||||
```
|
||||
|
||||
If there is an error the version wasn't parseable. The version object has methods
|
||||
to get the parts of the version, compare it to other versions, convert the
|
||||
version back into a string, and get the original string. For more details
|
||||
please see the [documentation](https://godoc.org/github.com/Masterminds/semver).
|
||||
|
||||
## Sorting Semantic Versions
|
||||
|
||||
A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/)
|
||||
package from the standard library. For example,
|
||||
|
||||
```go
|
||||
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||
vs := make([]*semver.Version, len(raw))
|
||||
for i, r := range raw {
|
||||
v, err := semver.NewVersion(r)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
vs[i] = v
|
||||
}
|
||||
|
||||
sort.Sort(semver.Collection(vs))
|
||||
```
|
||||
|
||||
## Checking Version Constraints
|
||||
|
||||
Checking a version against version constraints is one of the most featureful
|
||||
parts of the package.
|
||||
|
||||
```go
|
||||
c, err := semver.NewConstraint(">= 1.2.3")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, _ := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
// Check if the version meets the constraints. The a variable will be true.
|
||||
a := c.Check(v)
|
||||
```
|
||||
|
||||
## Basic Comparisons
|
||||
|
||||
There are two elements to the comparisons. First, a comparison string is a list
|
||||
of comma separated and comparisons. These are then separated by || separated or
|
||||
comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a
|
||||
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||
greater than or equal to 4.2.3.
|
||||
|
||||
The basic comparisons are:
|
||||
|
||||
* `=`: equal (aliased to no operator)
|
||||
* `!=`: not equal
|
||||
* `>`: greater than
|
||||
* `<`: less than
|
||||
* `>=`: greater than or equal to
|
||||
* `<=`: less than or equal to
|
||||
|
||||
## Working With Pre-release Versions
|
||||
|
||||
Pre-releases, for those not familiar with them, are used for software releases
|
||||
prior to stable or generally available releases. Examples of pre-releases include
|
||||
development, alpha, beta, and release candidate releases. A pre-release may be
|
||||
a version such as `1.2.3-beta.1` while the stable release would be `1.2.3`. In the
|
||||
order of precidence, pre-releases come before their associated releases. In this
|
||||
example `1.2.3-beta.1 < 1.2.3`.
|
||||
|
||||
According to the Semantic Version specification pre-releases may not be
|
||||
API compliant with their release counterpart. It says,
|
||||
|
||||
> A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version.
|
||||
|
||||
SemVer comparisons without a pre-release comparator will skip pre-release versions.
|
||||
For example, `>=1.2.3` will skip pre-releases when looking at a list of releases
|
||||
while `>=1.2.3-0` will evaluate and find pre-releases.
|
||||
|
||||
The reason for the `0` as a pre-release version in the example comparison is
|
||||
because pre-releases can only contain ASCII alphanumerics and hyphens (along with
|
||||
`.` separators), per the spec. Sorting happens in ASCII sort order, again per the spec. The lowest character is a `0` in ASCII sort order (see an [ASCII Table](http://www.asciitable.com/))
|
||||
|
||||
Understanding ASCII sort ordering is important because A-Z comes before a-z. That
|
||||
means `>=1.2.3-BETA` will return `1.2.3-alpha`. What you might expect from case
|
||||
sensitivity doesn't apply here. This is due to ASCII sort ordering which is what
|
||||
the spec specifies.
|
||||
|
||||
## Hyphen Range Comparisons
|
||||
|
||||
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||
These look like:
|
||||
|
||||
* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
|
||||
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5`
|
||||
|
||||
## Wildcards In Comparisons
|
||||
|
||||
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||
for all comparison operators. When used on the `=` operator it falls
|
||||
back to the pack level comparison (see tilde below). For example,
|
||||
|
||||
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||
* `<= 2.x` is equivalent to `< 3`
|
||||
* `*` is equivalent to `>= 0.0.0`
|
||||
|
||||
## Tilde Range Comparisons (Patch)
|
||||
|
||||
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||
version is specified and major level changes when the minor number is missing.
|
||||
For example,
|
||||
|
||||
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
|
||||
* `~1` is equivalent to `>= 1, < 2`
|
||||
* `~2.3` is equivalent to `>= 2.3, < 2.4`
|
||||
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `~1.x` is equivalent to `>= 1, < 2`
|
||||
|
||||
## Caret Range Comparisons (Major)
|
||||
|
||||
The caret (`^`) comparison operator is for major level changes. This is useful
|
||||
when comparisons of API versions as a major change is API breaking. For example,
|
||||
|
||||
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||
* `^0.0.1` is equivalent to `>= 0.0.1, < 1.0.0`
|
||||
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||
* `^2.3` is equivalent to `>= 2.3, < 3`
|
||||
* `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||
|
||||
# Validation
|
||||
|
||||
In addition to testing a version against a constraint, a version can be validated
|
||||
against a constraint. When validation fails a slice of errors containing why a
|
||||
version didn't meet the constraint is returned. For example,
|
||||
|
||||
```go
|
||||
c, err := semver.NewConstraint("<= 1.2.3, >= 1.4")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, _ := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
|
||||
// Validate a version against a constraint.
|
||||
a, msgs := c.Validate(v)
|
||||
// a is false
|
||||
for _, m := range msgs {
|
||||
fmt.Println(m)
|
||||
|
||||
// Loops over the errors which would read
|
||||
// "1.3 is greater than 1.2.3"
|
||||
// "1.3 is less than 1.4"
|
||||
}
|
||||
```
|
||||
|
||||
# Fuzzing
|
||||
|
||||
[dvyukov/go-fuzz](https://github.com/dvyukov/go-fuzz) is used for fuzzing.
|
||||
|
||||
1. `go-fuzz-build`
|
||||
2. `go-fuzz -workdir=fuzz`
|
||||
|
||||
# Contribute
|
||||
|
||||
If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues)
|
||||
or [create a pull request](https://github.com/Masterminds/semver/pulls).
|
||||
44
vendor/github.com/Masterminds/semver/appveyor.yml
generated
vendored
Normal file
44
vendor/github.com/Masterminds/semver/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\Masterminds\semver
|
||||
shallow_clone: true
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
install:
|
||||
- go version
|
||||
- go env
|
||||
- go get -u gopkg.in/alecthomas/gometalinter.v1
|
||||
- set PATH=%PATH%;%GOPATH%\bin
|
||||
- gometalinter.v1.exe --install
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- "gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable deadcode \
|
||||
--severity deadcode:error \
|
||||
--enable gofmt \
|
||||
--enable gosimple \
|
||||
--enable ineffassign \
|
||||
--enable misspell \
|
||||
--enable vet \
|
||||
--tests \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || exit_code=1"
|
||||
- "gometalinter.v1 \
|
||||
--disable-all \
|
||||
--enable golint \
|
||||
--vendor \
|
||||
--deadline 60s \
|
||||
./... || :"
|
||||
- go test -v
|
||||
|
||||
deploy: off
|
||||
24
vendor/github.com/Masterminds/semver/collection.go
generated
vendored
Normal file
24
vendor/github.com/Masterminds/semver/collection.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package semver
|
||||
|
||||
// Collection is a collection of Version instances and implements the sort
|
||||
// interface. See the sort package for more details.
|
||||
// https://golang.org/pkg/sort/
|
||||
type Collection []*Version
|
||||
|
||||
// Len returns the length of a collection. The number of Version instances
|
||||
// on the slice.
|
||||
func (c Collection) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
// Less is needed for the sort interface to compare two Version objects on the
|
||||
// slice. If checks if one is less than the other.
|
||||
func (c Collection) Less(i, j int) bool {
|
||||
return c[i].LessThan(c[j])
|
||||
}
|
||||
|
||||
// Swap is needed for the sort interface to replace the Version objects
|
||||
// at two different positions in the slice.
|
||||
func (c Collection) Swap(i, j int) {
|
||||
c[i], c[j] = c[j], c[i]
|
||||
}
|
||||
423
vendor/github.com/Masterminds/semver/constraints.go
generated
vendored
Normal file
423
vendor/github.com/Masterminds/semver/constraints.go
generated
vendored
Normal file
@@ -0,0 +1,423 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Constraints is one or more constraint that a semantic version can be
|
||||
// checked against.
|
||||
type Constraints struct {
|
||||
constraints [][]*constraint
|
||||
}
|
||||
|
||||
// NewConstraint returns a Constraints instance that a Version instance can
|
||||
// be checked against. If there is a parse error it will be returned.
|
||||
func NewConstraint(c string) (*Constraints, error) {
|
||||
|
||||
// Rewrite - ranges into a comparison operation.
|
||||
c = rewriteRange(c)
|
||||
|
||||
ors := strings.Split(c, "||")
|
||||
or := make([][]*constraint, len(ors))
|
||||
for k, v := range ors {
|
||||
cs := strings.Split(v, ",")
|
||||
result := make([]*constraint, len(cs))
|
||||
for i, s := range cs {
|
||||
pc, err := parseConstraint(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result[i] = pc
|
||||
}
|
||||
or[k] = result
|
||||
}
|
||||
|
||||
o := &Constraints{constraints: or}
|
||||
return o, nil
|
||||
}
|
||||
|
||||
// Check tests if a version satisfies the constraints.
|
||||
func (cs Constraints) Check(v *Version) bool {
|
||||
// loop over the ORs and check the inner ANDs
|
||||
for _, o := range cs.constraints {
|
||||
joy := true
|
||||
for _, c := range o {
|
||||
if !c.check(v) {
|
||||
joy = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if joy {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Validate checks if a version satisfies a constraint. If not a slice of
|
||||
// reasons for the failure are returned in addition to a bool.
|
||||
func (cs Constraints) Validate(v *Version) (bool, []error) {
|
||||
// loop over the ORs and check the inner ANDs
|
||||
var e []error
|
||||
|
||||
// Capture the prerelease message only once. When it happens the first time
|
||||
// this var is marked
|
||||
var prerelesase bool
|
||||
for _, o := range cs.constraints {
|
||||
joy := true
|
||||
for _, c := range o {
|
||||
// Before running the check handle the case there the version is
|
||||
// a prerelease and the check is not searching for prereleases.
|
||||
if c.con.pre == "" && v.pre != "" {
|
||||
if !prerelesase {
|
||||
em := fmt.Errorf("%s is a prerelease version and the constraint is only looking for release versions", v)
|
||||
e = append(e, em)
|
||||
prerelesase = true
|
||||
}
|
||||
joy = false
|
||||
|
||||
} else {
|
||||
|
||||
if !c.check(v) {
|
||||
em := fmt.Errorf(c.msg, v, c.orig)
|
||||
e = append(e, em)
|
||||
joy = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if joy {
|
||||
return true, []error{}
|
||||
}
|
||||
}
|
||||
|
||||
return false, e
|
||||
}
|
||||
|
||||
var constraintOps map[string]cfunc
|
||||
var constraintMsg map[string]string
|
||||
var constraintRegex *regexp.Regexp
|
||||
|
||||
func init() {
|
||||
constraintOps = map[string]cfunc{
|
||||
"": constraintTildeOrEqual,
|
||||
"=": constraintTildeOrEqual,
|
||||
"!=": constraintNotEqual,
|
||||
">": constraintGreaterThan,
|
||||
"<": constraintLessThan,
|
||||
">=": constraintGreaterThanEqual,
|
||||
"=>": constraintGreaterThanEqual,
|
||||
"<=": constraintLessThanEqual,
|
||||
"=<": constraintLessThanEqual,
|
||||
"~": constraintTilde,
|
||||
"~>": constraintTilde,
|
||||
"^": constraintCaret,
|
||||
}
|
||||
|
||||
constraintMsg = map[string]string{
|
||||
"": "%s is not equal to %s",
|
||||
"=": "%s is not equal to %s",
|
||||
"!=": "%s is equal to %s",
|
||||
">": "%s is less than or equal to %s",
|
||||
"<": "%s is greater than or equal to %s",
|
||||
">=": "%s is less than %s",
|
||||
"=>": "%s is less than %s",
|
||||
"<=": "%s is greater than %s",
|
||||
"=<": "%s is greater than %s",
|
||||
"~": "%s does not have same major and minor version as %s",
|
||||
"~>": "%s does not have same major and minor version as %s",
|
||||
"^": "%s does not have same major version as %s",
|
||||
}
|
||||
|
||||
ops := make([]string, 0, len(constraintOps))
|
||||
for k := range constraintOps {
|
||||
ops = append(ops, regexp.QuoteMeta(k))
|
||||
}
|
||||
|
||||
constraintRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`^\s*(%s)\s*(%s)\s*$`,
|
||||
strings.Join(ops, "|"),
|
||||
cvRegex))
|
||||
|
||||
constraintRangeRegex = regexp.MustCompile(fmt.Sprintf(
|
||||
`\s*(%s)\s+-\s+(%s)\s*`,
|
||||
cvRegex, cvRegex))
|
||||
}
|
||||
|
||||
// An individual constraint
|
||||
type constraint struct {
|
||||
// The callback function for the restraint. It performs the logic for
|
||||
// the constraint.
|
||||
function cfunc
|
||||
|
||||
msg string
|
||||
|
||||
// The version used in the constraint check. For example, if a constraint
|
||||
// is '<= 2.0.0' the con a version instance representing 2.0.0.
|
||||
con *Version
|
||||
|
||||
// The original parsed version (e.g., 4.x from != 4.x)
|
||||
orig string
|
||||
|
||||
// When an x is used as part of the version (e.g., 1.x)
|
||||
minorDirty bool
|
||||
dirty bool
|
||||
patchDirty bool
|
||||
}
|
||||
|
||||
// Check if a version meets the constraint
|
||||
func (c *constraint) check(v *Version) bool {
|
||||
return c.function(v, c)
|
||||
}
|
||||
|
||||
type cfunc func(v *Version, c *constraint) bool
|
||||
|
||||
func parseConstraint(c string) (*constraint, error) {
|
||||
m := constraintRegex.FindStringSubmatch(c)
|
||||
if m == nil {
|
||||
return nil, fmt.Errorf("improper constraint: %s", c)
|
||||
}
|
||||
|
||||
ver := m[2]
|
||||
orig := ver
|
||||
minorDirty := false
|
||||
patchDirty := false
|
||||
dirty := false
|
||||
if isX(m[3]) {
|
||||
ver = "0.0.0"
|
||||
dirty = true
|
||||
} else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" {
|
||||
minorDirty = true
|
||||
dirty = true
|
||||
ver = fmt.Sprintf("%s.0.0%s", m[3], m[6])
|
||||
} else if isX(strings.TrimPrefix(m[5], ".")) {
|
||||
dirty = true
|
||||
patchDirty = true
|
||||
ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6])
|
||||
}
|
||||
|
||||
con, err := NewVersion(ver)
|
||||
if err != nil {
|
||||
|
||||
// The constraintRegex should catch any regex parsing errors. So,
|
||||
// we should never get here.
|
||||
return nil, errors.New("constraint Parser Error")
|
||||
}
|
||||
|
||||
cs := &constraint{
|
||||
function: constraintOps[m[1]],
|
||||
msg: constraintMsg[m[1]],
|
||||
con: con,
|
||||
orig: orig,
|
||||
minorDirty: minorDirty,
|
||||
patchDirty: patchDirty,
|
||||
dirty: dirty,
|
||||
}
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
// Constraint functions
|
||||
func constraintNotEqual(v *Version, c *constraint) bool {
|
||||
if c.dirty {
|
||||
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.con.Major() != v.Major() {
|
||||
return true
|
||||
}
|
||||
if c.con.Minor() != v.Minor() && !c.minorDirty {
|
||||
return true
|
||||
} else if c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return !v.Equal(c.con)
|
||||
}
|
||||
|
||||
func constraintGreaterThan(v *Version, c *constraint) bool {
|
||||
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Compare(c.con) == 1
|
||||
}
|
||||
|
||||
func constraintLessThan(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !c.dirty {
|
||||
return v.Compare(c.con) < 0
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return false
|
||||
} else if v.Minor() > c.con.Minor() && !c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func constraintGreaterThanEqual(v *Version, c *constraint) bool {
|
||||
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
return v.Compare(c.con) >= 0
|
||||
}
|
||||
|
||||
func constraintLessThanEqual(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if !c.dirty {
|
||||
return v.Compare(c.con) <= 0
|
||||
}
|
||||
|
||||
if v.Major() > c.con.Major() {
|
||||
return false
|
||||
} else if v.Minor() > c.con.Minor() && !c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ~*, ~>* --> >= 0.0.0 (any)
|
||||
// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0
|
||||
// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0
|
||||
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0
|
||||
// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0
|
||||
// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0
|
||||
func constraintTilde(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.LessThan(c.con) {
|
||||
return false
|
||||
}
|
||||
|
||||
// ~0.0.0 is a special case where all constraints are accepted. It's
|
||||
// equivalent to >= 0.0.0.
|
||||
if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 &&
|
||||
!c.minorDirty && !c.patchDirty {
|
||||
return true
|
||||
}
|
||||
|
||||
if v.Major() != c.con.Major() {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Minor() != c.con.Minor() && !c.minorDirty {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// When there is a .x (dirty) status it automatically opts in to ~. Otherwise
|
||||
// it's a straight =
|
||||
func constraintTildeOrEqual(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if c.dirty {
|
||||
c.msg = constraintMsg["~"]
|
||||
return constraintTilde(v, c)
|
||||
}
|
||||
|
||||
return v.Equal(c.con)
|
||||
}
|
||||
|
||||
// ^* --> (any)
|
||||
// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0
|
||||
// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0
|
||||
// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0
|
||||
// ^1.2.3 --> >=1.2.3, <2.0.0
|
||||
// ^1.2.0 --> >=1.2.0, <2.0.0
|
||||
func constraintCaret(v *Version, c *constraint) bool {
|
||||
// If there is a pre-release on the version but the constraint isn't looking
|
||||
// for them assume that pre-releases are not compatible. See issue 21 for
|
||||
// more details.
|
||||
if v.Prerelease() != "" && c.con.Prerelease() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.LessThan(c.con) {
|
||||
return false
|
||||
}
|
||||
|
||||
if v.Major() != c.con.Major() {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
var constraintRangeRegex *regexp.Regexp
|
||||
|
||||
const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` +
|
||||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||
|
||||
func isX(x string) bool {
|
||||
switch x {
|
||||
case "x", "*", "X":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func rewriteRange(i string) string {
|
||||
m := constraintRangeRegex.FindAllStringSubmatch(i, -1)
|
||||
if m == nil {
|
||||
return i
|
||||
}
|
||||
o := i
|
||||
for _, v := range m {
|
||||
t := fmt.Sprintf(">= %s, <= %s", v[1], v[11])
|
||||
o = strings.Replace(o, v[0], t, 1)
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
115
vendor/github.com/Masterminds/semver/doc.go
generated
vendored
Normal file
115
vendor/github.com/Masterminds/semver/doc.go
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go.
|
||||
|
||||
Specifically it provides the ability to:
|
||||
|
||||
* Parse semantic versions
|
||||
* Sort semantic versions
|
||||
* Check if a semantic version fits within a set of constraints
|
||||
* Optionally work with a `v` prefix
|
||||
|
||||
Parsing Semantic Versions
|
||||
|
||||
To parse a semantic version use the `NewVersion` function. For example,
|
||||
|
||||
v, err := semver.NewVersion("1.2.3-beta.1+build345")
|
||||
|
||||
If there is an error the version wasn't parseable. The version object has methods
|
||||
to get the parts of the version, compare it to other versions, convert the
|
||||
version back into a string, and get the original string. For more details
|
||||
please see the documentation at https://godoc.org/github.com/Masterminds/semver.
|
||||
|
||||
Sorting Semantic Versions
|
||||
|
||||
A set of versions can be sorted using the `sort` package from the standard library.
|
||||
For example,
|
||||
|
||||
raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",}
|
||||
vs := make([]*semver.Version, len(raw))
|
||||
for i, r := range raw {
|
||||
v, err := semver.NewVersion(r)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing version: %s", err)
|
||||
}
|
||||
|
||||
vs[i] = v
|
||||
}
|
||||
|
||||
sort.Sort(semver.Collection(vs))
|
||||
|
||||
Checking Version Constraints
|
||||
|
||||
Checking a version against version constraints is one of the most featureful
|
||||
parts of the package.
|
||||
|
||||
c, err := semver.NewConstraint(">= 1.2.3")
|
||||
if err != nil {
|
||||
// Handle constraint not being parseable.
|
||||
}
|
||||
|
||||
v, err := semver.NewVersion("1.3")
|
||||
if err != nil {
|
||||
// Handle version not being parseable.
|
||||
}
|
||||
// Check if the version meets the constraints. The a variable will be true.
|
||||
a := c.Check(v)
|
||||
|
||||
Basic Comparisons
|
||||
|
||||
There are two elements to the comparisons. First, a comparison string is a list
|
||||
of comma separated and comparisons. These are then separated by || separated or
|
||||
comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a
|
||||
comparison that's greater than or equal to 1.2 and less than 3.0.0 or is
|
||||
greater than or equal to 4.2.3.
|
||||
|
||||
The basic comparisons are:
|
||||
|
||||
* `=`: equal (aliased to no operator)
|
||||
* `!=`: not equal
|
||||
* `>`: greater than
|
||||
* `<`: less than
|
||||
* `>=`: greater than or equal to
|
||||
* `<=`: less than or equal to
|
||||
|
||||
Hyphen Range Comparisons
|
||||
|
||||
There are multiple methods to handle ranges and the first is hyphens ranges.
|
||||
These look like:
|
||||
|
||||
* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5`
|
||||
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5`
|
||||
|
||||
Wildcards In Comparisons
|
||||
|
||||
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
|
||||
for all comparison operators. When used on the `=` operator it falls
|
||||
back to the pack level comparison (see tilde below). For example,
|
||||
|
||||
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `>= 1.2.x` is equivalent to `>= 1.2.0`
|
||||
* `<= 2.x` is equivalent to `<= 3`
|
||||
* `*` is equivalent to `>= 0.0.0`
|
||||
|
||||
Tilde Range Comparisons (Patch)
|
||||
|
||||
The tilde (`~`) comparison operator is for patch level ranges when a minor
|
||||
version is specified and major level changes when the minor number is missing.
|
||||
For example,
|
||||
|
||||
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
|
||||
* `~1` is equivalent to `>= 1, < 2`
|
||||
* `~2.3` is equivalent to `>= 2.3, < 2.4`
|
||||
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
|
||||
* `~1.x` is equivalent to `>= 1, < 2`
|
||||
|
||||
Caret Range Comparisons (Major)
|
||||
|
||||
The caret (`^`) comparison operator is for major level changes. This is useful
|
||||
when comparisons of API versions as a major change is API breaking. For example,
|
||||
|
||||
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
|
||||
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
|
||||
* `^2.3` is equivalent to `>= 2.3, < 3`
|
||||
* `^2.x` is equivalent to `>= 2.0.0, < 3`
|
||||
*/
|
||||
package semver
|
||||
425
vendor/github.com/Masterminds/semver/version.go
generated
vendored
Normal file
425
vendor/github.com/Masterminds/semver/version.go
generated
vendored
Normal file
@@ -0,0 +1,425 @@
|
||||
package semver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// The compiled version of the regex created at init() is cached here so it
|
||||
// only needs to be created once.
|
||||
var versionRegex *regexp.Regexp
|
||||
var validPrereleaseRegex *regexp.Regexp
|
||||
|
||||
var (
|
||||
// ErrInvalidSemVer is returned a version is found to be invalid when
|
||||
// being parsed.
|
||||
ErrInvalidSemVer = errors.New("Invalid Semantic Version")
|
||||
|
||||
// ErrInvalidMetadata is returned when the metadata is an invalid format
|
||||
ErrInvalidMetadata = errors.New("Invalid Metadata string")
|
||||
|
||||
// ErrInvalidPrerelease is returned when the pre-release is an invalid format
|
||||
ErrInvalidPrerelease = errors.New("Invalid Prerelease string")
|
||||
)
|
||||
|
||||
// SemVerRegex is the regular expression used to parse a semantic version.
|
||||
const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` +
|
||||
`(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` +
|
||||
`(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?`
|
||||
|
||||
// ValidPrerelease is the regular expression which validates
|
||||
// both prerelease and metadata values.
|
||||
const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)$`
|
||||
|
||||
// Version represents a single semantic version.
|
||||
type Version struct {
|
||||
major, minor, patch int64
|
||||
pre string
|
||||
metadata string
|
||||
original string
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionRegex = regexp.MustCompile("^" + SemVerRegex + "$")
|
||||
validPrereleaseRegex = regexp.MustCompile(ValidPrerelease)
|
||||
}
|
||||
|
||||
// NewVersion parses a given version and returns an instance of Version or
|
||||
// an error if unable to parse the version.
|
||||
func NewVersion(v string) (*Version, error) {
|
||||
m := versionRegex.FindStringSubmatch(v)
|
||||
if m == nil {
|
||||
return nil, ErrInvalidSemVer
|
||||
}
|
||||
|
||||
sv := &Version{
|
||||
metadata: m[8],
|
||||
pre: m[5],
|
||||
original: v,
|
||||
}
|
||||
|
||||
var temp int64
|
||||
temp, err := strconv.ParseInt(m[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||
}
|
||||
sv.major = temp
|
||||
|
||||
if m[2] != "" {
|
||||
temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||
}
|
||||
sv.minor = temp
|
||||
} else {
|
||||
sv.minor = 0
|
||||
}
|
||||
|
||||
if m[3] != "" {
|
||||
temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing version segment: %s", err)
|
||||
}
|
||||
sv.patch = temp
|
||||
} else {
|
||||
sv.patch = 0
|
||||
}
|
||||
|
||||
return sv, nil
|
||||
}
|
||||
|
||||
// MustParse parses a given version and panics on error.
|
||||
func MustParse(v string) *Version {
|
||||
sv, err := NewVersion(v)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return sv
|
||||
}
|
||||
|
||||
// String converts a Version object to a string.
|
||||
// Note, if the original version contained a leading v this version will not.
|
||||
// See the Original() method to retrieve the original value. Semantic Versions
|
||||
// don't contain a leading v per the spec. Instead it's optional on
|
||||
// implementation.
|
||||
func (v *Version) String() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch)
|
||||
if v.pre != "" {
|
||||
fmt.Fprintf(&buf, "-%s", v.pre)
|
||||
}
|
||||
if v.metadata != "" {
|
||||
fmt.Fprintf(&buf, "+%s", v.metadata)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Original returns the original value passed in to be parsed.
|
||||
func (v *Version) Original() string {
|
||||
return v.original
|
||||
}
|
||||
|
||||
// Major returns the major version.
|
||||
func (v *Version) Major() int64 {
|
||||
return v.major
|
||||
}
|
||||
|
||||
// Minor returns the minor version.
|
||||
func (v *Version) Minor() int64 {
|
||||
return v.minor
|
||||
}
|
||||
|
||||
// Patch returns the patch version.
|
||||
func (v *Version) Patch() int64 {
|
||||
return v.patch
|
||||
}
|
||||
|
||||
// Prerelease returns the pre-release version.
|
||||
func (v *Version) Prerelease() string {
|
||||
return v.pre
|
||||
}
|
||||
|
||||
// Metadata returns the metadata on the version.
|
||||
func (v *Version) Metadata() string {
|
||||
return v.metadata
|
||||
}
|
||||
|
||||
// originalVPrefix returns the original 'v' prefix if any.
|
||||
func (v *Version) originalVPrefix() string {
|
||||
|
||||
// Note, only lowercase v is supported as a prefix by the parser.
|
||||
if v.original != "" && v.original[:1] == "v" {
|
||||
return v.original[:1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IncPatch produces the next patch version.
|
||||
// If the current version does not have prerelease/metadata information,
|
||||
// it unsets metadata and prerelease values, increments patch number.
|
||||
// If the current version has any of prerelease or metadata information,
|
||||
// it unsets both values and keeps curent patch value
|
||||
func (v Version) IncPatch() Version {
|
||||
vNext := v
|
||||
// according to http://semver.org/#spec-item-9
|
||||
// Pre-release versions have a lower precedence than the associated normal version.
|
||||
// according to http://semver.org/#spec-item-10
|
||||
// Build metadata SHOULD be ignored when determining version precedence.
|
||||
if v.pre != "" {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
} else {
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = v.patch + 1
|
||||
}
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// IncMinor produces the next minor version.
|
||||
// Sets patch to 0.
|
||||
// Increments minor number.
|
||||
// Unsets metadata.
|
||||
// Unsets prerelease status.
|
||||
func (v Version) IncMinor() Version {
|
||||
vNext := v
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
vNext.minor = v.minor + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// IncMajor produces the next major version.
|
||||
// Sets patch to 0.
|
||||
// Sets minor to 0.
|
||||
// Increments major number.
|
||||
// Unsets metadata.
|
||||
// Unsets prerelease status.
|
||||
func (v Version) IncMajor() Version {
|
||||
vNext := v
|
||||
vNext.metadata = ""
|
||||
vNext.pre = ""
|
||||
vNext.patch = 0
|
||||
vNext.minor = 0
|
||||
vNext.major = v.major + 1
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext
|
||||
}
|
||||
|
||||
// SetPrerelease defines the prerelease value.
|
||||
// Value must not include the required 'hypen' prefix.
|
||||
func (v Version) SetPrerelease(prerelease string) (Version, error) {
|
||||
vNext := v
|
||||
if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) {
|
||||
return vNext, ErrInvalidPrerelease
|
||||
}
|
||||
vNext.pre = prerelease
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext, nil
|
||||
}
|
||||
|
||||
// SetMetadata defines metadata value.
|
||||
// Value must not include the required 'plus' prefix.
|
||||
func (v Version) SetMetadata(metadata string) (Version, error) {
|
||||
vNext := v
|
||||
if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) {
|
||||
return vNext, ErrInvalidMetadata
|
||||
}
|
||||
vNext.metadata = metadata
|
||||
vNext.original = v.originalVPrefix() + "" + vNext.String()
|
||||
return vNext, nil
|
||||
}
|
||||
|
||||
// LessThan tests if one version is less than another one.
|
||||
func (v *Version) LessThan(o *Version) bool {
|
||||
return v.Compare(o) < 0
|
||||
}
|
||||
|
||||
// GreaterThan tests if one version is greater than another one.
|
||||
func (v *Version) GreaterThan(o *Version) bool {
|
||||
return v.Compare(o) > 0
|
||||
}
|
||||
|
||||
// Equal tests if two versions are equal to each other.
|
||||
// Note, versions can be equal with different metadata since metadata
|
||||
// is not considered part of the comparable version.
|
||||
func (v *Version) Equal(o *Version) bool {
|
||||
return v.Compare(o) == 0
|
||||
}
|
||||
|
||||
// Compare compares this version to another one. It returns -1, 0, or 1 if
|
||||
// the version smaller, equal, or larger than the other version.
|
||||
//
|
||||
// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is
|
||||
// lower than the version without a prerelease.
|
||||
func (v *Version) Compare(o *Version) int {
|
||||
// Compare the major, minor, and patch version for differences. If a
|
||||
// difference is found return the comparison.
|
||||
if d := compareSegment(v.Major(), o.Major()); d != 0 {
|
||||
return d
|
||||
}
|
||||
if d := compareSegment(v.Minor(), o.Minor()); d != 0 {
|
||||
return d
|
||||
}
|
||||
if d := compareSegment(v.Patch(), o.Patch()); d != 0 {
|
||||
return d
|
||||
}
|
||||
|
||||
// At this point the major, minor, and patch versions are the same.
|
||||
ps := v.pre
|
||||
po := o.Prerelease()
|
||||
|
||||
if ps == "" && po == "" {
|
||||
return 0
|
||||
}
|
||||
if ps == "" {
|
||||
return 1
|
||||
}
|
||||
if po == "" {
|
||||
return -1
|
||||
}
|
||||
|
||||
return comparePrerelease(ps, po)
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements JSON.Unmarshaler interface.
|
||||
func (v *Version) UnmarshalJSON(b []byte) error {
|
||||
var s string
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return err
|
||||
}
|
||||
temp, err := NewVersion(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.major = temp.major
|
||||
v.minor = temp.minor
|
||||
v.patch = temp.patch
|
||||
v.pre = temp.pre
|
||||
v.metadata = temp.metadata
|
||||
v.original = temp.original
|
||||
temp = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements JSON.Marshaler interface.
|
||||
func (v *Version) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(v.String())
|
||||
}
|
||||
|
||||
func compareSegment(v, o int64) int {
|
||||
if v < o {
|
||||
return -1
|
||||
}
|
||||
if v > o {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func comparePrerelease(v, o string) int {
|
||||
|
||||
// split the prelease versions by their part. The separator, per the spec,
|
||||
// is a .
|
||||
sparts := strings.Split(v, ".")
|
||||
oparts := strings.Split(o, ".")
|
||||
|
||||
// Find the longer length of the parts to know how many loop iterations to
|
||||
// go through.
|
||||
slen := len(sparts)
|
||||
olen := len(oparts)
|
||||
|
||||
l := slen
|
||||
if olen > slen {
|
||||
l = olen
|
||||
}
|
||||
|
||||
// Iterate over each part of the prereleases to compare the differences.
|
||||
for i := 0; i < l; i++ {
|
||||
// Since the lentgh of the parts can be different we need to create
|
||||
// a placeholder. This is to avoid out of bounds issues.
|
||||
stemp := ""
|
||||
if i < slen {
|
||||
stemp = sparts[i]
|
||||
}
|
||||
|
||||
otemp := ""
|
||||
if i < olen {
|
||||
otemp = oparts[i]
|
||||
}
|
||||
|
||||
d := comparePrePart(stemp, otemp)
|
||||
if d != 0 {
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
||||
// Reaching here means two versions are of equal value but have different
|
||||
// metadata (the part following a +). They are not identical in string form
|
||||
// but the version comparison finds them to be equal.
|
||||
return 0
|
||||
}
|
||||
|
||||
func comparePrePart(s, o string) int {
|
||||
// Fastpath if they are equal
|
||||
if s == o {
|
||||
return 0
|
||||
}
|
||||
|
||||
// When s or o are empty we can use the other in an attempt to determine
|
||||
// the response.
|
||||
if s == "" {
|
||||
if o != "" {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
if o == "" {
|
||||
if s != "" {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// When comparing strings "99" is greater than "103". To handle
|
||||
// cases like this we need to detect numbers and compare them. According
|
||||
// to the semver spec, numbers are always positive. If there is a - at the
|
||||
// start like -99 this is to be evaluated as an alphanum. numbers always
|
||||
// have precedence over alphanum. Parsing as Uints because negative numbers
|
||||
// are ignored.
|
||||
|
||||
oi, n1 := strconv.ParseUint(o, 10, 64)
|
||||
si, n2 := strconv.ParseUint(s, 10, 64)
|
||||
|
||||
// The case where both are strings compare the strings
|
||||
if n1 != nil && n2 != nil {
|
||||
if s > o {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
} else if n1 != nil {
|
||||
// o is a string and s is a number
|
||||
return -1
|
||||
} else if n2 != nil {
|
||||
// s is a string and o is a number
|
||||
return 1
|
||||
}
|
||||
// Both are numbers
|
||||
if si > oi {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
|
||||
}
|
||||
10
vendor/github.com/Masterminds/semver/version_fuzz.go
generated
vendored
Normal file
10
vendor/github.com/Masterminds/semver/version_fuzz.go
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
// +build gofuzz
|
||||
|
||||
package semver
|
||||
|
||||
func Fuzz(data []byte) int {
|
||||
if _, err := NewVersion(string(data)); err != nil {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
2
vendor/github.com/Masterminds/sprig/.gitignore
generated
vendored
Normal file
2
vendor/github.com/Masterminds/sprig/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
vendor/
|
||||
/.glide
|
||||
24
vendor/github.com/Masterminds/sprig/.travis.yml
generated
vendored
Normal file
24
vendor/github.com/Masterminds/sprig/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- tip
|
||||
|
||||
# Setting sudo access to false will let Travis CI use containers rather than
|
||||
# VMs to run the tests. For more details see:
|
||||
# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
# - http://docs.travis-ci.com/user/workers/standard-infrastructure/
|
||||
sudo: false
|
||||
|
||||
script:
|
||||
- make setup test
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/06e3328629952dabe3e0
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: never # options: [always|never|change] default: always
|
||||
153
vendor/github.com/Masterminds/sprig/CHANGELOG.md
generated
vendored
Normal file
153
vendor/github.com/Masterminds/sprig/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,153 @@
|
||||
# Changelog
|
||||
|
||||
## Release 2.15.0 (2018-04-02)
|
||||
|
||||
### Added
|
||||
|
||||
- #68 and #69: Add json helpers to docs (thanks @arunvelsriram)
|
||||
- #66: Add ternary function (thanks @binoculars)
|
||||
- #67: Allow keys function to take multiple dicts (thanks @binoculars)
|
||||
- #89: Added sha1sum to crypto function (thanks @benkeil)
|
||||
- #81: Allow customizing Root CA that used by genSignedCert (thanks @chenzhiwei)
|
||||
- #92: Add travis testing for go 1.10
|
||||
- #93: Adding appveyor config for windows testing
|
||||
|
||||
### Changed
|
||||
|
||||
- #90: Updating to more recent dependencies
|
||||
- #73: replace satori/go.uuid with google/uuid (thanks @petterw)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #76: Fixed documentation typos (thanks @Thiht)
|
||||
- Fixed rounding issue on the `ago` function. Note, the removes support for Go 1.8 and older
|
||||
|
||||
## Release 2.14.1 (2017-12-01)
|
||||
|
||||
### Fixed
|
||||
|
||||
- #60: Fix typo in function name documentation (thanks @neil-ca-moore)
|
||||
- #61: Removing line with {{ due to blocking github pages genertion
|
||||
- #64: Update the list functions to handle int, string, and other slices for compatibility
|
||||
|
||||
## Release 2.14.0 (2017-10-06)
|
||||
|
||||
This new version of Sprig adds a set of functions for generating and working with SSL certificates.
|
||||
|
||||
- `genCA` generates an SSL Certificate Authority
|
||||
- `genSelfSignedCert` generates an SSL self-signed certificate
|
||||
- `genSignedCert` generates an SSL certificate and key based on a given CA
|
||||
|
||||
## Release 2.13.0 (2017-09-18)
|
||||
|
||||
This release adds new functions, including:
|
||||
|
||||
- `regexMatch`, `regexFindAll`, `regexFind`, `regexReplaceAll`, `regexReplaceAllLiteral`, and `regexSplit` to work with regular expressions
|
||||
- `floor`, `ceil`, and `round` math functions
|
||||
- `toDate` converts a string to a date
|
||||
- `nindent` is just like `indent` but also prepends a new line
|
||||
- `ago` returns the time from `time.Now`
|
||||
|
||||
### Added
|
||||
|
||||
- #40: Added basic regex functionality (thanks @alanquillin)
|
||||
- #41: Added ceil floor and round functions (thanks @alanquillin)
|
||||
- #48: Added toDate function (thanks @andreynering)
|
||||
- #50: Added nindent function (thanks @binoculars)
|
||||
- #46: Added ago function (thanks @slayer)
|
||||
|
||||
### Changed
|
||||
|
||||
- #51: Updated godocs to include new string functions (thanks @curtisallen)
|
||||
- #49: Added ability to merge multiple dicts (thanks @binoculars)
|
||||
|
||||
## Release 2.12.0 (2017-05-17)
|
||||
|
||||
- `snakecase`, `camelcase`, and `shuffle` are three new string functions
|
||||
- `fail` allows you to bail out of a template render when conditions are not met
|
||||
|
||||
## Release 2.11.0 (2017-05-02)
|
||||
|
||||
- Added `toJson` and `toPrettyJson`
|
||||
- Added `merge`
|
||||
- Refactored documentation
|
||||
|
||||
## Release 2.10.0 (2017-03-15)
|
||||
|
||||
- Added `semver` and `semverCompare` for Semantic Versions
|
||||
- `list` replaces `tuple`
|
||||
- Fixed issue with `join`
|
||||
- Added `first`, `last`, `intial`, `rest`, `prepend`, `append`, `toString`, `toStrings`, `sortAlpha`, `reverse`, `coalesce`, `pluck`, `pick`, `compact`, `keys`, `omit`, `uniq`, `has`, `without`
|
||||
|
||||
## Release 2.9.0 (2017-02-23)
|
||||
|
||||
- Added `splitList` to split a list
|
||||
- Added crypto functions of `genPrivateKey` and `derivePassword`
|
||||
|
||||
## Release 2.8.0 (2016-12-21)
|
||||
|
||||
- Added access to several path functions (`base`, `dir`, `clean`, `ext`, and `abs`)
|
||||
- Added functions for _mutating_ dictionaries (`set`, `unset`, `hasKey`)
|
||||
|
||||
## Release 2.7.0 (2016-12-01)
|
||||
|
||||
- Added `sha256sum` to generate a hash of an input
|
||||
- Added functions to convert a numeric or string to `int`, `int64`, `float64`
|
||||
|
||||
## Release 2.6.0 (2016-10-03)
|
||||
|
||||
- Added a `uuidv4` template function for generating UUIDs inside of a template.
|
||||
|
||||
## Release 2.5.0 (2016-08-19)
|
||||
|
||||
- New `trimSuffix`, `trimPrefix`, `hasSuffix`, and `hasPrefix` functions
|
||||
- New aliases have been added for a few functions that didn't follow the naming conventions (`trimAll` and `abbrevBoth`)
|
||||
- `trimall` and `abbrevboth` (notice the case) are deprecated and will be removed in 3.0.0
|
||||
|
||||
## Release 2.4.0 (2016-08-16)
|
||||
|
||||
- Adds two functions: `until` and `untilStep`
|
||||
|
||||
## Release 2.3.0 (2016-06-21)
|
||||
|
||||
- cat: Concatenate strings with whitespace separators.
|
||||
- replace: Replace parts of a string: `replace " " "-" "Me First"` renders "Me-First"
|
||||
- plural: Format plurals: `len "foo" | plural "one foo" "many foos"` renders "many foos"
|
||||
- indent: Indent blocks of text in a way that is sensitive to "\n" characters.
|
||||
|
||||
## Release 2.2.0 (2016-04-21)
|
||||
|
||||
- Added a `genPrivateKey` function (Thanks @bacongobbler)
|
||||
|
||||
## Release 2.1.0 (2016-03-30)
|
||||
|
||||
- `default` now prints the default value when it does not receive a value down the pipeline. It is much safer now to do `{{.Foo | default "bar"}}`.
|
||||
- Added accessors for "hermetic" functions. These return only functions that, when given the same input, produce the same output.
|
||||
|
||||
## Release 2.0.0 (2016-03-29)
|
||||
|
||||
Because we switched from `int` to `int64` as the return value for all integer math functions, the library's major version number has been incremented.
|
||||
|
||||
- `min` complements `max` (formerly `biggest`)
|
||||
- `empty` indicates that a value is the empty value for its type
|
||||
- `tuple` creates a tuple inside of a template: `{{$t := tuple "a", "b" "c"}}`
|
||||
- `dict` creates a dictionary inside of a template `{{$d := dict "key1" "val1" "key2" "val2"}}`
|
||||
- Date formatters have been added for HTML dates (as used in `date` input fields)
|
||||
- Integer math functions can convert from a number of types, including `string` (via `strconv.ParseInt`).
|
||||
|
||||
## Release 1.2.0 (2016-02-01)
|
||||
|
||||
- Added quote and squote
|
||||
- Added b32enc and b32dec
|
||||
- add now takes varargs
|
||||
- biggest now takes varargs
|
||||
|
||||
## Release 1.1.0 (2015-12-29)
|
||||
|
||||
- Added #4: Added contains function. strings.Contains, but with the arguments
|
||||
switched to simplify common pipelines. (thanks krancour)
|
||||
- Added Travis-CI testing support
|
||||
|
||||
## Release 1.0.0 (2015-12-23)
|
||||
|
||||
- Initial release
|
||||
20
vendor/github.com/Masterminds/sprig/LICENSE.txt
generated
vendored
Normal file
20
vendor/github.com/Masterminds/sprig/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Sprig
|
||||
Copyright (C) 2013 Masterminds
|
||||
|
||||
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.
|
||||
13
vendor/github.com/Masterminds/sprig/Makefile
generated
vendored
Normal file
13
vendor/github.com/Masterminds/sprig/Makefile
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
HAS_GLIDE := $(shell command -v glide;)
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -v .
|
||||
|
||||
.PHONY: setup
|
||||
setup:
|
||||
ifndef HAS_GLIDE
|
||||
go get -u github.com/Masterminds/glide
|
||||
endif
|
||||
glide install
|
||||
81
vendor/github.com/Masterminds/sprig/README.md
generated
vendored
Normal file
81
vendor/github.com/Masterminds/sprig/README.md
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
# Sprig: Template functions for Go templates
|
||||
[](https://masterminds.github.io/stability/sustained.html)
|
||||
[](https://travis-ci.org/Masterminds/sprig)
|
||||
|
||||
The Go language comes with a [built-in template
|
||||
language](http://golang.org/pkg/text/template/), but not
|
||||
very many template functions. This library provides a group of commonly
|
||||
used template functions.
|
||||
|
||||
It is inspired by the template functions found in
|
||||
[Twig](http://twig.sensiolabs.org/documentation) and also in various
|
||||
JavaScript libraries, such as [underscore.js](http://underscorejs.org/).
|
||||
|
||||
## Usage
|
||||
|
||||
Template developers can read the [Sprig function documentation](http://masterminds.github.io/sprig/) to
|
||||
learn about the >100 template functions available.
|
||||
|
||||
For Go developers wishing to include Sprig as a library in their programs,
|
||||
API documentation is available [at GoDoc.org](http://godoc.org/github.com/Masterminds/sprig), but
|
||||
read on for standard usage.
|
||||
|
||||
### Load the Sprig library
|
||||
|
||||
To load the Sprig `FuncMap`:
|
||||
|
||||
```go
|
||||
|
||||
import (
|
||||
"github.com/Masterminds/sprig"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// This example illustrates that the FuncMap *must* be set before the
|
||||
// templates themselves are loaded.
|
||||
tpl := template.Must(
|
||||
template.New("base").Funcs(sprig.FuncMap()).ParseGlob("*.html")
|
||||
)
|
||||
|
||||
|
||||
```
|
||||
|
||||
### Call the functions inside of templates
|
||||
|
||||
By convention, all functions are lowercase. This seems to follow the Go
|
||||
idiom for template functions (as opposed to template methods, which are
|
||||
TitleCase).
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
{{ "hello!" | upper | repeat 5 }}
|
||||
```
|
||||
|
||||
Produces:
|
||||
|
||||
```
|
||||
HELLO!HELLO!HELLO!HELLO!HELLO!
|
||||
```
|
||||
|
||||
## Principles:
|
||||
|
||||
The following principles were used in deciding on which functions to add, and
|
||||
determining how to implement them.
|
||||
|
||||
- Template functions should be used to build layout. Therefore, the following
|
||||
types of operations are within the domain of template functions:
|
||||
- Formatting
|
||||
- Layout
|
||||
- Simple type conversions
|
||||
- Utilities that assist in handling common formatting and layout needs (e.g. arithmetic)
|
||||
- Template functions should not return errors unless there is no way to print
|
||||
a sensible value. For example, converting a string to an integer should not
|
||||
produce an error if conversion fails. Instead, it should display a default
|
||||
value that can be displayed.
|
||||
- Simple math is necessary for grid layouts, pagers, and so on. Complex math
|
||||
(anything other than arithmetic) should be done outside of templates.
|
||||
- Template functions only deal with the data passed into them. They never retrieve
|
||||
data from a source.
|
||||
- Finally, do not override core Go template functions.
|
||||
26
vendor/github.com/Masterminds/sprig/appveyor.yml
generated
vendored
Normal file
26
vendor/github.com/Masterminds/sprig/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\Masterminds\sprig
|
||||
shallow_clone: true
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
install:
|
||||
- go get -u github.com/Masterminds/glide
|
||||
- set PATH=%GOPATH%\bin;%PATH%
|
||||
- go version
|
||||
- go env
|
||||
|
||||
build_script:
|
||||
- glide install
|
||||
- go install ./...
|
||||
|
||||
test_script:
|
||||
- go test -v
|
||||
|
||||
deploy: off
|
||||
435
vendor/github.com/Masterminds/sprig/crypto.go
generated
vendored
Normal file
435
vendor/github.com/Masterminds/sprig/crypto.go
generated
vendored
Normal file
@@ -0,0 +1,435 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/dsa"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
func sha256sum(input string) string {
|
||||
hash := sha256.Sum256([]byte(input))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func sha1sum(input string) string {
|
||||
hash := sha1.Sum([]byte(input))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// uuidv4 provides a safe and secure UUID v4 implementation
|
||||
func uuidv4() string {
|
||||
return fmt.Sprintf("%s", uuid.New())
|
||||
}
|
||||
|
||||
var master_password_seed = "com.lyndir.masterpassword"
|
||||
|
||||
var password_type_templates = map[string][][]byte{
|
||||
"maximum": {[]byte("anoxxxxxxxxxxxxxxxxx"), []byte("axxxxxxxxxxxxxxxxxno")},
|
||||
"long": {[]byte("CvcvnoCvcvCvcv"), []byte("CvcvCvcvnoCvcv"), []byte("CvcvCvcvCvcvno"), []byte("CvccnoCvcvCvcv"), []byte("CvccCvcvnoCvcv"),
|
||||
[]byte("CvccCvcvCvcvno"), []byte("CvcvnoCvccCvcv"), []byte("CvcvCvccnoCvcv"), []byte("CvcvCvccCvcvno"), []byte("CvcvnoCvcvCvcc"),
|
||||
[]byte("CvcvCvcvnoCvcc"), []byte("CvcvCvcvCvccno"), []byte("CvccnoCvccCvcv"), []byte("CvccCvccnoCvcv"), []byte("CvccCvccCvcvno"),
|
||||
[]byte("CvcvnoCvccCvcc"), []byte("CvcvCvccnoCvcc"), []byte("CvcvCvccCvccno"), []byte("CvccnoCvcvCvcc"), []byte("CvccCvcvnoCvcc"),
|
||||
[]byte("CvccCvcvCvccno")},
|
||||
"medium": {[]byte("CvcnoCvc"), []byte("CvcCvcno")},
|
||||
"short": {[]byte("Cvcn")},
|
||||
"basic": {[]byte("aaanaaan"), []byte("aannaaan"), []byte("aaannaaa")},
|
||||
"pin": {[]byte("nnnn")},
|
||||
}
|
||||
|
||||
var template_characters = map[byte]string{
|
||||
'V': "AEIOU",
|
||||
'C': "BCDFGHJKLMNPQRSTVWXYZ",
|
||||
'v': "aeiou",
|
||||
'c': "bcdfghjklmnpqrstvwxyz",
|
||||
'A': "AEIOUBCDFGHJKLMNPQRSTVWXYZ",
|
||||
'a': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz",
|
||||
'n': "0123456789",
|
||||
'o': "@&%?,=[]_:-+*$#!'^~;()/.",
|
||||
'x': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()",
|
||||
}
|
||||
|
||||
func derivePassword(counter uint32, password_type, password, user, site string) string {
|
||||
var templates = password_type_templates[password_type]
|
||||
if templates == nil {
|
||||
return fmt.Sprintf("cannot find password template %s", password_type)
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
buffer.WriteString(master_password_seed)
|
||||
binary.Write(&buffer, binary.BigEndian, uint32(len(user)))
|
||||
buffer.WriteString(user)
|
||||
|
||||
salt := buffer.Bytes()
|
||||
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 2, 64)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to derive password: %s", err)
|
||||
}
|
||||
|
||||
buffer.Truncate(len(master_password_seed))
|
||||
binary.Write(&buffer, binary.BigEndian, uint32(len(site)))
|
||||
buffer.WriteString(site)
|
||||
binary.Write(&buffer, binary.BigEndian, counter)
|
||||
|
||||
var hmacv = hmac.New(sha256.New, key)
|
||||
hmacv.Write(buffer.Bytes())
|
||||
var seed = hmacv.Sum(nil)
|
||||
var temp = templates[int(seed[0])%len(templates)]
|
||||
|
||||
buffer.Truncate(0)
|
||||
for i, element := range temp {
|
||||
pass_chars := template_characters[element]
|
||||
pass_char := pass_chars[int(seed[i+1])%len(pass_chars)]
|
||||
buffer.WriteByte(pass_char)
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func generatePrivateKey(typ string) string {
|
||||
var priv interface{}
|
||||
var err error
|
||||
switch typ {
|
||||
case "", "rsa":
|
||||
// good enough for government work
|
||||
priv, err = rsa.GenerateKey(rand.Reader, 4096)
|
||||
case "dsa":
|
||||
key := new(dsa.PrivateKey)
|
||||
// again, good enough for government work
|
||||
if err = dsa.GenerateParameters(&key.Parameters, rand.Reader, dsa.L2048N256); err != nil {
|
||||
return fmt.Sprintf("failed to generate dsa params: %s", err)
|
||||
}
|
||||
err = dsa.GenerateKey(key, rand.Reader)
|
||||
priv = key
|
||||
case "ecdsa":
|
||||
// again, good enough for government work
|
||||
priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
default:
|
||||
return "Unknown type " + typ
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to generate private key: %s", err)
|
||||
}
|
||||
|
||||
return string(pem.EncodeToMemory(pemBlockForKey(priv)))
|
||||
}
|
||||
|
||||
type DSAKeyFormat struct {
|
||||
Version int
|
||||
P, Q, G, Y, X *big.Int
|
||||
}
|
||||
|
||||
func pemBlockForKey(priv interface{}) *pem.Block {
|
||||
switch k := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
||||
case *dsa.PrivateKey:
|
||||
val := DSAKeyFormat{
|
||||
P: k.P, Q: k.Q, G: k.G,
|
||||
Y: k.Y, X: k.X,
|
||||
}
|
||||
bytes, _ := asn1.Marshal(val)
|
||||
return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes}
|
||||
case *ecdsa.PrivateKey:
|
||||
b, _ := x509.MarshalECPrivateKey(k)
|
||||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type certificate struct {
|
||||
Cert string
|
||||
Key string
|
||||
}
|
||||
|
||||
func buildCustomCertificate(b64cert string, b64key string) (certificate, error) {
|
||||
crt := certificate{}
|
||||
|
||||
cert, err := base64.StdEncoding.DecodeString(b64cert)
|
||||
if err != nil {
|
||||
return crt, errors.New("unable to decode base64 certificate")
|
||||
}
|
||||
|
||||
key, err := base64.StdEncoding.DecodeString(b64key)
|
||||
if err != nil {
|
||||
return crt, errors.New("unable to decode base64 private key")
|
||||
}
|
||||
|
||||
decodedCert, _ := pem.Decode(cert)
|
||||
if decodedCert == nil {
|
||||
return crt, errors.New("unable to decode certificate")
|
||||
}
|
||||
_, err = x509.ParseCertificate(decodedCert.Bytes)
|
||||
if err != nil {
|
||||
return crt, fmt.Errorf(
|
||||
"error parsing certificate: decodedCert.Bytes: %s",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
decodedKey, _ := pem.Decode(key)
|
||||
if decodedKey == nil {
|
||||
return crt, errors.New("unable to decode key")
|
||||
}
|
||||
_, err = x509.ParsePKCS1PrivateKey(decodedKey.Bytes)
|
||||
if err != nil {
|
||||
return crt, fmt.Errorf(
|
||||
"error parsing prive key: decodedKey.Bytes: %s",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
crt.Cert = string(cert)
|
||||
crt.Key = string(key)
|
||||
|
||||
return crt, nil
|
||||
}
|
||||
|
||||
func generateCertificateAuthority(
|
||||
cn string,
|
||||
daysValid int,
|
||||
) (certificate, error) {
|
||||
ca := certificate{}
|
||||
|
||||
template, err := getBaseCertTemplate(cn, nil, nil, daysValid)
|
||||
if err != nil {
|
||||
return ca, err
|
||||
}
|
||||
// Override KeyUsage and IsCA
|
||||
template.KeyUsage = x509.KeyUsageKeyEncipherment |
|
||||
x509.KeyUsageDigitalSignature |
|
||||
x509.KeyUsageCertSign
|
||||
template.IsCA = true
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return ca, fmt.Errorf("error generating rsa key: %s", err)
|
||||
}
|
||||
|
||||
ca.Cert, ca.Key, err = getCertAndKey(template, priv, template, priv)
|
||||
if err != nil {
|
||||
return ca, err
|
||||
}
|
||||
|
||||
return ca, nil
|
||||
}
|
||||
|
||||
func generateSelfSignedCertificate(
|
||||
cn string,
|
||||
ips []interface{},
|
||||
alternateDNS []interface{},
|
||||
daysValid int,
|
||||
) (certificate, error) {
|
||||
cert := certificate{}
|
||||
|
||||
template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return cert, fmt.Errorf("error generating rsa key: %s", err)
|
||||
}
|
||||
|
||||
cert.Cert, cert.Key, err = getCertAndKey(template, priv, template, priv)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func generateSignedCertificate(
|
||||
cn string,
|
||||
ips []interface{},
|
||||
alternateDNS []interface{},
|
||||
daysValid int,
|
||||
ca certificate,
|
||||
) (certificate, error) {
|
||||
cert := certificate{}
|
||||
|
||||
decodedSignerCert, _ := pem.Decode([]byte(ca.Cert))
|
||||
if decodedSignerCert == nil {
|
||||
return cert, errors.New("unable to decode certificate")
|
||||
}
|
||||
signerCert, err := x509.ParseCertificate(decodedSignerCert.Bytes)
|
||||
if err != nil {
|
||||
return cert, fmt.Errorf(
|
||||
"error parsing certificate: decodedSignerCert.Bytes: %s",
|
||||
err,
|
||||
)
|
||||
}
|
||||
decodedSignerKey, _ := pem.Decode([]byte(ca.Key))
|
||||
if decodedSignerKey == nil {
|
||||
return cert, errors.New("unable to decode key")
|
||||
}
|
||||
signerKey, err := x509.ParsePKCS1PrivateKey(decodedSignerKey.Bytes)
|
||||
if err != nil {
|
||||
return cert, fmt.Errorf(
|
||||
"error parsing prive key: decodedSignerKey.Bytes: %s",
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
template, err := getBaseCertTemplate(cn, ips, alternateDNS, daysValid)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return cert, fmt.Errorf("error generating rsa key: %s", err)
|
||||
}
|
||||
|
||||
cert.Cert, cert.Key, err = getCertAndKey(
|
||||
template,
|
||||
priv,
|
||||
signerCert,
|
||||
signerKey,
|
||||
)
|
||||
if err != nil {
|
||||
return cert, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
func getCertAndKey(
|
||||
template *x509.Certificate,
|
||||
signeeKey *rsa.PrivateKey,
|
||||
parent *x509.Certificate,
|
||||
signingKey *rsa.PrivateKey,
|
||||
) (string, string, error) {
|
||||
derBytes, err := x509.CreateCertificate(
|
||||
rand.Reader,
|
||||
template,
|
||||
parent,
|
||||
&signeeKey.PublicKey,
|
||||
signingKey,
|
||||
)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("error creating certificate: %s", err)
|
||||
}
|
||||
|
||||
certBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(
|
||||
&certBuffer,
|
||||
&pem.Block{Type: "CERTIFICATE", Bytes: derBytes},
|
||||
); err != nil {
|
||||
return "", "", fmt.Errorf("error pem-encoding certificate: %s", err)
|
||||
}
|
||||
|
||||
keyBuffer := bytes.Buffer{}
|
||||
if err := pem.Encode(
|
||||
&keyBuffer,
|
||||
&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(signeeKey),
|
||||
},
|
||||
); err != nil {
|
||||
return "", "", fmt.Errorf("error pem-encoding key: %s", err)
|
||||
}
|
||||
|
||||
return string(certBuffer.Bytes()), string(keyBuffer.Bytes()), nil
|
||||
}
|
||||
|
||||
func getBaseCertTemplate(
|
||||
cn string,
|
||||
ips []interface{},
|
||||
alternateDNS []interface{},
|
||||
daysValid int,
|
||||
) (*x509.Certificate, error) {
|
||||
ipAddresses, err := getNetIPs(ips)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dnsNames, err := getAlternateDNSStrs(alternateDNS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serialNumberUpperBound := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberUpperBound)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
CommonName: cn,
|
||||
},
|
||||
IPAddresses: ipAddresses,
|
||||
DNSNames: dnsNames,
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Hour * 24 * time.Duration(daysValid)),
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{
|
||||
x509.ExtKeyUsageServerAuth,
|
||||
x509.ExtKeyUsageClientAuth,
|
||||
},
|
||||
BasicConstraintsValid: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getNetIPs(ips []interface{}) ([]net.IP, error) {
|
||||
if ips == nil {
|
||||
return []net.IP{}, nil
|
||||
}
|
||||
var ipStr string
|
||||
var ok bool
|
||||
var netIP net.IP
|
||||
netIPs := make([]net.IP, len(ips))
|
||||
for i, ip := range ips {
|
||||
ipStr, ok = ip.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing ip: %v is not a string", ip)
|
||||
}
|
||||
netIP = net.ParseIP(ipStr)
|
||||
if netIP == nil {
|
||||
return nil, fmt.Errorf("error parsing ip: %s", ipStr)
|
||||
}
|
||||
netIPs[i] = netIP
|
||||
}
|
||||
return netIPs, nil
|
||||
}
|
||||
|
||||
func getAlternateDNSStrs(alternateDNS []interface{}) ([]string, error) {
|
||||
if alternateDNS == nil {
|
||||
return []string{}, nil
|
||||
}
|
||||
var dnsStr string
|
||||
var ok bool
|
||||
alternateDNSStrs := make([]string, len(alternateDNS))
|
||||
for i, dns := range alternateDNS {
|
||||
dnsStr, ok = dns.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"error processing alternate dns name: %v is not a string",
|
||||
dns,
|
||||
)
|
||||
}
|
||||
alternateDNSStrs[i] = dnsStr
|
||||
}
|
||||
return alternateDNSStrs, nil
|
||||
}
|
||||
76
vendor/github.com/Masterminds/sprig/date.go
generated
vendored
Normal file
76
vendor/github.com/Masterminds/sprig/date.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Given a format and a date, format the date string.
|
||||
//
|
||||
// Date can be a `time.Time` or an `int, int32, int64`.
|
||||
// In the later case, it is treated as seconds since UNIX
|
||||
// epoch.
|
||||
func date(fmt string, date interface{}) string {
|
||||
return dateInZone(fmt, date, "Local")
|
||||
}
|
||||
|
||||
func htmlDate(date interface{}) string {
|
||||
return dateInZone("2006-01-02", date, "Local")
|
||||
}
|
||||
|
||||
func htmlDateInZone(date interface{}, zone string) string {
|
||||
return dateInZone("2006-01-02", date, zone)
|
||||
}
|
||||
|
||||
func dateInZone(fmt string, date interface{}, zone string) string {
|
||||
var t time.Time
|
||||
switch date := date.(type) {
|
||||
default:
|
||||
t = time.Now()
|
||||
case time.Time:
|
||||
t = date
|
||||
case int64:
|
||||
t = time.Unix(date, 0)
|
||||
case int:
|
||||
t = time.Unix(int64(date), 0)
|
||||
case int32:
|
||||
t = time.Unix(int64(date), 0)
|
||||
}
|
||||
|
||||
loc, err := time.LoadLocation(zone)
|
||||
if err != nil {
|
||||
loc, _ = time.LoadLocation("UTC")
|
||||
}
|
||||
|
||||
return t.In(loc).Format(fmt)
|
||||
}
|
||||
|
||||
func dateModify(fmt string, date time.Time) time.Time {
|
||||
d, err := time.ParseDuration(fmt)
|
||||
if err != nil {
|
||||
return date
|
||||
}
|
||||
return date.Add(d)
|
||||
}
|
||||
|
||||
func dateAgo(date interface{}) string {
|
||||
var t time.Time
|
||||
|
||||
switch date := date.(type) {
|
||||
default:
|
||||
t = time.Now()
|
||||
case time.Time:
|
||||
t = date
|
||||
case int64:
|
||||
t = time.Unix(date, 0)
|
||||
case int:
|
||||
t = time.Unix(int64(date), 0)
|
||||
}
|
||||
// Drop resolution to seconds
|
||||
duration := time.Since(t).Round(time.Second)
|
||||
return duration.String()
|
||||
}
|
||||
|
||||
func toDate(fmt, str string) time.Time {
|
||||
t, _ := time.ParseInLocation(fmt, str, time.Local)
|
||||
return t
|
||||
}
|
||||
83
vendor/github.com/Masterminds/sprig/defaults.go
generated
vendored
Normal file
83
vendor/github.com/Masterminds/sprig/defaults.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// dfault checks whether `given` is set, and returns default if not set.
|
||||
//
|
||||
// This returns `d` if `given` appears not to be set, and `given` otherwise.
|
||||
//
|
||||
// For numeric types 0 is unset.
|
||||
// For strings, maps, arrays, and slices, len() = 0 is considered unset.
|
||||
// For bool, false is unset.
|
||||
// Structs are never considered unset.
|
||||
//
|
||||
// For everything else, including pointers, a nil value is unset.
|
||||
func dfault(d interface{}, given ...interface{}) interface{} {
|
||||
|
||||
if empty(given) || empty(given[0]) {
|
||||
return d
|
||||
}
|
||||
return given[0]
|
||||
}
|
||||
|
||||
// empty returns true if the given value has the zero value for its type.
|
||||
func empty(given interface{}) bool {
|
||||
g := reflect.ValueOf(given)
|
||||
if !g.IsValid() {
|
||||
return true
|
||||
}
|
||||
|
||||
// Basically adapted from text/template.isTrue
|
||||
switch g.Kind() {
|
||||
default:
|
||||
return g.IsNil()
|
||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
||||
return g.Len() == 0
|
||||
case reflect.Bool:
|
||||
return g.Bool() == false
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return g.Complex() == 0
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return g.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return g.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return g.Float() == 0
|
||||
case reflect.Struct:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// coalesce returns the first non-empty value.
|
||||
func coalesce(v ...interface{}) interface{} {
|
||||
for _, val := range v {
|
||||
if !empty(val) {
|
||||
return val
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// toJson encodes an item into a JSON string
|
||||
func toJson(v interface{}) string {
|
||||
output, _ := json.Marshal(v)
|
||||
return string(output)
|
||||
}
|
||||
|
||||
// toPrettyJson encodes an item into a pretty (indented) JSON string
|
||||
func toPrettyJson(v interface{}) string {
|
||||
output, _ := json.MarshalIndent(v, "", " ")
|
||||
return string(output)
|
||||
}
|
||||
|
||||
// ternary returns the first value if the last value is true, otherwise returns the second value.
|
||||
func ternary(vt interface{}, vf interface{}, v bool) interface{} {
|
||||
if v {
|
||||
return vt
|
||||
}
|
||||
|
||||
return vf
|
||||
}
|
||||
97
vendor/github.com/Masterminds/sprig/dict.go
generated
vendored
Normal file
97
vendor/github.com/Masterminds/sprig/dict.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
package sprig
|
||||
|
||||
import "github.com/imdario/mergo"
|
||||
|
||||
func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} {
|
||||
d[key] = value
|
||||
return d
|
||||
}
|
||||
|
||||
func unset(d map[string]interface{}, key string) map[string]interface{} {
|
||||
delete(d, key)
|
||||
return d
|
||||
}
|
||||
|
||||
func hasKey(d map[string]interface{}, key string) bool {
|
||||
_, ok := d[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func pluck(key string, d ...map[string]interface{}) []interface{} {
|
||||
res := []interface{}{}
|
||||
for _, dict := range d {
|
||||
if val, ok := dict[key]; ok {
|
||||
res = append(res, val)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func keys(dicts ...map[string]interface{}) []string {
|
||||
k := []string{}
|
||||
for _, dict := range dicts {
|
||||
for key := range dict {
|
||||
k = append(k, key)
|
||||
}
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func pick(dict map[string]interface{}, keys ...string) map[string]interface{} {
|
||||
res := map[string]interface{}{}
|
||||
for _, k := range keys {
|
||||
if v, ok := dict[k]; ok {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func omit(dict map[string]interface{}, keys ...string) map[string]interface{} {
|
||||
res := map[string]interface{}{}
|
||||
|
||||
omit := make(map[string]bool, len(keys))
|
||||
for _, k := range keys {
|
||||
omit[k] = true
|
||||
}
|
||||
|
||||
for k, v := range dict {
|
||||
if _, ok := omit[k]; !ok {
|
||||
res[k] = v
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func dict(v ...interface{}) map[string]interface{} {
|
||||
dict := map[string]interface{}{}
|
||||
lenv := len(v)
|
||||
for i := 0; i < lenv; i += 2 {
|
||||
key := strval(v[i])
|
||||
if i+1 >= lenv {
|
||||
dict[key] = ""
|
||||
continue
|
||||
}
|
||||
dict[key] = v[i+1]
|
||||
}
|
||||
return dict
|
||||
}
|
||||
|
||||
func merge(dst map[string]interface{}, srcs ...map[string]interface{}) interface{} {
|
||||
for _, src := range srcs {
|
||||
if err := mergo.Merge(&dst, src); err != nil {
|
||||
// Swallow errors inside of a template.
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
func values(dict map[string]interface{}) []interface{} {
|
||||
values := []interface{}{}
|
||||
for _, value := range dict {
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
19
vendor/github.com/Masterminds/sprig/doc.go
generated
vendored
Normal file
19
vendor/github.com/Masterminds/sprig/doc.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Sprig: Template functions for Go.
|
||||
|
||||
This package contains a number of utility functions for working with data
|
||||
inside of Go `html/template` and `text/template` files.
|
||||
|
||||
To add these functions, use the `template.Funcs()` method:
|
||||
|
||||
t := templates.New("foo").Funcs(sprig.FuncMap())
|
||||
|
||||
Note that you should add the function map before you parse any template files.
|
||||
|
||||
In several cases, Sprig reverses the order of arguments from the way they
|
||||
appear in the standard library. This is to make it easier to pipe
|
||||
arguments into functions.
|
||||
|
||||
See http://masterminds.github.io/sprig/ for more detailed documentation on each of the available functions.
|
||||
*/
|
||||
package sprig
|
||||
285
vendor/github.com/Masterminds/sprig/functions.go
generated
vendored
Normal file
285
vendor/github.com/Masterminds/sprig/functions.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"html/template"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
ttemplate "text/template"
|
||||
"time"
|
||||
|
||||
util "github.com/aokoli/goutils"
|
||||
"github.com/huandu/xstrings"
|
||||
)
|
||||
|
||||
// Produce the function map.
|
||||
//
|
||||
// Use this to pass the functions into the template engine:
|
||||
//
|
||||
// tpl := template.New("foo").Funcs(sprig.FuncMap()))
|
||||
//
|
||||
func FuncMap() template.FuncMap {
|
||||
return HtmlFuncMap()
|
||||
}
|
||||
|
||||
// HermeticTextFuncMap returns a 'text/template'.FuncMap with only repeatable functions.
|
||||
func HermeticTxtFuncMap() ttemplate.FuncMap {
|
||||
r := TxtFuncMap()
|
||||
for _, name := range nonhermeticFunctions {
|
||||
delete(r, name)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// HermeticHtmlFuncMap returns an 'html/template'.Funcmap with only repeatable functions.
|
||||
func HermeticHtmlFuncMap() template.FuncMap {
|
||||
r := HtmlFuncMap()
|
||||
for _, name := range nonhermeticFunctions {
|
||||
delete(r, name)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// TextFuncMap returns a 'text/template'.FuncMap
|
||||
func TxtFuncMap() ttemplate.FuncMap {
|
||||
return ttemplate.FuncMap(GenericFuncMap())
|
||||
}
|
||||
|
||||
// HtmlFuncMap returns an 'html/template'.Funcmap
|
||||
func HtmlFuncMap() template.FuncMap {
|
||||
return template.FuncMap(GenericFuncMap())
|
||||
}
|
||||
|
||||
// GenericFuncMap returns a copy of the basic function map as a map[string]interface{}.
|
||||
func GenericFuncMap() map[string]interface{} {
|
||||
gfm := make(map[string]interface{}, len(genericMap))
|
||||
for k, v := range genericMap {
|
||||
gfm[k] = v
|
||||
}
|
||||
return gfm
|
||||
}
|
||||
|
||||
// These functions are not guaranteed to evaluate to the same result for given input, because they
|
||||
// refer to the environemnt or global state.
|
||||
var nonhermeticFunctions = []string{
|
||||
// Date functions
|
||||
"date",
|
||||
"date_in_zone",
|
||||
"date_modify",
|
||||
"now",
|
||||
"htmlDate",
|
||||
"htmlDateInZone",
|
||||
"dateInZone",
|
||||
"dateModify",
|
||||
|
||||
// Strings
|
||||
"randAlphaNum",
|
||||
"randAlpha",
|
||||
"randAscii",
|
||||
"randNumeric",
|
||||
"uuidv4",
|
||||
|
||||
// OS
|
||||
"env",
|
||||
"expandenv",
|
||||
}
|
||||
|
||||
var genericMap = map[string]interface{}{
|
||||
"hello": func() string { return "Hello!" },
|
||||
|
||||
// Date functions
|
||||
"date": date,
|
||||
"date_in_zone": dateInZone,
|
||||
"date_modify": dateModify,
|
||||
"now": func() time.Time { return time.Now() },
|
||||
"htmlDate": htmlDate,
|
||||
"htmlDateInZone": htmlDateInZone,
|
||||
"dateInZone": dateInZone,
|
||||
"dateModify": dateModify,
|
||||
"ago": dateAgo,
|
||||
"toDate": toDate,
|
||||
|
||||
// Strings
|
||||
"abbrev": abbrev,
|
||||
"abbrevboth": abbrevboth,
|
||||
"trunc": trunc,
|
||||
"trim": strings.TrimSpace,
|
||||
"upper": strings.ToUpper,
|
||||
"lower": strings.ToLower,
|
||||
"title": strings.Title,
|
||||
"untitle": untitle,
|
||||
"substr": substring,
|
||||
// Switch order so that "foo" | repeat 5
|
||||
"repeat": func(count int, str string) string { return strings.Repeat(str, count) },
|
||||
// Deprecated: Use trimAll.
|
||||
"trimall": func(a, b string) string { return strings.Trim(b, a) },
|
||||
// Switch order so that "$foo" | trimall "$"
|
||||
"trimAll": func(a, b string) string { return strings.Trim(b, a) },
|
||||
"trimSuffix": func(a, b string) string { return strings.TrimSuffix(b, a) },
|
||||
"trimPrefix": func(a, b string) string { return strings.TrimPrefix(b, a) },
|
||||
"nospace": util.DeleteWhiteSpace,
|
||||
"initials": initials,
|
||||
"randAlphaNum": randAlphaNumeric,
|
||||
"randAlpha": randAlpha,
|
||||
"randAscii": randAscii,
|
||||
"randNumeric": randNumeric,
|
||||
"swapcase": util.SwapCase,
|
||||
"shuffle": xstrings.Shuffle,
|
||||
"snakecase": xstrings.ToSnakeCase,
|
||||
"camelcase": xstrings.ToCamelCase,
|
||||
"wrap": func(l int, s string) string { return util.Wrap(s, l) },
|
||||
"wrapWith": func(l int, sep, str string) string { return util.WrapCustom(str, l, sep, true) },
|
||||
// Switch order so that "foobar" | contains "foo"
|
||||
"contains": func(substr string, str string) bool { return strings.Contains(str, substr) },
|
||||
"hasPrefix": func(substr string, str string) bool { return strings.HasPrefix(str, substr) },
|
||||
"hasSuffix": func(substr string, str string) bool { return strings.HasSuffix(str, substr) },
|
||||
"quote": quote,
|
||||
"squote": squote,
|
||||
"cat": cat,
|
||||
"indent": indent,
|
||||
"nindent": nindent,
|
||||
"replace": replace,
|
||||
"plural": plural,
|
||||
"sha1sum": sha1sum,
|
||||
"sha256sum": sha256sum,
|
||||
"toString": strval,
|
||||
|
||||
// Wrap Atoi to stop errors.
|
||||
"atoi": func(a string) int { i, _ := strconv.Atoi(a); return i },
|
||||
"int64": toInt64,
|
||||
"int": toInt,
|
||||
"float64": toFloat64,
|
||||
|
||||
//"gt": func(a, b int) bool {return a > b},
|
||||
//"gte": func(a, b int) bool {return a >= b},
|
||||
//"lt": func(a, b int) bool {return a < b},
|
||||
//"lte": func(a, b int) bool {return a <= b},
|
||||
|
||||
// split "/" foo/bar returns map[int]string{0: foo, 1: bar}
|
||||
"split": split,
|
||||
"splitList": func(sep, orig string) []string { return strings.Split(orig, sep) },
|
||||
// splitn "/" foo/bar/fuu returns map[int]string{0: foo, 1: bar/fuu}
|
||||
"splitn": splitn,
|
||||
"toStrings": strslice,
|
||||
|
||||
"until": until,
|
||||
"untilStep": untilStep,
|
||||
|
||||
// VERY basic arithmetic.
|
||||
"add1": func(i interface{}) int64 { return toInt64(i) + 1 },
|
||||
"add": func(i ...interface{}) int64 {
|
||||
var a int64 = 0
|
||||
for _, b := range i {
|
||||
a += toInt64(b)
|
||||
}
|
||||
return a
|
||||
},
|
||||
"sub": func(a, b interface{}) int64 { return toInt64(a) - toInt64(b) },
|
||||
"div": func(a, b interface{}) int64 { return toInt64(a) / toInt64(b) },
|
||||
"mod": func(a, b interface{}) int64 { return toInt64(a) % toInt64(b) },
|
||||
"mul": func(a interface{}, v ...interface{}) int64 {
|
||||
val := toInt64(a)
|
||||
for _, b := range v {
|
||||
val = val * toInt64(b)
|
||||
}
|
||||
return val
|
||||
},
|
||||
"biggest": max,
|
||||
"max": max,
|
||||
"min": min,
|
||||
"ceil": ceil,
|
||||
"floor": floor,
|
||||
"round": round,
|
||||
|
||||
// string slices. Note that we reverse the order b/c that's better
|
||||
// for template processing.
|
||||
"join": join,
|
||||
"sortAlpha": sortAlpha,
|
||||
|
||||
// Defaults
|
||||
"default": dfault,
|
||||
"empty": empty,
|
||||
"coalesce": coalesce,
|
||||
"compact": compact,
|
||||
"toJson": toJson,
|
||||
"toPrettyJson": toPrettyJson,
|
||||
"ternary": ternary,
|
||||
|
||||
// Reflection
|
||||
"typeOf": typeOf,
|
||||
"typeIs": typeIs,
|
||||
"typeIsLike": typeIsLike,
|
||||
"kindOf": kindOf,
|
||||
"kindIs": kindIs,
|
||||
|
||||
// OS:
|
||||
"env": func(s string) string { return os.Getenv(s) },
|
||||
"expandenv": func(s string) string { return os.ExpandEnv(s) },
|
||||
|
||||
// File Paths:
|
||||
"base": path.Base,
|
||||
"dir": path.Dir,
|
||||
"clean": path.Clean,
|
||||
"ext": path.Ext,
|
||||
"isAbs": path.IsAbs,
|
||||
|
||||
// Encoding:
|
||||
"b64enc": base64encode,
|
||||
"b64dec": base64decode,
|
||||
"b32enc": base32encode,
|
||||
"b32dec": base32decode,
|
||||
|
||||
// Data Structures:
|
||||
"tuple": list, // FIXME: with the addition of append/prepend these are no longer immutable.
|
||||
"list": list,
|
||||
"dict": dict,
|
||||
"set": set,
|
||||
"unset": unset,
|
||||
"hasKey": hasKey,
|
||||
"pluck": pluck,
|
||||
"keys": keys,
|
||||
"pick": pick,
|
||||
"omit": omit,
|
||||
"merge": merge,
|
||||
"values": values,
|
||||
|
||||
"append": push, "push": push,
|
||||
"prepend": prepend,
|
||||
"first": first,
|
||||
"rest": rest,
|
||||
"last": last,
|
||||
"initial": initial,
|
||||
"reverse": reverse,
|
||||
"uniq": uniq,
|
||||
"without": without,
|
||||
"has": has,
|
||||
"slice": slice,
|
||||
|
||||
// Crypto:
|
||||
"genPrivateKey": generatePrivateKey,
|
||||
"derivePassword": derivePassword,
|
||||
"buildCustomCert": buildCustomCertificate,
|
||||
"genCA": generateCertificateAuthority,
|
||||
"genSelfSignedCert": generateSelfSignedCertificate,
|
||||
"genSignedCert": generateSignedCertificate,
|
||||
|
||||
// UUIDs:
|
||||
"uuidv4": uuidv4,
|
||||
|
||||
// SemVer:
|
||||
"semver": semver,
|
||||
"semverCompare": semverCompare,
|
||||
|
||||
// Flow Control:
|
||||
"fail": func(msg string) (string, error) { return "", errors.New(msg) },
|
||||
|
||||
// Regex
|
||||
"regexMatch": regexMatch,
|
||||
"regexFindAll": regexFindAll,
|
||||
"regexFind": regexFind,
|
||||
"regexReplaceAll": regexReplaceAll,
|
||||
"regexReplaceAllLiteral": regexReplaceAllLiteral,
|
||||
"regexSplit": regexSplit,
|
||||
}
|
||||
33
vendor/github.com/Masterminds/sprig/glide.lock
generated
vendored
Normal file
33
vendor/github.com/Masterminds/sprig/glide.lock
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
hash: 770b6a1132b743dadf6a0bb5fb8bf7083b1a5209f6d6c07826234ab2a97aade9
|
||||
updated: 2018-04-02T23:08:56.947456531+02:00
|
||||
imports:
|
||||
- name: github.com/aokoli/goutils
|
||||
version: 9c37978a95bd5c709a15883b6242714ea6709e64
|
||||
- name: github.com/google/uuid
|
||||
version: 064e2069ce9c359c118179501254f67d7d37ba24
|
||||
- name: github.com/huandu/xstrings
|
||||
version: 3959339b333561bf62a38b424fd41517c2c90f40
|
||||
- name: github.com/imdario/mergo
|
||||
version: 7fe0c75c13abdee74b09fcacef5ea1c6bba6a874
|
||||
- name: github.com/Masterminds/goutils
|
||||
version: 3391d3790d23d03408670993e957e8f408993c34
|
||||
- name: github.com/Masterminds/semver
|
||||
version: 59c29afe1a994eacb71c833025ca7acf874bb1da
|
||||
- name: github.com/stretchr/testify
|
||||
version: e3a8ff8ce36581f87a15341206f205b1da467059
|
||||
subpackages:
|
||||
- assert
|
||||
- name: golang.org/x/crypto
|
||||
version: d172538b2cfce0c13cee31e647d0367aa8cd2486
|
||||
subpackages:
|
||||
- pbkdf2
|
||||
- scrypt
|
||||
testImports:
|
||||
- name: github.com/davecgh/go-spew
|
||||
version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
|
||||
subpackages:
|
||||
- spew
|
||||
- name: github.com/pmezard/go-difflib
|
||||
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
|
||||
subpackages:
|
||||
- difflib
|
||||
15
vendor/github.com/Masterminds/sprig/glide.yaml
generated
vendored
Normal file
15
vendor/github.com/Masterminds/sprig/glide.yaml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
package: github.com/Masterminds/sprig
|
||||
import:
|
||||
- package: github.com/Masterminds/goutils
|
||||
version: ^1.0.0
|
||||
- package: github.com/google/uuid
|
||||
version: ^0.2
|
||||
- package: golang.org/x/crypto
|
||||
subpackages:
|
||||
- scrypt
|
||||
- package: github.com/Masterminds/semver
|
||||
version: v1.2.2
|
||||
- package: github.com/stretchr/testify
|
||||
- package: github.com/imdario/mergo
|
||||
version: ~0.2.2
|
||||
- package: github.com/huandu/xstrings
|
||||
291
vendor/github.com/Masterminds/sprig/list.go
generated
vendored
Normal file
291
vendor/github.com/Masterminds/sprig/list.go
generated
vendored
Normal file
@@ -0,0 +1,291 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Reflection is used in these functions so that slices and arrays of strings,
|
||||
// ints, and other types not implementing []interface{} can be worked with.
|
||||
// For example, this is useful if you need to work on the output of regexs.
|
||||
|
||||
func list(v ...interface{}) []interface{} {
|
||||
return v
|
||||
}
|
||||
|
||||
func push(list interface{}, v interface{}) []interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
nl := make([]interface{}, l)
|
||||
for i := 0; i < l; i++ {
|
||||
nl[i] = l2.Index(i).Interface()
|
||||
}
|
||||
|
||||
return append(nl, v)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot push on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func prepend(list interface{}, v interface{}) []interface{} {
|
||||
//return append([]interface{}{v}, list...)
|
||||
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
nl := make([]interface{}, l)
|
||||
for i := 0; i < l; i++ {
|
||||
nl[i] = l2.Index(i).Interface()
|
||||
}
|
||||
|
||||
return append([]interface{}{v}, nl...)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot prepend on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func last(list interface{}) interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l2.Index(l - 1).Interface()
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find last on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func first(list interface{}) interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return l2.Index(0).Interface()
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find first on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func rest(list interface{}) []interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
nl := make([]interface{}, l-1)
|
||||
for i := 1; i < l; i++ {
|
||||
nl[i-1] = l2.Index(i).Interface()
|
||||
}
|
||||
|
||||
return nl
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find rest on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func initial(list interface{}) []interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
nl := make([]interface{}, l-1)
|
||||
for i := 0; i < l-1; i++ {
|
||||
nl[i] = l2.Index(i).Interface()
|
||||
}
|
||||
|
||||
return nl
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find initial on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func sortAlpha(list interface{}) []string {
|
||||
k := reflect.Indirect(reflect.ValueOf(list)).Kind()
|
||||
switch k {
|
||||
case reflect.Slice, reflect.Array:
|
||||
a := strslice(list)
|
||||
s := sort.StringSlice(a)
|
||||
s.Sort()
|
||||
return s
|
||||
}
|
||||
return []string{strval(list)}
|
||||
}
|
||||
|
||||
func reverse(v interface{}) []interface{} {
|
||||
tp := reflect.TypeOf(v).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(v)
|
||||
|
||||
l := l2.Len()
|
||||
// We do not sort in place because the incoming array should not be altered.
|
||||
nl := make([]interface{}, l)
|
||||
for i := 0; i < l; i++ {
|
||||
nl[l-i-1] = l2.Index(i).Interface()
|
||||
}
|
||||
|
||||
return nl
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find reverse on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func compact(list interface{}) []interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
nl := []interface{}{}
|
||||
var item interface{}
|
||||
for i := 0; i < l; i++ {
|
||||
item = l2.Index(i).Interface()
|
||||
if !empty(item) {
|
||||
nl = append(nl, item)
|
||||
}
|
||||
}
|
||||
|
||||
return nl
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot compact on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func uniq(list interface{}) []interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
dest := []interface{}{}
|
||||
var item interface{}
|
||||
for i := 0; i < l; i++ {
|
||||
item = l2.Index(i).Interface()
|
||||
if !inList(dest, item) {
|
||||
dest = append(dest, item)
|
||||
}
|
||||
}
|
||||
|
||||
return dest
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find uniq on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func inList(haystack []interface{}, needle interface{}) bool {
|
||||
for _, h := range haystack {
|
||||
if reflect.DeepEqual(needle, h) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func without(list interface{}, omit ...interface{}) []interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
res := []interface{}{}
|
||||
var item interface{}
|
||||
for i := 0; i < l; i++ {
|
||||
item = l2.Index(i).Interface()
|
||||
if !inList(omit, item) {
|
||||
res = append(res, item)
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find without on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
func has(needle interface{}, haystack interface{}) bool {
|
||||
tp := reflect.TypeOf(haystack).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(haystack)
|
||||
var item interface{}
|
||||
l := l2.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
item = l2.Index(i).Interface()
|
||||
if reflect.DeepEqual(needle, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
default:
|
||||
panic(fmt.Sprintf("Cannot find has on type %s", tp))
|
||||
}
|
||||
}
|
||||
|
||||
// $list := [1, 2, 3, 4, 5]
|
||||
// slice $list -> list[0:5] = list[:]
|
||||
// slice $list 0 3 -> list[0:3] = list[:3]
|
||||
// slice $list 3 5 -> list[3:5]
|
||||
// slice $list 3 -> list[3:5] = list[3:]
|
||||
func slice(list interface{}, indices ...interface{}) interface{} {
|
||||
tp := reflect.TypeOf(list).Kind()
|
||||
switch tp {
|
||||
case reflect.Slice, reflect.Array:
|
||||
l2 := reflect.ValueOf(list)
|
||||
|
||||
l := l2.Len()
|
||||
if l == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var start, end int
|
||||
if len(indices) > 0 {
|
||||
start = toInt(indices[0])
|
||||
}
|
||||
if len(indices) < 2 {
|
||||
end = l
|
||||
} else {
|
||||
end = toInt(indices[1])
|
||||
}
|
||||
|
||||
return l2.Slice(start, end).Interface()
|
||||
default:
|
||||
panic(fmt.Sprintf("list should be type of slice or array but %s", tp))
|
||||
}
|
||||
}
|
||||
159
vendor/github.com/Masterminds/sprig/numeric.go
generated
vendored
Normal file
159
vendor/github.com/Masterminds/sprig/numeric.go
generated
vendored
Normal file
@@ -0,0 +1,159 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// toFloat64 converts 64-bit floats
|
||||
func toFloat64(v interface{}) float64 {
|
||||
if str, ok := v.(string); ok {
|
||||
iv, err := strconv.ParseFloat(str, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return iv
|
||||
}
|
||||
|
||||
val := reflect.Indirect(reflect.ValueOf(v))
|
||||
switch val.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return float64(val.Int())
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return float64(val.Uint())
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
return float64(val.Uint())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return val.Float()
|
||||
case reflect.Bool:
|
||||
if val.Bool() == true {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func toInt(v interface{}) int {
|
||||
//It's not optimal. Bud I don't want duplicate toInt64 code.
|
||||
return int(toInt64(v))
|
||||
}
|
||||
|
||||
// toInt64 converts integer types to 64-bit integers
|
||||
func toInt64(v interface{}) int64 {
|
||||
if str, ok := v.(string); ok {
|
||||
iv, err := strconv.ParseInt(str, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return iv
|
||||
}
|
||||
|
||||
val := reflect.Indirect(reflect.ValueOf(v))
|
||||
switch val.Kind() {
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return val.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||
return int64(val.Uint())
|
||||
case reflect.Uint, reflect.Uint64:
|
||||
tv := val.Uint()
|
||||
if tv <= math.MaxInt64 {
|
||||
return int64(tv)
|
||||
}
|
||||
// TODO: What is the sensible thing to do here?
|
||||
return math.MaxInt64
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return int64(val.Float())
|
||||
case reflect.Bool:
|
||||
if val.Bool() == true {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func max(a interface{}, i ...interface{}) int64 {
|
||||
aa := toInt64(a)
|
||||
for _, b := range i {
|
||||
bb := toInt64(b)
|
||||
if bb > aa {
|
||||
aa = bb
|
||||
}
|
||||
}
|
||||
return aa
|
||||
}
|
||||
|
||||
func min(a interface{}, i ...interface{}) int64 {
|
||||
aa := toInt64(a)
|
||||
for _, b := range i {
|
||||
bb := toInt64(b)
|
||||
if bb < aa {
|
||||
aa = bb
|
||||
}
|
||||
}
|
||||
return aa
|
||||
}
|
||||
|
||||
func until(count int) []int {
|
||||
step := 1
|
||||
if count < 0 {
|
||||
step = -1
|
||||
}
|
||||
return untilStep(0, count, step)
|
||||
}
|
||||
|
||||
func untilStep(start, stop, step int) []int {
|
||||
v := []int{}
|
||||
|
||||
if stop < start {
|
||||
if step >= 0 {
|
||||
return v
|
||||
}
|
||||
for i := start; i > stop; i += step {
|
||||
v = append(v, i)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
if step <= 0 {
|
||||
return v
|
||||
}
|
||||
for i := start; i < stop; i += step {
|
||||
v = append(v, i)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func floor(a interface{}) float64 {
|
||||
aa := toFloat64(a)
|
||||
return math.Floor(aa)
|
||||
}
|
||||
|
||||
func ceil(a interface{}) float64 {
|
||||
aa := toFloat64(a)
|
||||
return math.Ceil(aa)
|
||||
}
|
||||
|
||||
func round(a interface{}, p int, r_opt ...float64) float64 {
|
||||
roundOn := .5
|
||||
if len(r_opt) > 0 {
|
||||
roundOn = r_opt[0]
|
||||
}
|
||||
val := toFloat64(a)
|
||||
places := toFloat64(p)
|
||||
|
||||
var round float64
|
||||
pow := math.Pow(10, places)
|
||||
digit := pow * val
|
||||
_, div := math.Modf(digit)
|
||||
if div >= roundOn {
|
||||
round = math.Ceil(digit)
|
||||
} else {
|
||||
round = math.Floor(digit)
|
||||
}
|
||||
return round / pow
|
||||
}
|
||||
28
vendor/github.com/Masterminds/sprig/reflect.go
generated
vendored
Normal file
28
vendor/github.com/Masterminds/sprig/reflect.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// typeIs returns true if the src is the type named in target.
|
||||
func typeIs(target string, src interface{}) bool {
|
||||
return target == typeOf(src)
|
||||
}
|
||||
|
||||
func typeIsLike(target string, src interface{}) bool {
|
||||
t := typeOf(src)
|
||||
return target == t || "*"+target == t
|
||||
}
|
||||
|
||||
func typeOf(src interface{}) string {
|
||||
return fmt.Sprintf("%T", src)
|
||||
}
|
||||
|
||||
func kindIs(target string, src interface{}) bool {
|
||||
return target == kindOf(src)
|
||||
}
|
||||
|
||||
func kindOf(src interface{}) string {
|
||||
return reflect.ValueOf(src).Kind().String()
|
||||
}
|
||||
35
vendor/github.com/Masterminds/sprig/regex.go
generated
vendored
Normal file
35
vendor/github.com/Masterminds/sprig/regex.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
func regexMatch(regex string, s string) bool {
|
||||
match, _ := regexp.MatchString(regex, s)
|
||||
return match
|
||||
}
|
||||
|
||||
func regexFindAll(regex string, s string, n int) []string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.FindAllString(s, n)
|
||||
}
|
||||
|
||||
func regexFind(regex string, s string) string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.FindString(s)
|
||||
}
|
||||
|
||||
func regexReplaceAll(regex string, s string, repl string) string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.ReplaceAllString(s, repl)
|
||||
}
|
||||
|
||||
func regexReplaceAllLiteral(regex string, s string, repl string) string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.ReplaceAllLiteralString(s, repl)
|
||||
}
|
||||
|
||||
func regexSplit(regex string, s string, n int) []string {
|
||||
r := regexp.MustCompile(regex)
|
||||
return r.Split(s, n)
|
||||
}
|
||||
23
vendor/github.com/Masterminds/sprig/semver.go
generated
vendored
Normal file
23
vendor/github.com/Masterminds/sprig/semver.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
sv2 "github.com/Masterminds/semver"
|
||||
)
|
||||
|
||||
func semverCompare(constraint, version string) (bool, error) {
|
||||
c, err := sv2.NewConstraint(constraint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
v, err := sv2.NewVersion(version)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return c.Check(v), nil
|
||||
}
|
||||
|
||||
func semver(version string) (*sv2.Version, error) {
|
||||
return sv2.NewVersion(version)
|
||||
}
|
||||
210
vendor/github.com/Masterminds/sprig/strings.go
generated
vendored
Normal file
210
vendor/github.com/Masterminds/sprig/strings.go
generated
vendored
Normal file
@@ -0,0 +1,210 @@
|
||||
package sprig
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
util "github.com/aokoli/goutils"
|
||||
)
|
||||
|
||||
func base64encode(v string) string {
|
||||
return base64.StdEncoding.EncodeToString([]byte(v))
|
||||
}
|
||||
|
||||
func base64decode(v string) string {
|
||||
data, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func base32encode(v string) string {
|
||||
return base32.StdEncoding.EncodeToString([]byte(v))
|
||||
}
|
||||
|
||||
func base32decode(v string) string {
|
||||
data, err := base32.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func abbrev(width int, s string) string {
|
||||
if width < 4 {
|
||||
return s
|
||||
}
|
||||
r, _ := util.Abbreviate(s, width)
|
||||
return r
|
||||
}
|
||||
|
||||
func abbrevboth(left, right int, s string) string {
|
||||
if right < 4 || left > 0 && right < 7 {
|
||||
return s
|
||||
}
|
||||
r, _ := util.AbbreviateFull(s, left, right)
|
||||
return r
|
||||
}
|
||||
func initials(s string) string {
|
||||
// Wrap this just to eliminate the var args, which templates don't do well.
|
||||
return util.Initials(s)
|
||||
}
|
||||
|
||||
func randAlphaNumeric(count int) string {
|
||||
// It is not possible, it appears, to actually generate an error here.
|
||||
r, _ := util.RandomAlphaNumeric(count)
|
||||
return r
|
||||
}
|
||||
|
||||
func randAlpha(count int) string {
|
||||
r, _ := util.RandomAlphabetic(count)
|
||||
return r
|
||||
}
|
||||
|
||||
func randAscii(count int) string {
|
||||
r, _ := util.RandomAscii(count)
|
||||
return r
|
||||
}
|
||||
|
||||
func randNumeric(count int) string {
|
||||
r, _ := util.RandomNumeric(count)
|
||||
return r
|
||||
}
|
||||
|
||||
func untitle(str string) string {
|
||||
return util.Uncapitalize(str)
|
||||
}
|
||||
|
||||
func quote(str ...interface{}) string {
|
||||
out := make([]string, len(str))
|
||||
for i, s := range str {
|
||||
out[i] = fmt.Sprintf("%q", strval(s))
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func squote(str ...interface{}) string {
|
||||
out := make([]string, len(str))
|
||||
for i, s := range str {
|
||||
out[i] = fmt.Sprintf("'%v'", s)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
func cat(v ...interface{}) string {
|
||||
r := strings.TrimSpace(strings.Repeat("%v ", len(v)))
|
||||
return fmt.Sprintf(r, v...)
|
||||
}
|
||||
|
||||
func indent(spaces int, v string) string {
|
||||
pad := strings.Repeat(" ", spaces)
|
||||
return pad + strings.Replace(v, "\n", "\n"+pad, -1)
|
||||
}
|
||||
|
||||
func nindent(spaces int, v string) string {
|
||||
return "\n" + indent(spaces, v)
|
||||
}
|
||||
|
||||
func replace(old, new, src string) string {
|
||||
return strings.Replace(src, old, new, -1)
|
||||
}
|
||||
|
||||
func plural(one, many string, count int) string {
|
||||
if count == 1 {
|
||||
return one
|
||||
}
|
||||
return many
|
||||
}
|
||||
|
||||
func strslice(v interface{}) []string {
|
||||
switch v := v.(type) {
|
||||
case []string:
|
||||
return v
|
||||
case []interface{}:
|
||||
l := len(v)
|
||||
b := make([]string, l)
|
||||
for i := 0; i < l; i++ {
|
||||
b[i] = strval(v[i])
|
||||
}
|
||||
return b
|
||||
default:
|
||||
val := reflect.ValueOf(v)
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
l := val.Len()
|
||||
b := make([]string, l)
|
||||
for i := 0; i < l; i++ {
|
||||
b[i] = strval(val.Index(i).Interface())
|
||||
}
|
||||
return b
|
||||
default:
|
||||
return []string{strval(v)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func strval(v interface{}) string {
|
||||
switch v := v.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
case error:
|
||||
return v.Error()
|
||||
case fmt.Stringer:
|
||||
return v.String()
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func trunc(c int, s string) string {
|
||||
if len(s) <= c {
|
||||
return s
|
||||
}
|
||||
return s[0:c]
|
||||
}
|
||||
|
||||
func join(sep string, v interface{}) string {
|
||||
return strings.Join(strslice(v), sep)
|
||||
}
|
||||
|
||||
func split(sep, orig string) map[string]string {
|
||||
parts := strings.Split(orig, sep)
|
||||
res := make(map[string]string, len(parts))
|
||||
for i, v := range parts {
|
||||
res["_"+strconv.Itoa(i)] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func splitn(sep string, n int, orig string) map[string]string {
|
||||
parts := strings.SplitN(orig, sep, n)
|
||||
res := make(map[string]string, len(parts))
|
||||
for i, v := range parts {
|
||||
res["_"+strconv.Itoa(i)] = v
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// substring creates a substring of the given string.
|
||||
//
|
||||
// If start is < 0, this calls string[:length].
|
||||
//
|
||||
// If start is >= 0 and length < 0, this calls string[start:]
|
||||
//
|
||||
// Otherwise, this calls string[start, length].
|
||||
func substring(start, length int, s string) string {
|
||||
if start < 0 {
|
||||
return s[:length]
|
||||
}
|
||||
if length < 0 {
|
||||
return s[start:]
|
||||
}
|
||||
return s[start:length]
|
||||
}
|
||||
1
vendor/github.com/Microsoft/go-winio/.gitattributes
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/.gitattributes
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
10
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
10
vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.vscode/
|
||||
|
||||
*.exe
|
||||
|
||||
# testing
|
||||
testdata
|
||||
|
||||
# go workspaces
|
||||
go.work
|
||||
go.work.sum
|
||||
147
vendor/github.com/Microsoft/go-winio/.golangci.yml
generated
vendored
Normal file
147
vendor/github.com/Microsoft/go-winio/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
linters:
|
||||
enable:
|
||||
# style
|
||||
- containedctx # struct contains a context
|
||||
- dupl # duplicate code
|
||||
- errname # erorrs are named correctly
|
||||
- nolintlint # "//nolint" directives are properly explained
|
||||
- revive # golint replacement
|
||||
- unconvert # unnecessary conversions
|
||||
- wastedassign
|
||||
|
||||
# bugs, performance, unused, etc ...
|
||||
- contextcheck # function uses a non-inherited context
|
||||
- errorlint # errors not wrapped for 1.13
|
||||
- exhaustive # check exhaustiveness of enum switch statements
|
||||
- gofmt # files are gofmt'ed
|
||||
- gosec # security
|
||||
- nilerr # returns nil even with non-nil error
|
||||
- thelper # test helpers without t.Helper()
|
||||
- unparam # unused function params
|
||||
|
||||
issues:
|
||||
exclude-dirs:
|
||||
- pkg/etw/sample
|
||||
|
||||
exclude-rules:
|
||||
# err is very often shadowed in nested scopes
|
||||
- linters:
|
||||
- govet
|
||||
text: '^shadow: declaration of "err" shadows declaration'
|
||||
|
||||
# ignore long lines for skip autogen directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^line-length-limit: "
|
||||
source: "^//(go:generate|sys) "
|
||||
|
||||
#TODO: remove after upgrading to go1.18
|
||||
# ignore comment spacing for nolint and sys directives
|
||||
- linters:
|
||||
- revive
|
||||
text: "^comment-spacings: no space between comment delimiter and comment text"
|
||||
source: "//(cspell:|nolint:|sys |todo)"
|
||||
|
||||
# not on go 1.18 yet, so no any
|
||||
- linters:
|
||||
- revive
|
||||
text: "^use-any: since GO 1.18 'interface{}' can be replaced by 'any'"
|
||||
|
||||
# allow unjustified ignores of error checks in defer statements
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errcheck` should provide explanation"
|
||||
source: '^\s*defer '
|
||||
|
||||
# allow unjustified ignores of error lints for io.EOF
|
||||
- linters:
|
||||
- nolintlint
|
||||
text: "^directive `//nolint:errorlint` should provide explanation"
|
||||
source: '[=|!]= io.EOF'
|
||||
|
||||
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
govet:
|
||||
enable-all: true
|
||||
disable:
|
||||
# struct order is often for Win32 compat
|
||||
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
|
||||
- fieldalignment
|
||||
nolintlint:
|
||||
require-explanation: true
|
||||
require-specific: true
|
||||
revive:
|
||||
# revive is more configurable than static check, so likely the preferred alternative to static-check
|
||||
# (once the perf issue is solved: https://github.com/golangci/golangci-lint/issues/2997)
|
||||
enable-all-rules:
|
||||
true
|
||||
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
|
||||
rules:
|
||||
# rules with required arguments
|
||||
- name: argument-limit
|
||||
disabled: true
|
||||
- name: banned-characters
|
||||
disabled: true
|
||||
- name: cognitive-complexity
|
||||
disabled: true
|
||||
- name: cyclomatic
|
||||
disabled: true
|
||||
- name: file-header
|
||||
disabled: true
|
||||
- name: function-length
|
||||
disabled: true
|
||||
- name: function-result-limit
|
||||
disabled: true
|
||||
- name: max-public-structs
|
||||
disabled: true
|
||||
# geneally annoying rules
|
||||
- name: add-constant # complains about any and all strings and integers
|
||||
disabled: true
|
||||
- name: confusing-naming # we frequently use "Foo()" and "foo()" together
|
||||
disabled: true
|
||||
- name: flag-parameter # excessive, and a common idiom we use
|
||||
disabled: true
|
||||
- name: unhandled-error # warns over common fmt.Print* and io.Close; rely on errcheck instead
|
||||
disabled: true
|
||||
# general config
|
||||
- name: line-length-limit
|
||||
arguments:
|
||||
- 140
|
||||
- name: var-naming
|
||||
arguments:
|
||||
- []
|
||||
- - CID
|
||||
- CRI
|
||||
- CTRD
|
||||
- DACL
|
||||
- DLL
|
||||
- DOS
|
||||
- ETW
|
||||
- FSCTL
|
||||
- GCS
|
||||
- GMSA
|
||||
- HCS
|
||||
- HV
|
||||
- IO
|
||||
- LCOW
|
||||
- LDAP
|
||||
- LPAC
|
||||
- LTSC
|
||||
- MMIO
|
||||
- NT
|
||||
- OCI
|
||||
- PMEM
|
||||
- PWSH
|
||||
- RX
|
||||
- SACl
|
||||
- SID
|
||||
- SMB
|
||||
- TX
|
||||
- VHD
|
||||
- VHDX
|
||||
- VMID
|
||||
- VPCI
|
||||
- WCOW
|
||||
- WIM
|
||||
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
1
vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* @microsoft/containerplat
|
||||
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Microsoft
|
||||
|
||||
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.
|
||||
|
||||
89
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
89
vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# go-winio [](https://github.com/microsoft/go-winio/actions/workflows/ci.yml)
|
||||
|
||||
This repository contains utilities for efficiently performing Win32 IO operations in
|
||||
Go. Currently, this is focused on accessing named pipes and other file handles, and
|
||||
for using named pipes as a net transport.
|
||||
|
||||
This code relies on IO completion ports to avoid blocking IO on system threads, allowing Go
|
||||
to reuse the thread to schedule another goroutine. This limits support to Windows Vista and
|
||||
newer operating systems. This is similar to the implementation of network sockets in Go's net
|
||||
package.
|
||||
|
||||
Please see the LICENSE file for licensing information.
|
||||
|
||||
## Contributing
|
||||
|
||||
This project welcomes contributions and suggestions.
|
||||
Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that
|
||||
you have the right to, and actually do, grant us the rights to use your contribution.
|
||||
For details, visit [Microsoft CLA](https://cla.microsoft.com).
|
||||
|
||||
When you submit a pull request, a CLA-bot will automatically determine whether you need to
|
||||
provide a CLA and decorate the PR appropriately (e.g., label, comment).
|
||||
Simply follow the instructions provided by the bot.
|
||||
You will only need to do this once across all repos using our CLA.
|
||||
|
||||
Additionally, the pull request pipeline requires the following steps to be performed before
|
||||
mergining.
|
||||
|
||||
### Code Sign-Off
|
||||
|
||||
We require that contributors sign their commits using [`git commit --signoff`][git-commit-s]
|
||||
to certify they either authored the work themselves or otherwise have permission to use it in this project.
|
||||
|
||||
A range of commits can be signed off using [`git rebase --signoff`][git-rebase-s].
|
||||
|
||||
Please see [the developer certificate](https://developercertificate.org) for more info,
|
||||
as well as to make sure that you can attest to the rules listed.
|
||||
Our CI uses the DCO Github app to ensure that all commits in a given PR are signed-off.
|
||||
|
||||
### Linting
|
||||
|
||||
Code must pass a linting stage, which uses [`golangci-lint`][lint].
|
||||
The linting settings are stored in [`.golangci.yaml`](./.golangci.yaml), and can be run
|
||||
automatically with VSCode by adding the following to your workspace or folder settings:
|
||||
|
||||
```json
|
||||
"go.lintTool": "golangci-lint",
|
||||
"go.lintOnSave": "package",
|
||||
```
|
||||
|
||||
Additional editor [integrations options are also available][lint-ide].
|
||||
|
||||
Alternatively, `golangci-lint` can be [installed locally][lint-install] and run from the repo root:
|
||||
|
||||
```shell
|
||||
# use . or specify a path to only lint a package
|
||||
# to show all lint errors, use flags "--max-issues-per-linter=0 --max-same-issues=0"
|
||||
> golangci-lint run ./...
|
||||
```
|
||||
|
||||
### Go Generate
|
||||
|
||||
The pipeline checks that auto-generated code, via `go generate`, are up to date.
|
||||
|
||||
This can be done for the entire repo:
|
||||
|
||||
```shell
|
||||
> go generate ./...
|
||||
```
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
|
||||
For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or
|
||||
contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Special Thanks
|
||||
|
||||
Thanks to [natefinch][natefinch] for the inspiration for this library.
|
||||
See [npipe](https://github.com/natefinch/npipe) for another named pipe implementation.
|
||||
|
||||
[lint]: https://golangci-lint.run/
|
||||
[lint-ide]: https://golangci-lint.run/usage/integrations/#editor-integration
|
||||
[lint-install]: https://golangci-lint.run/usage/install/#local-installation
|
||||
|
||||
[git-commit-s]: https://git-scm.com/docs/git-commit#Documentation/git-commit.txt--s
|
||||
[git-rebase-s]: https://git-scm.com/docs/git-rebase#Documentation/git-rebase.txt---signoff
|
||||
|
||||
[natefinch]: https://github.com/natefinch
|
||||
41
vendor/github.com/Microsoft/go-winio/SECURITY.md
generated
vendored
Normal file
41
vendor/github.com/Microsoft/go-winio/SECURITY.md
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- BEGIN MICROSOFT SECURITY.MD V0.0.7 BLOCK -->
|
||||
|
||||
## Security
|
||||
|
||||
Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
|
||||
|
||||
If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
**Please do not report security vulnerabilities through public GitHub issues.**
|
||||
|
||||
Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
|
||||
|
||||
If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
|
||||
|
||||
You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
|
||||
|
||||
Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
|
||||
|
||||
* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
|
||||
* Full paths of source file(s) related to the manifestation of the issue
|
||||
* The location of the affected source code (tag/branch/commit or direct URL)
|
||||
* Any special configuration required to reproduce the issue
|
||||
* Step-by-step instructions to reproduce the issue
|
||||
* Proof-of-concept or exploit code (if possible)
|
||||
* Impact of the issue, including how an attacker might exploit the issue
|
||||
|
||||
This information will help us triage your report more quickly.
|
||||
|
||||
If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
|
||||
|
||||
## Preferred Languages
|
||||
|
||||
We prefer all communications to be in English.
|
||||
|
||||
## Policy
|
||||
|
||||
Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
|
||||
|
||||
<!-- END MICROSOFT SECURITY.MD BLOCK -->
|
||||
287
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
287
vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"unicode/utf16"
|
||||
|
||||
"github.com/Microsoft/go-winio/internal/fs"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//sys backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupRead
|
||||
//sys backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) = BackupWrite
|
||||
|
||||
const (
|
||||
BackupData = uint32(iota + 1)
|
||||
BackupEaData
|
||||
BackupSecurity
|
||||
BackupAlternateData
|
||||
BackupLink
|
||||
BackupPropertyData
|
||||
BackupObjectId //revive:disable-line:var-naming ID, not Id
|
||||
BackupReparseData
|
||||
BackupSparseBlock
|
||||
BackupTxfsData
|
||||
)
|
||||
|
||||
const (
|
||||
StreamSparseAttributes = uint32(8)
|
||||
)
|
||||
|
||||
//nolint:revive // var-naming: ALL_CAPS
|
||||
const (
|
||||
WRITE_DAC = windows.WRITE_DAC
|
||||
WRITE_OWNER = windows.WRITE_OWNER
|
||||
ACCESS_SYSTEM_SECURITY = windows.ACCESS_SYSTEM_SECURITY
|
||||
)
|
||||
|
||||
// BackupHeader represents a backup stream of a file.
|
||||
type BackupHeader struct {
|
||||
//revive:disable-next-line:var-naming ID, not Id
|
||||
Id uint32 // The backup stream ID
|
||||
Attributes uint32 // Stream attributes
|
||||
Size int64 // The size of the stream in bytes
|
||||
Name string // The name of the stream (for BackupAlternateData only).
|
||||
Offset int64 // The offset of the stream in the file (for BackupSparseBlock only).
|
||||
}
|
||||
|
||||
type win32StreamID struct {
|
||||
StreamID uint32
|
||||
Attributes uint32
|
||||
Size uint64
|
||||
NameSize uint32
|
||||
}
|
||||
|
||||
// BackupStreamReader reads from a stream produced by the BackupRead Win32 API and produces a series
|
||||
// of BackupHeader values.
|
||||
type BackupStreamReader struct {
|
||||
r io.Reader
|
||||
bytesLeft int64
|
||||
}
|
||||
|
||||
// NewBackupStreamReader produces a BackupStreamReader from any io.Reader.
|
||||
func NewBackupStreamReader(r io.Reader) *BackupStreamReader {
|
||||
return &BackupStreamReader{r, 0}
|
||||
}
|
||||
|
||||
// Next returns the next backup stream and prepares for calls to Read(). It skips the remainder of the current stream if
|
||||
// it was not completely read.
|
||||
func (r *BackupStreamReader) Next() (*BackupHeader, error) {
|
||||
if r.bytesLeft > 0 { //nolint:nestif // todo: flatten this
|
||||
if s, ok := r.r.(io.Seeker); ok {
|
||||
// Make sure Seek on io.SeekCurrent sometimes succeeds
|
||||
// before trying the actual seek.
|
||||
if _, err := s.Seek(0, io.SeekCurrent); err == nil {
|
||||
if _, err = s.Seek(r.bytesLeft, io.SeekCurrent); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.bytesLeft = 0
|
||||
}
|
||||
}
|
||||
if _, err := io.Copy(io.Discard, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
var wsi win32StreamID
|
||||
if err := binary.Read(r.r, binary.LittleEndian, &wsi); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr := &BackupHeader{
|
||||
Id: wsi.StreamID,
|
||||
Attributes: wsi.Attributes,
|
||||
Size: int64(wsi.Size),
|
||||
}
|
||||
if wsi.NameSize != 0 {
|
||||
name := make([]uint16, int(wsi.NameSize/2))
|
||||
if err := binary.Read(r.r, binary.LittleEndian, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.Name = windows.UTF16ToString(name)
|
||||
}
|
||||
if wsi.StreamID == BackupSparseBlock {
|
||||
if err := binary.Read(r.r, binary.LittleEndian, &hdr.Offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hdr.Size -= 8
|
||||
}
|
||||
r.bytesLeft = hdr.Size
|
||||
return hdr, nil
|
||||
}
|
||||
|
||||
// Read reads from the current backup stream.
|
||||
func (r *BackupStreamReader) Read(b []byte) (int, error) {
|
||||
if r.bytesLeft == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
if int64(len(b)) > r.bytesLeft {
|
||||
b = b[:r.bytesLeft]
|
||||
}
|
||||
n, err := r.r.Read(b)
|
||||
r.bytesLeft -= int64(n)
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
} else if r.bytesLeft == 0 && err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// BackupStreamWriter writes a stream compatible with the BackupWrite Win32 API.
|
||||
type BackupStreamWriter struct {
|
||||
w io.Writer
|
||||
bytesLeft int64
|
||||
}
|
||||
|
||||
// NewBackupStreamWriter produces a BackupStreamWriter on top of an io.Writer.
|
||||
func NewBackupStreamWriter(w io.Writer) *BackupStreamWriter {
|
||||
return &BackupStreamWriter{w, 0}
|
||||
}
|
||||
|
||||
// WriteHeader writes the next backup stream header and prepares for calls to Write().
|
||||
func (w *BackupStreamWriter) WriteHeader(hdr *BackupHeader) error {
|
||||
if w.bytesLeft != 0 {
|
||||
return fmt.Errorf("missing %d bytes", w.bytesLeft)
|
||||
}
|
||||
name := utf16.Encode([]rune(hdr.Name))
|
||||
wsi := win32StreamID{
|
||||
StreamID: hdr.Id,
|
||||
Attributes: hdr.Attributes,
|
||||
Size: uint64(hdr.Size),
|
||||
NameSize: uint32(len(name) * 2),
|
||||
}
|
||||
if hdr.Id == BackupSparseBlock {
|
||||
// Include space for the int64 block offset
|
||||
wsi.Size += 8
|
||||
}
|
||||
if err := binary.Write(w.w, binary.LittleEndian, &wsi); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(name) != 0 {
|
||||
if err := binary.Write(w.w, binary.LittleEndian, name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if hdr.Id == BackupSparseBlock {
|
||||
if err := binary.Write(w.w, binary.LittleEndian, hdr.Offset); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
w.bytesLeft = hdr.Size
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes to the current backup stream.
|
||||
func (w *BackupStreamWriter) Write(b []byte) (int, error) {
|
||||
if w.bytesLeft < int64(len(b)) {
|
||||
return 0, fmt.Errorf("too many bytes by %d", int64(len(b))-w.bytesLeft)
|
||||
}
|
||||
n, err := w.w.Write(b)
|
||||
w.bytesLeft -= int64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// BackupFileReader provides an io.ReadCloser interface on top of the BackupRead Win32 API.
|
||||
type BackupFileReader struct {
|
||||
f *os.File
|
||||
includeSecurity bool
|
||||
ctx uintptr
|
||||
}
|
||||
|
||||
// NewBackupFileReader returns a new BackupFileReader from a file handle. If includeSecurity is true,
|
||||
// Read will attempt to read the security descriptor of the file.
|
||||
func NewBackupFileReader(f *os.File, includeSecurity bool) *BackupFileReader {
|
||||
r := &BackupFileReader{f, includeSecurity, 0}
|
||||
return r
|
||||
}
|
||||
|
||||
// Read reads a backup stream from the file by calling the Win32 API BackupRead().
|
||||
func (r *BackupFileReader) Read(b []byte) (int, error) {
|
||||
var bytesRead uint32
|
||||
err := backupRead(windows.Handle(r.f.Fd()), b, &bytesRead, false, r.includeSecurity, &r.ctx)
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "BackupRead", Path: r.f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(r.f)
|
||||
if bytesRead == 0 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return int(bytesRead), nil
|
||||
}
|
||||
|
||||
// Close frees Win32 resources associated with the BackupFileReader. It does not close
|
||||
// the underlying file.
|
||||
func (r *BackupFileReader) Close() error {
|
||||
if r.ctx != 0 {
|
||||
_ = backupRead(windows.Handle(r.f.Fd()), nil, nil, true, false, &r.ctx)
|
||||
runtime.KeepAlive(r.f)
|
||||
r.ctx = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BackupFileWriter provides an io.WriteCloser interface on top of the BackupWrite Win32 API.
|
||||
type BackupFileWriter struct {
|
||||
f *os.File
|
||||
includeSecurity bool
|
||||
ctx uintptr
|
||||
}
|
||||
|
||||
// NewBackupFileWriter returns a new BackupFileWriter from a file handle. If includeSecurity is true,
|
||||
// Write() will attempt to restore the security descriptor from the stream.
|
||||
func NewBackupFileWriter(f *os.File, includeSecurity bool) *BackupFileWriter {
|
||||
w := &BackupFileWriter{f, includeSecurity, 0}
|
||||
return w
|
||||
}
|
||||
|
||||
// Write restores a portion of the file using the provided backup stream.
|
||||
func (w *BackupFileWriter) Write(b []byte) (int, error) {
|
||||
var bytesWritten uint32
|
||||
err := backupWrite(windows.Handle(w.f.Fd()), b, &bytesWritten, false, w.includeSecurity, &w.ctx)
|
||||
if err != nil {
|
||||
return 0, &os.PathError{Op: "BackupWrite", Path: w.f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(w.f)
|
||||
if int(bytesWritten) != len(b) {
|
||||
return int(bytesWritten), errors.New("not all bytes could be written")
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
// Close frees Win32 resources associated with the BackupFileWriter. It does not
|
||||
// close the underlying file.
|
||||
func (w *BackupFileWriter) Close() error {
|
||||
if w.ctx != 0 {
|
||||
_ = backupWrite(windows.Handle(w.f.Fd()), nil, nil, true, false, &w.ctx)
|
||||
runtime.KeepAlive(w.f)
|
||||
w.ctx = 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenForBackup opens a file or directory, potentially skipping access checks if the backup
|
||||
// or restore privileges have been acquired.
|
||||
//
|
||||
// If the file opened was a directory, it cannot be used with Readdir().
|
||||
func OpenForBackup(path string, access uint32, share uint32, createmode uint32) (*os.File, error) {
|
||||
h, err := fs.CreateFile(path,
|
||||
fs.AccessMask(access),
|
||||
fs.FileShareMode(share),
|
||||
nil,
|
||||
fs.FileCreationDisposition(createmode),
|
||||
fs.FILE_FLAG_BACKUP_SEMANTICS|fs.FILE_FLAG_OPEN_REPARSE_POINT,
|
||||
0,
|
||||
)
|
||||
if err != nil {
|
||||
err = &os.PathError{Op: "open", Path: path, Err: err}
|
||||
return nil, err
|
||||
}
|
||||
return os.NewFile(uintptr(h), path), nil
|
||||
}
|
||||
22
vendor/github.com/Microsoft/go-winio/doc.go
generated
vendored
Normal file
22
vendor/github.com/Microsoft/go-winio/doc.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// This package provides utilities for efficiently performing Win32 IO operations in Go.
|
||||
// Currently, this package is provides support for genreal IO and management of
|
||||
// - named pipes
|
||||
// - files
|
||||
// - [Hyper-V sockets]
|
||||
//
|
||||
// This code is similar to Go's [net] package, and uses IO completion ports to avoid
|
||||
// blocking IO on system threads, allowing Go to reuse the thread to schedule other goroutines.
|
||||
//
|
||||
// This limits support to Windows Vista and newer operating systems.
|
||||
//
|
||||
// Additionally, this package provides support for:
|
||||
// - creating and managing GUIDs
|
||||
// - writing to [ETW]
|
||||
// - opening and manageing VHDs
|
||||
// - parsing [Windows Image files]
|
||||
// - auto-generating Win32 API code
|
||||
//
|
||||
// [Hyper-V sockets]: https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service
|
||||
// [ETW]: https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/event-tracing-for-windows--etw-
|
||||
// [Windows Image files]: https://docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/work-with-windows-images
|
||||
package winio
|
||||
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
137
vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
package winio
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type fileFullEaInformation struct {
|
||||
NextEntryOffset uint32
|
||||
Flags uint8
|
||||
NameLength uint8
|
||||
ValueLength uint16
|
||||
}
|
||||
|
||||
var (
|
||||
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{})
|
||||
|
||||
errInvalidEaBuffer = errors.New("invalid extended attribute buffer")
|
||||
errEaNameTooLarge = errors.New("extended attribute name too large")
|
||||
errEaValueTooLarge = errors.New("extended attribute value too large")
|
||||
)
|
||||
|
||||
// ExtendedAttribute represents a single Windows EA.
|
||||
type ExtendedAttribute struct {
|
||||
Name string
|
||||
Value []byte
|
||||
Flags uint8
|
||||
}
|
||||
|
||||
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) {
|
||||
var info fileFullEaInformation
|
||||
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info)
|
||||
if err != nil {
|
||||
err = errInvalidEaBuffer
|
||||
return ea, nb, err
|
||||
}
|
||||
|
||||
nameOffset := fileFullEaInformationSize
|
||||
nameLen := int(info.NameLength)
|
||||
valueOffset := nameOffset + int(info.NameLength) + 1
|
||||
valueLen := int(info.ValueLength)
|
||||
nextOffset := int(info.NextEntryOffset)
|
||||
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) {
|
||||
err = errInvalidEaBuffer
|
||||
return ea, nb, err
|
||||
}
|
||||
|
||||
ea.Name = string(b[nameOffset : nameOffset+nameLen])
|
||||
ea.Value = b[valueOffset : valueOffset+valueLen]
|
||||
ea.Flags = info.Flags
|
||||
if info.NextEntryOffset != 0 {
|
||||
nb = b[info.NextEntryOffset:]
|
||||
}
|
||||
return ea, nb, err
|
||||
}
|
||||
|
||||
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION
|
||||
// buffer retrieved from BackupRead, ZwQueryEaFile, etc.
|
||||
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) {
|
||||
for len(b) != 0 {
|
||||
ea, nb, err := parseEa(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eas = append(eas, ea)
|
||||
b = nb
|
||||
}
|
||||
return eas, err
|
||||
}
|
||||
|
||||
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error {
|
||||
if int(uint8(len(ea.Name))) != len(ea.Name) {
|
||||
return errEaNameTooLarge
|
||||
}
|
||||
if int(uint16(len(ea.Value))) != len(ea.Value) {
|
||||
return errEaValueTooLarge
|
||||
}
|
||||
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value))
|
||||
withPadding := (entrySize + 3) &^ 3
|
||||
nextOffset := uint32(0)
|
||||
if !last {
|
||||
nextOffset = withPadding
|
||||
}
|
||||
info := fileFullEaInformation{
|
||||
NextEntryOffset: nextOffset,
|
||||
Flags: ea.Flags,
|
||||
NameLength: uint8(len(ea.Name)),
|
||||
ValueLength: uint16(len(ea.Value)),
|
||||
}
|
||||
|
||||
err := binary.Write(buf, binary.LittleEndian, &info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write([]byte(ea.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = buf.WriteByte(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write(ea.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION
|
||||
// buffer for use with BackupWrite, ZwSetEaFile, etc.
|
||||
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) {
|
||||
var buf bytes.Buffer
|
||||
for i := range eas {
|
||||
last := false
|
||||
if i == len(eas)-1 {
|
||||
last = true
|
||||
}
|
||||
|
||||
err := writeEa(&buf, &eas[i], last)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
320
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
320
vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
@@ -0,0 +1,320 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
//sys cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) = CancelIoEx
|
||||
//sys createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) = CreateIoCompletionPort
|
||||
//sys getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) = GetQueuedCompletionStatus
|
||||
//sys setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) = SetFileCompletionNotificationModes
|
||||
//sys wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) = ws2_32.WSAGetOverlappedResult
|
||||
|
||||
var (
|
||||
ErrFileClosed = errors.New("file has already been closed")
|
||||
ErrTimeout = &timeoutError{}
|
||||
)
|
||||
|
||||
type timeoutError struct{}
|
||||
|
||||
func (*timeoutError) Error() string { return "i/o timeout" }
|
||||
func (*timeoutError) Timeout() bool { return true }
|
||||
func (*timeoutError) Temporary() bool { return true }
|
||||
|
||||
type timeoutChan chan struct{}
|
||||
|
||||
var ioInitOnce sync.Once
|
||||
var ioCompletionPort windows.Handle
|
||||
|
||||
// ioResult contains the result of an asynchronous IO operation.
|
||||
type ioResult struct {
|
||||
bytes uint32
|
||||
err error
|
||||
}
|
||||
|
||||
// ioOperation represents an outstanding asynchronous Win32 IO.
|
||||
type ioOperation struct {
|
||||
o windows.Overlapped
|
||||
ch chan ioResult
|
||||
}
|
||||
|
||||
func initIO() {
|
||||
h, err := createIoCompletionPort(windows.InvalidHandle, 0, 0, 0xffffffff)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ioCompletionPort = h
|
||||
go ioCompletionProcessor(h)
|
||||
}
|
||||
|
||||
// win32File implements Reader, Writer, and Closer on a Win32 handle without blocking in a syscall.
|
||||
// It takes ownership of this handle and will close it if it is garbage collected.
|
||||
type win32File struct {
|
||||
handle windows.Handle
|
||||
wg sync.WaitGroup
|
||||
wgLock sync.RWMutex
|
||||
closing atomic.Bool
|
||||
socket bool
|
||||
readDeadline deadlineHandler
|
||||
writeDeadline deadlineHandler
|
||||
}
|
||||
|
||||
type deadlineHandler struct {
|
||||
setLock sync.Mutex
|
||||
channel timeoutChan
|
||||
channelLock sync.RWMutex
|
||||
timer *time.Timer
|
||||
timedout atomic.Bool
|
||||
}
|
||||
|
||||
// makeWin32File makes a new win32File from an existing file handle.
|
||||
func makeWin32File(h windows.Handle) (*win32File, error) {
|
||||
f := &win32File{handle: h}
|
||||
ioInitOnce.Do(initIO)
|
||||
_, err := createIoCompletionPort(h, ioCompletionPort, 0, 0xffffffff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = setFileCompletionNotificationModes(h, windows.FILE_SKIP_COMPLETION_PORT_ON_SUCCESS|windows.FILE_SKIP_SET_EVENT_ON_HANDLE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.readDeadline.channel = make(timeoutChan)
|
||||
f.writeDeadline.channel = make(timeoutChan)
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Deprecated: use NewOpenFile instead.
|
||||
func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
|
||||
return NewOpenFile(windows.Handle(h))
|
||||
}
|
||||
|
||||
func NewOpenFile(h windows.Handle) (io.ReadWriteCloser, error) {
|
||||
// If we return the result of makeWin32File directly, it can result in an
|
||||
// interface-wrapped nil, rather than a nil interface value.
|
||||
f, err := makeWin32File(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// closeHandle closes the resources associated with a Win32 handle.
|
||||
func (f *win32File) closeHandle() {
|
||||
f.wgLock.Lock()
|
||||
// Atomically set that we are closing, releasing the resources only once.
|
||||
if !f.closing.Swap(true) {
|
||||
f.wgLock.Unlock()
|
||||
// cancel all IO and wait for it to complete
|
||||
_ = cancelIoEx(f.handle, nil)
|
||||
f.wg.Wait()
|
||||
// at this point, no new IO can start
|
||||
windows.Close(f.handle)
|
||||
f.handle = 0
|
||||
} else {
|
||||
f.wgLock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes a win32File.
|
||||
func (f *win32File) Close() error {
|
||||
f.closeHandle()
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsClosed checks if the file has been closed.
|
||||
func (f *win32File) IsClosed() bool {
|
||||
return f.closing.Load()
|
||||
}
|
||||
|
||||
// prepareIO prepares for a new IO operation.
|
||||
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
|
||||
func (f *win32File) prepareIO() (*ioOperation, error) {
|
||||
f.wgLock.RLock()
|
||||
if f.closing.Load() {
|
||||
f.wgLock.RUnlock()
|
||||
return nil, ErrFileClosed
|
||||
}
|
||||
f.wg.Add(1)
|
||||
f.wgLock.RUnlock()
|
||||
c := &ioOperation{}
|
||||
c.ch = make(chan ioResult)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// ioCompletionProcessor processes completed async IOs forever.
|
||||
func ioCompletionProcessor(h windows.Handle) {
|
||||
for {
|
||||
var bytes uint32
|
||||
var key uintptr
|
||||
var op *ioOperation
|
||||
err := getQueuedCompletionStatus(h, &bytes, &key, &op, windows.INFINITE)
|
||||
if op == nil {
|
||||
panic(err)
|
||||
}
|
||||
op.ch <- ioResult{bytes, err}
|
||||
}
|
||||
}
|
||||
|
||||
// todo: helsaawy - create an asyncIO version that takes a context
|
||||
|
||||
// asyncIO processes the return value from ReadFile or WriteFile, blocking until
|
||||
// the operation has actually completed.
|
||||
func (f *win32File) asyncIO(c *ioOperation, d *deadlineHandler, bytes uint32, err error) (int, error) {
|
||||
if err != windows.ERROR_IO_PENDING { //nolint:errorlint // err is Errno
|
||||
return int(bytes), err
|
||||
}
|
||||
|
||||
if f.closing.Load() {
|
||||
_ = cancelIoEx(f.handle, &c.o)
|
||||
}
|
||||
|
||||
var timeout timeoutChan
|
||||
if d != nil {
|
||||
d.channelLock.Lock()
|
||||
timeout = d.channel
|
||||
d.channelLock.Unlock()
|
||||
}
|
||||
|
||||
var r ioResult
|
||||
select {
|
||||
case r = <-c.ch:
|
||||
err = r.err
|
||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||
if f.closing.Load() {
|
||||
err = ErrFileClosed
|
||||
}
|
||||
} else if err != nil && f.socket {
|
||||
// err is from Win32. Query the overlapped structure to get the winsock error.
|
||||
var bytes, flags uint32
|
||||
err = wsaGetOverlappedResult(f.handle, &c.o, &bytes, false, &flags)
|
||||
}
|
||||
case <-timeout:
|
||||
_ = cancelIoEx(f.handle, &c.o)
|
||||
r = <-c.ch
|
||||
err = r.err
|
||||
if err == windows.ERROR_OPERATION_ABORTED { //nolint:errorlint // err is Errno
|
||||
err = ErrTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// runtime.KeepAlive is needed, as c is passed via native
|
||||
// code to ioCompletionProcessor, c must remain alive
|
||||
// until the channel read is complete.
|
||||
// todo: (de)allocate *ioOperation via win32 heap functions, instead of needing to KeepAlive?
|
||||
runtime.KeepAlive(c)
|
||||
return int(r.bytes), err
|
||||
}
|
||||
|
||||
// Read reads from a file handle.
|
||||
func (f *win32File) Read(b []byte) (int, error) {
|
||||
c, err := f.prepareIO()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.wg.Done()
|
||||
|
||||
if f.readDeadline.timedout.Load() {
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
|
||||
var bytes uint32
|
||||
err = windows.ReadFile(f.handle, b, &bytes, &c.o)
|
||||
n, err := f.asyncIO(c, &f.readDeadline, bytes, err)
|
||||
runtime.KeepAlive(b)
|
||||
|
||||
// Handle EOF conditions.
|
||||
if err == nil && n == 0 && len(b) != 0 {
|
||||
return 0, io.EOF
|
||||
} else if err == windows.ERROR_BROKEN_PIPE { //nolint:errorlint // err is Errno
|
||||
return 0, io.EOF
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write writes to a file handle.
|
||||
func (f *win32File) Write(b []byte) (int, error) {
|
||||
c, err := f.prepareIO()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.wg.Done()
|
||||
|
||||
if f.writeDeadline.timedout.Load() {
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
|
||||
var bytes uint32
|
||||
err = windows.WriteFile(f.handle, b, &bytes, &c.o)
|
||||
n, err := f.asyncIO(c, &f.writeDeadline, bytes, err)
|
||||
runtime.KeepAlive(b)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (f *win32File) SetReadDeadline(deadline time.Time) error {
|
||||
return f.readDeadline.set(deadline)
|
||||
}
|
||||
|
||||
func (f *win32File) SetWriteDeadline(deadline time.Time) error {
|
||||
return f.writeDeadline.set(deadline)
|
||||
}
|
||||
|
||||
func (f *win32File) Flush() error {
|
||||
return windows.FlushFileBuffers(f.handle)
|
||||
}
|
||||
|
||||
func (f *win32File) Fd() uintptr {
|
||||
return uintptr(f.handle)
|
||||
}
|
||||
|
||||
func (d *deadlineHandler) set(deadline time.Time) error {
|
||||
d.setLock.Lock()
|
||||
defer d.setLock.Unlock()
|
||||
|
||||
if d.timer != nil {
|
||||
if !d.timer.Stop() {
|
||||
<-d.channel
|
||||
}
|
||||
d.timer = nil
|
||||
}
|
||||
d.timedout.Store(false)
|
||||
|
||||
select {
|
||||
case <-d.channel:
|
||||
d.channelLock.Lock()
|
||||
d.channel = make(chan struct{})
|
||||
d.channelLock.Unlock()
|
||||
default:
|
||||
}
|
||||
|
||||
if deadline.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
timeoutIO := func() {
|
||||
d.timedout.Store(true)
|
||||
close(d.channel)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
duration := deadline.Sub(now)
|
||||
if deadline.After(now) {
|
||||
// Deadline is in the future, set a timer to wait
|
||||
d.timer = time.AfterFunc(duration, timeoutIO)
|
||||
} else {
|
||||
// Deadline is in the past. Cancel all pending IO now.
|
||||
timeoutIO()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
106
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
106
vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package winio
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// FileBasicInfo contains file access time and file attributes information.
|
||||
type FileBasicInfo struct {
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime windows.Filetime
|
||||
FileAttributes uint32
|
||||
_ uint32 // padding
|
||||
}
|
||||
|
||||
// alignedFileBasicInfo is a FileBasicInfo, but aligned to uint64 by containing
|
||||
// uint64 rather than windows.Filetime. Filetime contains two uint32s. uint64
|
||||
// alignment is necessary to pass this as FILE_BASIC_INFO.
|
||||
type alignedFileBasicInfo struct {
|
||||
CreationTime, LastAccessTime, LastWriteTime, ChangeTime uint64
|
||||
FileAttributes uint32
|
||||
_ uint32 // padding
|
||||
}
|
||||
|
||||
// GetFileBasicInfo retrieves times and attributes for a file.
|
||||
func GetFileBasicInfo(f *os.File) (*FileBasicInfo, error) {
|
||||
bi := &alignedFileBasicInfo{}
|
||||
if err := windows.GetFileInformationByHandleEx(
|
||||
windows.Handle(f.Fd()),
|
||||
windows.FileBasicInfo,
|
||||
(*byte)(unsafe.Pointer(bi)),
|
||||
uint32(unsafe.Sizeof(*bi)),
|
||||
); err != nil {
|
||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
// Reinterpret the alignedFileBasicInfo as a FileBasicInfo so it matches the
|
||||
// public API of this module. The data may be unnecessarily aligned.
|
||||
return (*FileBasicInfo)(unsafe.Pointer(bi)), nil
|
||||
}
|
||||
|
||||
// SetFileBasicInfo sets times and attributes for a file.
|
||||
func SetFileBasicInfo(f *os.File, bi *FileBasicInfo) error {
|
||||
// Create an alignedFileBasicInfo based on a FileBasicInfo. The copy is
|
||||
// suitable to pass to GetFileInformationByHandleEx.
|
||||
biAligned := *(*alignedFileBasicInfo)(unsafe.Pointer(bi))
|
||||
if err := windows.SetFileInformationByHandle(
|
||||
windows.Handle(f.Fd()),
|
||||
windows.FileBasicInfo,
|
||||
(*byte)(unsafe.Pointer(&biAligned)),
|
||||
uint32(unsafe.Sizeof(biAligned)),
|
||||
); err != nil {
|
||||
return &os.PathError{Op: "SetFileInformationByHandle", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileStandardInfo contains extended information for the file.
|
||||
// FILE_STANDARD_INFO in WinBase.h
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_standard_info
|
||||
type FileStandardInfo struct {
|
||||
AllocationSize, EndOfFile int64
|
||||
NumberOfLinks uint32
|
||||
DeletePending, Directory bool
|
||||
}
|
||||
|
||||
// GetFileStandardInfo retrieves ended information for the file.
|
||||
func GetFileStandardInfo(f *os.File) (*FileStandardInfo, error) {
|
||||
si := &FileStandardInfo{}
|
||||
if err := windows.GetFileInformationByHandleEx(windows.Handle(f.Fd()),
|
||||
windows.FileStandardInfo,
|
||||
(*byte)(unsafe.Pointer(si)),
|
||||
uint32(unsafe.Sizeof(*si))); err != nil {
|
||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
return si, nil
|
||||
}
|
||||
|
||||
// FileIDInfo contains the volume serial number and file ID for a file. This pair should be
|
||||
// unique on a system.
|
||||
type FileIDInfo struct {
|
||||
VolumeSerialNumber uint64
|
||||
FileID [16]byte
|
||||
}
|
||||
|
||||
// GetFileID retrieves the unique (volume, file ID) pair for a file.
|
||||
func GetFileID(f *os.File) (*FileIDInfo, error) {
|
||||
fileID := &FileIDInfo{}
|
||||
if err := windows.GetFileInformationByHandleEx(
|
||||
windows.Handle(f.Fd()),
|
||||
windows.FileIdInfo,
|
||||
(*byte)(unsafe.Pointer(fileID)),
|
||||
uint32(unsafe.Sizeof(*fileID)),
|
||||
); err != nil {
|
||||
return nil, &os.PathError{Op: "GetFileInformationByHandleEx", Path: f.Name(), Err: err}
|
||||
}
|
||||
runtime.KeepAlive(f)
|
||||
return fileID, nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user