added vendor deps
This commit is contained in:
parent
104e143fd4
commit
4c422a5ae3
4919 changed files with 7766135 additions and 0 deletions
12
.gear/predownloaded-development/vendor/emperror.dev/errors/.editorconfig
vendored
Normal file
12
.gear/predownloaded-development/vendor/emperror.dev/errors/.editorconfig
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.go]
|
||||||
|
indent_style = tab
|
||||||
14
.gear/predownloaded-development/vendor/emperror.dev/errors/.fossa.yml
vendored
Normal file
14
.gear/predownloaded-development/vendor/emperror.dev/errors/.fossa.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
version: 2
|
||||||
|
cli:
|
||||||
|
server: https://app.fossa.com
|
||||||
|
fetcher: custom
|
||||||
|
project: emperror.dev/errors
|
||||||
|
analyze:
|
||||||
|
modules:
|
||||||
|
- name: emperror.dev/errors
|
||||||
|
type: go
|
||||||
|
target: emperror.dev/errors
|
||||||
|
strategy: gomodules
|
||||||
|
path: .
|
||||||
|
options:
|
||||||
|
allow-unresolved: true
|
||||||
6
.gear/predownloaded-development/vendor/emperror.dev/errors/.gitignore
vendored
Normal file
6
.gear/predownloaded-development/vendor/emperror.dev/errors/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
/tmp/
|
||||||
|
/vendor/
|
||||||
|
|
||||||
|
# Please output directory and local configuration
|
||||||
|
plz-out
|
||||||
|
.plzconfig.local
|
||||||
86
.gear/predownloaded-development/vendor/emperror.dev/errors/.golangci.yml
vendored
Normal file
86
.gear/predownloaded-development/vendor/emperror.dev/errors/.golangci.yml
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
run:
|
||||||
|
skip-dirs:
|
||||||
|
- tests
|
||||||
|
skip-files:
|
||||||
|
- format_test.go
|
||||||
|
- wrap_go1_12.go
|
||||||
|
- wrap_go1_12_test.go
|
||||||
|
- wrap_test.go
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gci:
|
||||||
|
local-prefixes: emperror.dev/errors
|
||||||
|
goimports:
|
||||||
|
local-prefixes: emperror.dev/errors
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- bodyclose
|
||||||
|
- deadcode
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- exhaustive
|
||||||
|
- exportloopref
|
||||||
|
- gci
|
||||||
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
|
- gocognit
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- godot
|
||||||
|
- gofmt
|
||||||
|
- gofumpt
|
||||||
|
- goimports
|
||||||
|
- golint
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- lll
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nlreturn
|
||||||
|
- noctx
|
||||||
|
- nolintlint
|
||||||
|
- prealloc
|
||||||
|
- rowserrcheck
|
||||||
|
- scopelint
|
||||||
|
- sqlclosecheck
|
||||||
|
- staticcheck
|
||||||
|
- structcheck
|
||||||
|
- stylecheck
|
||||||
|
- typecheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
- whitespace
|
||||||
|
|
||||||
|
# unused
|
||||||
|
# - depguard
|
||||||
|
# - goheader
|
||||||
|
# - gomodguard
|
||||||
|
|
||||||
|
# don't enable:
|
||||||
|
# - asciicheck
|
||||||
|
# - funlen
|
||||||
|
# - godox
|
||||||
|
# - goerr113
|
||||||
|
# - gomnd
|
||||||
|
# - interfacer
|
||||||
|
# - maligned
|
||||||
|
# - nestif
|
||||||
|
# - testpackage
|
||||||
|
# - wsl
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- text: "package comment should not have leading space"
|
||||||
|
linters:
|
||||||
|
- golint
|
||||||
21
.gear/predownloaded-development/vendor/emperror.dev/errors/.plzconfig
vendored
Normal file
21
.gear/predownloaded-development/vendor/emperror.dev/errors/.plzconfig
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
[please]
|
||||||
|
version = 15.5.0
|
||||||
|
|
||||||
|
[go]
|
||||||
|
importpath = emperror.dev/errors
|
||||||
|
|
||||||
|
[buildconfig]
|
||||||
|
golangci-lint-version = 1.31.0
|
||||||
|
gotestsum-version = 0.5.3
|
||||||
|
|
||||||
|
[alias "lint"]
|
||||||
|
desc = Runs linters for this repo
|
||||||
|
cmd = run ///pleasings2//tools/go:golangci-lint -- run
|
||||||
|
|
||||||
|
[alias "gotest"]
|
||||||
|
desc = Runs tests for this repo
|
||||||
|
cmd = run ///pleasings2//tools/go:gotestsum -- --no-summary=skipped --format short -- -race -covermode=atomic -coverprofile=plz-out/log/coverage.txt ./...
|
||||||
|
|
||||||
|
[alias "release"]
|
||||||
|
desc = Release a new version
|
||||||
|
cmd = run ///pleasings2//tools/misc:releaser --
|
||||||
21
.gear/predownloaded-development/vendor/emperror.dev/errors/BUILD
vendored
Normal file
21
.gear/predownloaded-development/vendor/emperror.dev/errors/BUILD
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
github_repo(
|
||||||
|
name = "pleasings2",
|
||||||
|
repo = "sagikazarmark/mypleasings",
|
||||||
|
revision = "f8a12721c6f929db3e227e07c152d428ac47ab1b",
|
||||||
|
)
|
||||||
|
|
||||||
|
sh_cmd(
|
||||||
|
name = "download_tests",
|
||||||
|
shell = "/usr/bin/env bash",
|
||||||
|
labels = ["manual"],
|
||||||
|
cmd = """
|
||||||
|
mkdir -p tests
|
||||||
|
# curl https://raw.githubusercontent.com/pkg/errors/master/errors_test.go | sed \\\\$'s|"testing"|"testing"\\n\\n\\t. "emperror.dev/errors"|; s|github.com/pkg/errors|emperror.dev/errors/tests|g' > tests/errors_test.go
|
||||||
|
curl https://raw.githubusercontent.com/pkg/errors/master/errors_test.go | sed \\\\$'s|"errors"|. "emperror.dev/errors"|; s|/github.com/pkg/errors||g; s|github.com/pkg/errors|emperror.dev/errors/tests|g; s|errors\.New|NewPlain|g; s|x := New("error")|x := NewPlain("error")|g' > tests/errors_test.go
|
||||||
|
curl https://raw.githubusercontent.com/pkg/errors/master/example_test.go | sed 's|"github.com/pkg/errors"|"emperror.dev/errors"|' > tests/example_test.go
|
||||||
|
curl https://raw.githubusercontent.com/pkg/errors/master/format_test.go | sed \\\\$'s|"errors"|. "emperror.dev/errors"|; s|/github.com/pkg/errors||g; s|github.com/pkg/errors|emperror.dev/errors/tests|g; s|errors\.New|NewPlain|g' > tests/format_test.go
|
||||||
|
|
||||||
|
curl https://raw.githubusercontent.com/golang/go/master/src/errors/errors_test.go | sed \\\\$'s|"errors"|"emperror.dev/errors"|; s|errors\.New|errors.NewPlain|g; s|"fmt"||g' | head -35 > tests/errors_std_test.go
|
||||||
|
echo -e "// +build go1.13\n\n\\\$(curl https://raw.githubusercontent.com/golang/go/master/src/errors/wrap_test.go)" | sed \\\\$'s|"errors"|"emperror.dev/errors"|; s|errors\.New|errors.NewPlain|g' > tests/wrap_test.go
|
||||||
|
""",
|
||||||
|
)
|
||||||
125
.gear/predownloaded-development/vendor/emperror.dev/errors/CHANGELOG.md
vendored
Normal file
125
.gear/predownloaded-development/vendor/emperror.dev/errors/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
# Change Log
|
||||||
|
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
|
||||||
|
## [0.8.1] - 2022-02-23
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Reduced memory allocation when collecting stack information
|
||||||
|
|
||||||
|
|
||||||
|
## [0.8.0] - 2020-09-14
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
|
||||||
|
## [0.7.0] - 2020-01-13
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated dependencies ([github.com/pkg/errors](https://github.com/pkg/errors))
|
||||||
|
- Use `WithMessage` and `WithMessagef` from [github.com/pkg/errors](https://github.com/pkg/errors)
|
||||||
|
|
||||||
|
|
||||||
|
## [0.6.0] - 2020-01-09
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
|
||||||
|
|
||||||
|
## [0.5.2] - 2020-01-06
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- `match`: exported `ErrorMatcherFunc`
|
||||||
|
|
||||||
|
|
||||||
|
## [0.5.1] - 2020-01-06
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- `match`: race condition in `As`
|
||||||
|
|
||||||
|
|
||||||
|
## [0.5.0] - 2020-01-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `match` package for matching errors
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.3] - 2019-09-05
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `Sentinel` error type for creating [constant error](https://dave.cheney.net/2016/04/07/constant-errors)
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.2] - 2019-07-19
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `NewWithDetails` function to create a new error with details attached
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.1] - 2019-07-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `utils/keyval` package to work with key-value pairs.
|
||||||
|
|
||||||
|
|
||||||
|
## [0.4.0] - 2019-07-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Error details
|
||||||
|
|
||||||
|
|
||||||
|
## [0.3.0] - 2019-07-14
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Multi error
|
||||||
|
- `UnwrapEach` function
|
||||||
|
|
||||||
|
|
||||||
|
## [0.2.0] - 2019-07-12
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `*If` functions that only annotate an error with a stack trace if there isn't one already in the error chain
|
||||||
|
|
||||||
|
|
||||||
|
## [0.1.0] - 2019-07-12
|
||||||
|
|
||||||
|
- Initial release
|
||||||
|
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/emperror/errors/compare/v0.8.1...HEAD
|
||||||
|
[0.8.1]: https://github.com/emperror/errors/compare/v0.8.0...v0.8.1
|
||||||
|
[0.8.0]: https://github.com/emperror/errors/compare/v0.7.0...v0.8.0
|
||||||
|
[0.7.0]: https://github.com/emperror/errors/compare/v0.6.0...v0.7.0
|
||||||
|
[0.6.0]: https://github.com/emperror/errors/compare/v0.5.2...v0.6.0
|
||||||
|
[0.5.2]: https://github.com/emperror/errors/compare/v0.5.1...v0.5.2
|
||||||
|
[0.5.1]: https://github.com/emperror/errors/compare/v0.5.0...v0.5.1
|
||||||
|
[0.5.0]: https://github.com/emperror/errors/compare/v0.4.3...v0.5.0
|
||||||
|
[0.4.3]: https://github.com/emperror/errors/compare/v0.4.2...v0.4.3
|
||||||
|
[0.4.2]: https://github.com/emperror/errors/compare/v0.4.1...v0.4.2
|
||||||
|
[0.4.1]: https://github.com/emperror/errors/compare/v0.4.0...v0.4.1
|
||||||
|
[0.4.0]: https://github.com/emperror/errors/compare/v0.3.0...v0.4.0
|
||||||
|
[0.3.0]: https://github.com/emperror/errors/compare/v0.2.0...v0.3.0
|
||||||
|
[0.2.0]: https://github.com/emperror/errors/compare/v0.1.0...v0.2.0
|
||||||
|
[0.1.0]: https://github.com/emperror/errors/compare/v0.0.0...v0.1.0
|
||||||
19
.gear/predownloaded-development/vendor/emperror.dev/errors/LICENSE
vendored
Normal file
19
.gear/predownloaded-development/vendor/emperror.dev/errors/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2019 Márk Sági-Kazár <mark.sagikazar@gmail.com>
|
||||||
|
|
||||||
|
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.
|
||||||
57
.gear/predownloaded-development/vendor/emperror.dev/errors/LICENSE_THIRD_PARTY
vendored
Normal file
57
.gear/predownloaded-development/vendor/emperror.dev/errors/LICENSE_THIRD_PARTY
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
Certain parts of this library are inspired by (or entirely copied from) various third party libraries.
|
||||||
|
This file contains their licenses.
|
||||||
|
|
||||||
|
github.com/pkg/errors:
|
||||||
|
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
github.com/golang/xerrors:
|
||||||
|
Copyright (c) 2019 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
145
.gear/predownloaded-development/vendor/emperror.dev/errors/README.md
vendored
Normal file
145
.gear/predownloaded-development/vendor/emperror.dev/errors/README.md
vendored
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
# Emperror: Errors [](https://github.com/avelino/awesome-go#error-handling)
|
||||||
|
|
||||||
|
[](https://github.com/emperror/errors/actions?query=workflow%3ACI)
|
||||||
|
[](https://codecov.io/gh/emperror/errors)
|
||||||
|
[](https://goreportcard.com/report/emperror.dev/errors)
|
||||||
|

|
||||||
|
[](https://pkg.go.dev/mod/emperror.dev/errors)
|
||||||
|
[](https://app.fossa.com/projects/custom%2B8125%2Femperror.dev%2Ferrors?ref=badge_shield)
|
||||||
|
|
||||||
|
|
||||||
|
**Drop-in replacement for the standard library `errors` package and [github.com/pkg/errors](https://github.com/pkg/errors).**
|
||||||
|
|
||||||
|
This is a single, lightweight library merging the features of standard library `errors` package
|
||||||
|
and [github.com/pkg/errors](https://github.com/pkg/errors). It also backports a few features
|
||||||
|
(like Go 1.13 error handling related features).
|
||||||
|
|
||||||
|
Standard library features:
|
||||||
|
- `New` creates an error with stack trace
|
||||||
|
- `Unwrap` supports both Go 1.13 wrapper (`interface { Unwrap() error }`) and **pkg/errors** causer (`interface { Cause() error }`) interface
|
||||||
|
- Backported `Is` and `As` functions
|
||||||
|
|
||||||
|
[github.com/pkg/errors](https://github.com/pkg/errors) features:
|
||||||
|
- `New`, `Errorf`, `WithMessage`, `WithMessagef`, `WithStack`, `Wrap`, `Wrapf` functions behave the same way as in the original library
|
||||||
|
- `Cause` supports both Go 1.13 wrapper (`interface { Unwrap() error }`) and **pkg/errors** causer (`interface { Cause() error }`) interface
|
||||||
|
|
||||||
|
Additional features:
|
||||||
|
- `NewPlain` creates a new error without any attached context, like stack trace
|
||||||
|
- `Sentinel` is a shorthand type for creating [constant error](https://dave.cheney.net/2016/04/07/constant-errors)
|
||||||
|
- `WithStackDepth` allows attaching stack trace with a custom caller depth
|
||||||
|
- `WithStackDepthIf`, `WithStackIf`, `WrapIf`, `WrapIff` only annotate errors with a stack trace if there isn't one already in the error chain
|
||||||
|
- Multi error aggregating multiple errors into a single value
|
||||||
|
- `NewWithDetails`, `WithDetails` and `Wrap*WithDetails` functions to add key-value pairs to an error
|
||||||
|
- Match errors using the `match` package
|
||||||
|
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get emperror.dev/errors
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "emperror.dev/errors"
|
||||||
|
|
||||||
|
// ErrSomethingWentWrong is a sentinel error which can be useful within a single API layer.
|
||||||
|
const ErrSomethingWentWrong = errors.Sentinel("something went wrong")
|
||||||
|
|
||||||
|
// ErrMyError is an error that can be returned from a public API.
|
||||||
|
type ErrMyError struct {
|
||||||
|
Msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrMyError) Error() string {
|
||||||
|
return e.Msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func foo() error {
|
||||||
|
// Attach stack trace to the sentinel error.
|
||||||
|
return errors.WithStack(ErrSomethingWentWrong)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bar() error {
|
||||||
|
return errors.Wrap(ErrMyError{"something went wrong"}, "error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := foo(); err != nil {
|
||||||
|
if errors.Cause(err) == ErrSomethingWentWrong { // or errors.Is(ErrSomethingWentWrong)
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bar(); err != nil {
|
||||||
|
if errors.As(err, &ErrMyError{}) {
|
||||||
|
// handle error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Match errors:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"emperror.dev/errors"
|
||||||
|
"emperror.dev/errors/match"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrSomethingWentWrong is a sentinel error which can be useful within a single API layer.
|
||||||
|
const ErrSomethingWentWrong = errors.Sentinel("something went wrong")
|
||||||
|
|
||||||
|
type clientError interface{
|
||||||
|
ClientError() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func foo() error {
|
||||||
|
// Attach stack trace to the sentinel error.
|
||||||
|
return errors.WithStack(ErrSomethingWentWrong)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var ce clientError
|
||||||
|
matcher := match.Any{match.As(&ce), match.Is(ErrSomethingWentWrong)}
|
||||||
|
|
||||||
|
if err := foo(); err != nil {
|
||||||
|
if matcher.MatchError(err) {
|
||||||
|
// you can use matchers to write complex conditions for handling (or not) an error
|
||||||
|
// used in emperror
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Contributions are welcome! :)
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
1. Make changes on a new branch
|
||||||
|
1. Run the test suite:
|
||||||
|
```bash
|
||||||
|
./pleasew build
|
||||||
|
./pleasew test
|
||||||
|
./pleasew gotest
|
||||||
|
./pleasew lint
|
||||||
|
```
|
||||||
|
1. Commit, push and open a PR
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The MIT License (MIT). Please see [License File](LICENSE) for more information.
|
||||||
|
|
||||||
|
Certain parts of this library are inspired by (or entirely copied from) various third party libraries.
|
||||||
|
Their licenses can be found in the [Third Party License File](LICENSE_THIRD_PARTY).
|
||||||
|
|
||||||
|
[](https://app.fossa.com/projects/custom%2B8125%2Femperror.dev%2Ferrors?ref=badge_large)
|
||||||
88
.gear/predownloaded-development/vendor/emperror.dev/errors/error_details.go
vendored
Normal file
88
.gear/predownloaded-development/vendor/emperror.dev/errors/error_details.go
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithDetails annotates err with with arbitrary key-value pairs.
|
||||||
|
func WithDetails(err error, details ...interface{}) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(details) == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(details)%2 != 0 {
|
||||||
|
details = append(details, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var w *withDetails
|
||||||
|
if !As(err, &w) {
|
||||||
|
w = &withDetails{
|
||||||
|
error: err,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limiting the capacity of the stored keyvals ensures that a new
|
||||||
|
// backing array is created if the slice must grow in With.
|
||||||
|
// Using the extra capacity without copying risks a data race.
|
||||||
|
d := append(w.details, details...)
|
||||||
|
w.details = d[:len(d):len(d)]
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDetails extracts the key-value pairs from err's chain.
|
||||||
|
func GetDetails(err error) []interface{} {
|
||||||
|
var details []interface{}
|
||||||
|
|
||||||
|
// Usually there is only one error with details (when using the WithDetails API),
|
||||||
|
// but errors themselves can also implement the details interface exposing their attributes.
|
||||||
|
UnwrapEach(err, func(err error) bool {
|
||||||
|
if derr, ok := err.(interface{ Details() []interface{} }); ok {
|
||||||
|
details = append(derr.Details(), details...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return details
|
||||||
|
}
|
||||||
|
|
||||||
|
// withDetails annotates an error with arbitrary key-value pairs.
|
||||||
|
type withDetails struct {
|
||||||
|
error error
|
||||||
|
details []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withDetails) Error() string { return w.error.Error() }
|
||||||
|
func (w *withDetails) Cause() error { return w.error }
|
||||||
|
func (w *withDetails) Unwrap() error { return w.error }
|
||||||
|
|
||||||
|
// Details returns the appended details.
|
||||||
|
func (w *withDetails) Details() []interface{} {
|
||||||
|
return w.details
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withDetails) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
_, _ = fmt.Fprintf(s, "%+v", w.error)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(s, "%v", w.error)
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
_, _ = fmt.Fprintf(s, "%s", w.error)
|
||||||
|
|
||||||
|
case 'q':
|
||||||
|
_, _ = fmt.Fprintf(s, "%q", w.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
245
.gear/predownloaded-development/vendor/emperror.dev/errors/errors.go
vendored
Normal file
245
.gear/predownloaded-development/vendor/emperror.dev/errors/errors.go
vendored
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
/*
|
||||||
|
Package errors is a drop-in replacement for the standard errors package and github.com/pkg/errors.
|
||||||
|
|
||||||
|
|
||||||
|
Overview
|
||||||
|
|
||||||
|
This is a single, lightweight library merging the features of standard library `errors` package
|
||||||
|
and https://github.com/pkg/errors. It also backports a few features
|
||||||
|
(like Go 1.13 error handling related features).
|
||||||
|
|
||||||
|
|
||||||
|
Printing errors
|
||||||
|
|
||||||
|
If not stated otherwise, errors can be formatted with the following specifiers:
|
||||||
|
%s error message
|
||||||
|
%q double-quoted error message
|
||||||
|
%v error message in default format
|
||||||
|
%+v error message and stack trace
|
||||||
|
*/
|
||||||
|
package errors // import "emperror.dev/errors"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewPlain returns a simple error without any annotated context, like stack trace.
|
||||||
|
// Useful for creating sentinel errors and in testing.
|
||||||
|
//
|
||||||
|
// var ErrSomething = errors.NewPlain("something went wrong")
|
||||||
|
func NewPlain(message string) error {
|
||||||
|
return &plainError{message}
|
||||||
|
}
|
||||||
|
|
||||||
|
// plainError is a trivial implementation of error.
|
||||||
|
type plainError struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *plainError) Error() string {
|
||||||
|
return e.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sentinel is a simple error without any annotated context, like stack trace.
|
||||||
|
// Useful for creating sentinel errors.
|
||||||
|
//
|
||||||
|
// const ErrSomething = errors.Sentinel("something went wrong")
|
||||||
|
//
|
||||||
|
// See https://dave.cheney.net/2016/04/07/constant-errors
|
||||||
|
type Sentinel string
|
||||||
|
|
||||||
|
func (e Sentinel) Error() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new error annotated with stack trace at the point New is called.
|
||||||
|
//
|
||||||
|
// New is a shorthand for:
|
||||||
|
// WithStack(NewPlain(message))
|
||||||
|
func New(message string) error {
|
||||||
|
return WithStackDepth(NewPlain(message), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithDetails returns a new error annotated with stack trace at the point NewWithDetails is called,
|
||||||
|
// and the supplied details.
|
||||||
|
func NewWithDetails(message string, details ...interface{}) error {
|
||||||
|
return WithDetails(WithStackDepth(NewPlain(message), 1), details...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf returns a new error with a formatted message and annotated with stack trace at the point Errorf is called.
|
||||||
|
//
|
||||||
|
// err := errors.Errorf("something went %s", "wrong")
|
||||||
|
func Errorf(format string, a ...interface{}) error {
|
||||||
|
return WithStackDepth(NewPlain(fmt.Sprintf(format, a...)), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||||
|
// If err is nil, WithStack returns nil.
|
||||||
|
//
|
||||||
|
// WithStack is commonly used with sentinel errors and errors returned from libraries
|
||||||
|
// not annotating errors with stack trace:
|
||||||
|
//
|
||||||
|
// var ErrSomething = errors.NewPlain("something went wrong")
|
||||||
|
//
|
||||||
|
// func doSomething() error {
|
||||||
|
// return errors.WithStack(ErrSomething)
|
||||||
|
// }
|
||||||
|
func WithStack(err error) error {
|
||||||
|
return WithStackDepth(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStackDepth annotates err with a stack trace at the given call depth.
|
||||||
|
// Zero identifies the caller of WithStackDepth itself.
|
||||||
|
// If err is nil, WithStackDepth returns nil.
|
||||||
|
//
|
||||||
|
// WithStackDepth is generally used in other error constructors:
|
||||||
|
//
|
||||||
|
// func MyWrapper(err error) error {
|
||||||
|
// return WithStackDepth(err, 1)
|
||||||
|
// }
|
||||||
|
func WithStackDepth(err error, depth int) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &withStack{
|
||||||
|
error: err,
|
||||||
|
stack: callers(depth + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStackIf behaves the same way as WithStack except it does not annotate the error with a stack trace
|
||||||
|
// if there is already one in err's chain.
|
||||||
|
func WithStackIf(err error) error {
|
||||||
|
return WithStackDepthIf(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStackDepthIf behaves the same way as WithStackDepth except it does not annotate the error with a stack trace
|
||||||
|
// if there is already one in err's chain.
|
||||||
|
func WithStackDepthIf(err error, depth int) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var st interface{ StackTrace() errors.StackTrace }
|
||||||
|
if !As(err, &st) {
|
||||||
|
return &withStack{
|
||||||
|
error: err,
|
||||||
|
stack: callers(depth + 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type withStack struct {
|
||||||
|
error
|
||||||
|
*stack
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *withStack) Cause() error { return w.error }
|
||||||
|
func (w *withStack) Unwrap() error { return w.error }
|
||||||
|
|
||||||
|
// nolint: errcheck
|
||||||
|
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
if s.Flag('+') {
|
||||||
|
fmt.Fprintf(s, "%+v", w.error)
|
||||||
|
w.stack.Format(s, verb)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fallthrough
|
||||||
|
case 's':
|
||||||
|
io.WriteString(s, w.Error())
|
||||||
|
case 'q':
|
||||||
|
fmt.Fprintf(s, "%q", w.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessage annotates err with a new message.
|
||||||
|
// If err is nil, WithMessage returns nil.
|
||||||
|
//
|
||||||
|
// WithMessage is useful when the error already contains a stack trace, but adding additional info to the message
|
||||||
|
// helps in debugging.
|
||||||
|
//
|
||||||
|
// Errors returned by WithMessage are formatted slightly differently:
|
||||||
|
// %s error messages separated by a colon and a space (": ")
|
||||||
|
// %q double-quoted error messages separated by a colon and a space (": ")
|
||||||
|
// %v one error message per line
|
||||||
|
// %+v one error message per line and stack trace (if any)
|
||||||
|
func WithMessage(err error, message string) error {
|
||||||
|
return errors.WithMessage(err, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithMessagef annotates err with the format specifier.
|
||||||
|
// If err is nil, WithMessagef returns nil.
|
||||||
|
//
|
||||||
|
// WithMessagef is useful when the error already contains a stack trace, but adding additional info to the message
|
||||||
|
// helps in debugging.
|
||||||
|
//
|
||||||
|
// The same formatting rules apply as in case of WithMessage.
|
||||||
|
func WithMessagef(err error, format string, a ...interface{}) error {
|
||||||
|
return errors.WithMessagef(err, format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrap is called, and the supplied message.
|
||||||
|
// If err is nil, Wrap returns nil.
|
||||||
|
//
|
||||||
|
// Wrap is a shorthand for:
|
||||||
|
// WithStack(WithMessage(err, message))
|
||||||
|
func Wrap(err error, message string) error {
|
||||||
|
return WithStackDepth(WithMessage(err, message), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf returns an error annotating err with a stack trace
|
||||||
|
// at the point Wrapf is called, and the format specifier.
|
||||||
|
// If err is nil, Wrapf returns nil.
|
||||||
|
//
|
||||||
|
// Wrapf is a shorthand for:
|
||||||
|
// WithStack(WithMessagef(err, format, a...))
|
||||||
|
func Wrapf(err error, format string, a ...interface{}) error {
|
||||||
|
return WithStackDepth(WithMessagef(err, format, a...), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapIf behaves the same way as Wrap except it does not annotate the error with a stack trace
|
||||||
|
// if there is already one in err's chain.
|
||||||
|
//
|
||||||
|
// If err is nil, WrapIf returns nil.
|
||||||
|
func WrapIf(err error, message string) error {
|
||||||
|
return WithStackDepthIf(WithMessage(err, message), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapIff behaves the same way as Wrapf except it does not annotate the error with a stack trace
|
||||||
|
// if there is already one in err's chain.
|
||||||
|
//
|
||||||
|
// If err is nil, WrapIff returns nil.
|
||||||
|
func WrapIff(err error, format string, a ...interface{}) error {
|
||||||
|
return WithStackDepthIf(WithMessagef(err, format, a...), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapWithDetails returns an error annotating err with a stack trace
|
||||||
|
// at the point WrapWithDetails is called, and the supplied message and details.
|
||||||
|
// If err is nil, WrapWithDetails returns nil.
|
||||||
|
//
|
||||||
|
// WrapWithDetails is a shorthand for:
|
||||||
|
// WithDetails(WithStack(WithMessage(err, message, details...))
|
||||||
|
func WrapWithDetails(err error, message string, details ...interface{}) error {
|
||||||
|
return WithDetails(WithStackDepth(WithMessage(err, message), 1), details...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapIfWithDetails returns an error annotating err with a stack trace
|
||||||
|
// at the point WrapIfWithDetails is called, and the supplied message and details.
|
||||||
|
// If err is nil, WrapIfWithDetails returns nil.
|
||||||
|
//
|
||||||
|
// WrapIfWithDetails is a shorthand for:
|
||||||
|
// WithDetails(WithStackIf(WithMessage(err, message, details...))
|
||||||
|
func WrapIfWithDetails(err error, message string, details ...interface{}) error {
|
||||||
|
return WithDetails(WithStackDepthIf(WithMessage(err, message), 1), details...)
|
||||||
|
}
|
||||||
69
.gear/predownloaded-development/vendor/emperror.dev/errors/errors_multi.go
vendored
Normal file
69
.gear/predownloaded-development/vendor/emperror.dev/errors/errors_multi.go
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/multierr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Combine combines the passed errors into a single error.
|
||||||
|
//
|
||||||
|
// If zero arguments were passed or if all items are nil, a nil error is
|
||||||
|
// returned.
|
||||||
|
//
|
||||||
|
// If only a single error was passed, it is returned as-is.
|
||||||
|
//
|
||||||
|
// Combine omits nil errors so this function may be used to combine
|
||||||
|
// together errors from operations that fail independently of each other.
|
||||||
|
//
|
||||||
|
// errors.Combine(
|
||||||
|
// reader.Close(),
|
||||||
|
// writer.Close(),
|
||||||
|
// pipe.Close(),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// If any of the passed errors is already an aggregated error, it will be flattened along
|
||||||
|
// with the other errors.
|
||||||
|
//
|
||||||
|
// errors.Combine(errors.Combine(err1, err2), err3)
|
||||||
|
// // is the same as
|
||||||
|
// errors.Combine(err1, err2, err3)
|
||||||
|
//
|
||||||
|
// The returned error formats into a readable multi-line error message if
|
||||||
|
// formatted with %+v.
|
||||||
|
//
|
||||||
|
// fmt.Sprintf("%+v", errors.Combine(err1, err2))
|
||||||
|
func Combine(errors ...error) error {
|
||||||
|
return multierr.Combine(errors...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append appends the given errors together. Either value may be nil.
|
||||||
|
//
|
||||||
|
// This function is a specialization of Combine for the common case where
|
||||||
|
// there are only two errors.
|
||||||
|
//
|
||||||
|
// err = errors.Append(reader.Close(), writer.Close())
|
||||||
|
//
|
||||||
|
// The following pattern may also be used to record failure of deferred
|
||||||
|
// operations without losing information about the original error.
|
||||||
|
//
|
||||||
|
// func doSomething(..) (err error) {
|
||||||
|
// f := acquireResource()
|
||||||
|
// defer func() {
|
||||||
|
// err = errors.Append(err, f.Close())
|
||||||
|
// }()
|
||||||
|
func Append(left error, right error) error {
|
||||||
|
return multierr.Append(left, right)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrors returns a slice containing zero or more errors that the supplied
|
||||||
|
// error is composed of. If the error is nil, the returned slice is empty.
|
||||||
|
//
|
||||||
|
// err := errors.Append(r.Close(), w.Close())
|
||||||
|
// errors := errors.GetErrors(err)
|
||||||
|
//
|
||||||
|
// If the error is not composed of other errors, the returned slice contains
|
||||||
|
// just the error that was passed in.
|
||||||
|
//
|
||||||
|
// Callers of this function are free to modify the returned slice.
|
||||||
|
func GetErrors(err error) []error {
|
||||||
|
return multierr.Errors(err)
|
||||||
|
}
|
||||||
72
.gear/predownloaded-development/vendor/emperror.dev/errors/pleasew
vendored
Normal file
72
.gear/predownloaded-development/vendor/emperror.dev/errors/pleasew
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -u
|
||||||
|
|
||||||
|
RED="\x1B[31m"
|
||||||
|
GREEN="\x1B[32m"
|
||||||
|
YELLOW="\x1B[33m"
|
||||||
|
RESET="\x1B[0m"
|
||||||
|
|
||||||
|
DEFAULT_URL_BASE="https://get.please.build"
|
||||||
|
# We might already have it downloaded...
|
||||||
|
LOCATION=`grep -i "^location" .plzconfig 2>/dev/null | cut -d '=' -f 2 | tr -d ' '`
|
||||||
|
if [ -z "$LOCATION" ]; then
|
||||||
|
if [ -z "$HOME" ]; then
|
||||||
|
echo -e >&2 "${RED}\$HOME not set, not sure where to look for Please.${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
LOCATION="${HOME}/.please"
|
||||||
|
else
|
||||||
|
# It can contain a literal ~, need to explicitly handle that.
|
||||||
|
LOCATION="${LOCATION/\~/$HOME}"
|
||||||
|
fi
|
||||||
|
# If this exists at any version, let it handle any update.
|
||||||
|
TARGET="${LOCATION}/please"
|
||||||
|
if [ -f "$TARGET" ]; then
|
||||||
|
exec "$TARGET" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
URL_BASE="`grep -i "^downloadlocation" .plzconfig | cut -d '=' -f 2 | tr -d ' '`"
|
||||||
|
if [ -z "$URL_BASE" ]; then
|
||||||
|
URL_BASE=$DEFAULT_URL_BASE
|
||||||
|
fi
|
||||||
|
URL_BASE="${URL_BASE%/}"
|
||||||
|
|
||||||
|
VERSION="`grep -i "^version[^a-z]" .plzconfig`"
|
||||||
|
VERSION="${VERSION#*=}" # Strip until after first =
|
||||||
|
VERSION="${VERSION/ /}" # Remove all spaces
|
||||||
|
VERSION="${VERSION#>=}" # Strip any initial >=
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo -e >&2 "${YELLOW}Can't determine version, will use latest.${RESET}"
|
||||||
|
VERSION=`curl -fsSL ${URL_BASE}/latest_version`
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the os / arch to download. You can do this quite nicely with go env
|
||||||
|
# but we use this script on machines that don't necessarily have Go itself.
|
||||||
|
OS=`uname`
|
||||||
|
if [ "$OS" = "Linux" ]; then
|
||||||
|
GOOS="linux"
|
||||||
|
elif [ "$OS" = "Darwin" ]; then
|
||||||
|
GOOS="darwin"
|
||||||
|
else
|
||||||
|
echo -e >&2 "${RED}Unknown operating system $OS${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Don't have any builds other than amd64 at the moment.
|
||||||
|
ARCH="amd64"
|
||||||
|
|
||||||
|
PLEASE_URL="${URL_BASE}/${GOOS}_${ARCH}/${VERSION}/please_${VERSION}.tar.xz"
|
||||||
|
DIR="${LOCATION}/${VERSION}"
|
||||||
|
# Potentially we could reuse this but it's easier not to really.
|
||||||
|
if [ ! -d "$DIR" ]; then
|
||||||
|
rm -rf "$DIR"
|
||||||
|
fi
|
||||||
|
echo -e >&2 "${GREEN}Downloading Please ${VERSION} to ${DIR}...${RESET}"
|
||||||
|
mkdir -p "$DIR"
|
||||||
|
curl -fsSL "${PLEASE_URL}" | tar -xJpf- --strip-components=1 -C "$DIR"
|
||||||
|
# Link it all back up a dir
|
||||||
|
for x in `ls "$DIR"`; do
|
||||||
|
ln -sf "${DIR}/${x}" "$LOCATION"
|
||||||
|
done
|
||||||
|
ln -sf "${DIR}/please" "${LOCATION}/plz"
|
||||||
|
echo -e >&2 "${GREEN}Should be good to go now, running plz...${RESET}"
|
||||||
|
exec "$TARGET" "$@"
|
||||||
66
.gear/predownloaded-development/vendor/emperror.dev/errors/stack.go
vendored
Normal file
66
.gear/predownloaded-development/vendor/emperror.dev/errors/stack.go
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
// Copyright 2015 Dave Cheney <dave@cheney.net>. All rights reserved.
|
||||||
|
// Use of this source code (or at least parts of it) is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE_THIRD_PARTY file.
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||||
|
//
|
||||||
|
// It is an alias of the same type in github.com/pkg/errors.
|
||||||
|
type StackTrace = errors.StackTrace
|
||||||
|
|
||||||
|
// Frame represents a program counter inside a stack frame.
|
||||||
|
// For historical reasons if Frame is interpreted as a uintptr
|
||||||
|
// its value represents the program counter + 1.
|
||||||
|
//
|
||||||
|
// It is an alias of the same type in github.com/pkg/errors.
|
||||||
|
type Frame = errors.Frame
|
||||||
|
|
||||||
|
// stack represents a stack of program counters.
|
||||||
|
//
|
||||||
|
// It is a duplicate of the same (sadly unexported) type in github.com/pkg/errors.
|
||||||
|
type stack []uintptr
|
||||||
|
|
||||||
|
// nolint: gocritic
|
||||||
|
func (s *stack) Format(st fmt.State, verb rune) {
|
||||||
|
switch verb {
|
||||||
|
case 'v':
|
||||||
|
switch {
|
||||||
|
case st.Flag('+'):
|
||||||
|
for _, pc := range *s {
|
||||||
|
f := Frame(pc)
|
||||||
|
fmt.Fprintf(st, "\n%+v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stack) StackTrace() StackTrace {
|
||||||
|
f := make([]Frame, len(*s))
|
||||||
|
for i := 0; i < len(f); i++ {
|
||||||
|
f[i] = Frame((*s)[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// callers is based on the function with the same name in github.com/pkg/errors,
|
||||||
|
// but accepts a custom depth (useful to customize the error constructor caller depth).
|
||||||
|
func callers(depth int) *stack {
|
||||||
|
const maxDepth = 32
|
||||||
|
|
||||||
|
var pcs [maxDepth]uintptr
|
||||||
|
|
||||||
|
n := runtime.Callers(2+depth, pcs[:])
|
||||||
|
st := make(stack, n)
|
||||||
|
copy(st, pcs[:n])
|
||||||
|
|
||||||
|
return &st
|
||||||
|
}
|
||||||
53
.gear/predownloaded-development/vendor/emperror.dev/errors/wrap.go
vendored
Normal file
53
.gear/predownloaded-development/vendor/emperror.dev/errors/wrap.go
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
package errors
|
||||||
|
|
||||||
|
// Unwrap returns the result of calling the Unwrap method on err, if err implements
|
||||||
|
// Unwrap. Otherwise, Unwrap returns nil.
|
||||||
|
//
|
||||||
|
// It supports both Go 1.13 Unwrap and github.com/pkg/errors.Causer interfaces
|
||||||
|
// (the former takes precedence).
|
||||||
|
func Unwrap(err error) error {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case interface{ Unwrap() error }:
|
||||||
|
return e.Unwrap()
|
||||||
|
|
||||||
|
case interface{ Cause() error }:
|
||||||
|
return e.Cause()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnwrapEach loops through an error chain and calls a function for each of them.
|
||||||
|
//
|
||||||
|
// The provided function can return false to break the loop before it reaches the end of the chain.
|
||||||
|
//
|
||||||
|
// It supports both Go 1.13 errors.Wrapper and github.com/pkg/errors.Causer interfaces
|
||||||
|
// (the former takes precedence).
|
||||||
|
func UnwrapEach(err error, fn func(err error) bool) {
|
||||||
|
for err != nil {
|
||||||
|
continueLoop := fn(err)
|
||||||
|
if !continueLoop {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Unwrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cause returns the last error (root cause) in an err's chain.
|
||||||
|
// If err has no chain, it is returned directly.
|
||||||
|
//
|
||||||
|
// It supports both Go 1.13 errors.Wrapper and github.com/pkg/errors.Causer interfaces
|
||||||
|
// (the former takes precedence).
|
||||||
|
func Cause(err error) error {
|
||||||
|
for {
|
||||||
|
cause := Unwrap(err)
|
||||||
|
if cause == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cause
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
72
.gear/predownloaded-development/vendor/emperror.dev/errors/wrap_go1_12.go
vendored
Normal file
72
.gear/predownloaded-development/vendor/emperror.dev/errors/wrap_go1_12.go
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
// Copyright 2019 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code (or at least parts of it) is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE_THIRD_PARTY file.
|
||||||
|
// +build !go1.13
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Is reports whether any error in err's chain matches target.
|
||||||
|
//
|
||||||
|
// An error is considered to match a target if it is equal to that target or if
|
||||||
|
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||||
|
func Is(err, target error) bool {
|
||||||
|
if target == nil {
|
||||||
|
return err == target
|
||||||
|
}
|
||||||
|
|
||||||
|
isComparable := reflect.TypeOf(target).Comparable()
|
||||||
|
for {
|
||||||
|
if isComparable && err == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// TODO: consider supporing target.Is(err). This would allow
|
||||||
|
// user-definable predicates, but also may allow for coping with sloppy
|
||||||
|
// APIs, thereby making it easier to get away with them.
|
||||||
|
if err = Unwrap(err); err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As finds the first error in err's chain that matches the type to which target
|
||||||
|
// points, and if so, sets the target to its value and returns true. An error
|
||||||
|
// matches a type if it is assignable to the target type, or if it has a method
|
||||||
|
// As(interface{}) bool such that As(target) returns true. As will panic if target
|
||||||
|
// is not a non-nil pointer to a type which implements error or is of interface type.
|
||||||
|
//
|
||||||
|
// The As method should set the target to its value and return true if err
|
||||||
|
// matches the type to which target points.
|
||||||
|
func As(err error, target interface{}) bool {
|
||||||
|
if target == nil {
|
||||||
|
panic("errors: target cannot be nil")
|
||||||
|
}
|
||||||
|
val := reflect.ValueOf(target)
|
||||||
|
typ := val.Type()
|
||||||
|
if typ.Kind() != reflect.Ptr || val.IsNil() {
|
||||||
|
panic("errors: target must be a non-nil pointer")
|
||||||
|
}
|
||||||
|
if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) {
|
||||||
|
panic("errors: *target must be interface or implement error")
|
||||||
|
}
|
||||||
|
targetType := typ.Elem()
|
||||||
|
for err != nil {
|
||||||
|
if reflect.TypeOf(err).AssignableTo(targetType) {
|
||||||
|
val.Elem().Set(reflect.ValueOf(err))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
err = Unwrap(err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
27
.gear/predownloaded-development/vendor/emperror.dev/errors/wrap_go1_13.go
vendored
Normal file
27
.gear/predownloaded-development/vendor/emperror.dev/errors/wrap_go1_13.go
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// +build go1.13
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Is reports whether any error in err's chain matches target.
|
||||||
|
//
|
||||||
|
// An error is considered to match a target if it is equal to that target or if
|
||||||
|
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||||
|
func Is(err, target error) bool {
|
||||||
|
return errors.Is(err, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// As finds the first error in err's chain that matches the type to which target
|
||||||
|
// points, and if so, sets the target to its value and returns true. An error
|
||||||
|
// matches a type if it is assignable to the target type, or if it has a method
|
||||||
|
// As(interface{}) bool such that As(target) returns true. As will panic if target
|
||||||
|
// is not a non-nil pointer to a type which implements error or is of interface type.
|
||||||
|
//
|
||||||
|
// The As method should set the target to its value and return true if err
|
||||||
|
// matches the type to which target points.
|
||||||
|
func As(err error, target interface{}) bool {
|
||||||
|
return errors.As(err, target)
|
||||||
|
}
|
||||||
55
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/CONTRIBUTING.md
generated
vendored
Normal file
55
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/LICENSE
generated
vendored
Normal file
21
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/README.md
generated
vendored
Normal file
510
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/confirm.go
generated
vendored
Normal file
154
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/core/template.go
generated
vendored
Normal file
104
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/core/write.go
generated
vendored
Normal file
376
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/editor.go
generated
vendored
Normal file
226
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/filter.go
generated
vendored
Normal file
1
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/filter.go
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
package survey
|
||||||
219
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/input.go
generated
vendored
Normal file
219
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/multiline.go
generated
vendored
Normal file
112
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/multiselect.go
generated
vendored
Normal file
360
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/password.go
generated
vendored
Normal file
106
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/renderer.go
generated
vendored
Normal file
195
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/select.go
generated
vendored
Normal file
329
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/survey.go
generated
vendored
Normal file
474
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/LICENSE.txt
generated
vendored
Normal file
22
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/README.md
generated
vendored
Normal file
3
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/buffered_reader.go
generated
vendored
Normal file
22
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/cursor.go
generated
vendored
Normal file
209
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/cursor_windows.go
generated
vendored
Normal file
164
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/display.go
generated
vendored
Normal file
9
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/display_posix.go
generated
vendored
Normal file
13
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/display_windows.go
generated
vendored
Normal file
31
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/error.go
generated
vendored
Normal file
10
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/output.go
generated
vendored
Normal file
20
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/output_windows.go
generated
vendored
Normal file
253
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/runereader.go
generated
vendored
Normal file
417
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_bsd.go
generated
vendored
Normal file
14
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_linux.go
generated
vendored
Normal file
14
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_posix.go
generated
vendored
Normal file
132
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_ppc64le.go
generated
vendored
Normal file
8
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/runereader_windows.go
generated
vendored
Normal file
142
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/sequences.go
generated
vendored
Normal file
32
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/stdio.go
generated
vendored
Normal file
24
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/syscall_windows.go
generated
vendored
Normal file
39
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/terminal/terminal.go
generated
vendored
Normal file
8
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/transform.go
generated
vendored
Normal file
82
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/AlecAivazis/survey/v2/validate.go
generated
vendored
Normal file
128
.gear/predownloaded-development/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())
|
||||||
|
}
|
||||||
8
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/.errcheck.txt
generated
vendored
Normal file
8
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/.errcheck.txt
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).Array
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).ArrayAppend
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).ArrayAppend
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).ArrayConcat
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).ArrayConcatP
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).ArrayP
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).Set
|
||||||
|
(*github.com/Jeffail/gabs/v2.Container).SetIndex
|
||||||
41
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/.golangci.yml
generated
vendored
Normal file
41
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/.golangci.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
run:
|
||||||
|
timeout: 30s
|
||||||
|
|
||||||
|
issues:
|
||||||
|
max-issues-per-linter: 0
|
||||||
|
max-same-issues: 0
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
exclude: .errcheck.txt
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- experimental
|
||||||
|
- opinionated
|
||||||
|
- performance
|
||||||
|
- style
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
# Default linters reported by golangci-lint help linters` in v1.39.0
|
||||||
|
- gosimple
|
||||||
|
- staticcheck
|
||||||
|
- unused
|
||||||
|
- errcheck
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- typecheck
|
||||||
|
# Extra linters:
|
||||||
|
- wastedassign
|
||||||
|
- stylecheck
|
||||||
|
- gofmt
|
||||||
|
- goimports
|
||||||
|
- gocritic
|
||||||
|
- revive
|
||||||
|
- unconvert
|
||||||
|
- durationcheck
|
||||||
|
- depguard
|
||||||
|
- gosec
|
||||||
|
- bodyclose
|
||||||
19
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/LICENSE
generated
vendored
Normal file
19
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2019 Ashley Jeffs
|
||||||
|
|
||||||
|
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.
|
||||||
297
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/README.md
generated
vendored
Normal file
297
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,297 @@
|
||||||
|

|
||||||
|
|
||||||
|
[![pkg.go for Jeffail/gabs][godoc-badge]][godoc-url]
|
||||||
|
|
||||||
|
Gabs is a small utility for dealing with dynamic or unknown JSON structures in Go. It's pretty much just a helpful wrapper for navigating hierarchies of `map[string]interface{}` objects provided by the `encoding/json` package. It does nothing spectacular apart from being fabulous.
|
||||||
|
|
||||||
|
If you're migrating from version 1 check out [`migration.md`][migration-doc] for guidance.
|
||||||
|
|
||||||
|
## Use
|
||||||
|
|
||||||
|
### Import
|
||||||
|
|
||||||
|
Using modules:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/Jeffail/gabs/v2"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Without modules:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"github.com/Jeffail/gabs"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parsing and searching JSON
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsed, err := gabs.ParseJSON([]byte(`{
|
||||||
|
"outer":{
|
||||||
|
"inner":{
|
||||||
|
"value1":10,
|
||||||
|
"value2":22
|
||||||
|
},
|
||||||
|
"alsoInner":{
|
||||||
|
"value1":20,
|
||||||
|
"array1":[
|
||||||
|
30, 40
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var value float64
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
value, ok = jsonParsed.Path("outer.inner.value1").Data().(float64)
|
||||||
|
// value == 10.0, ok == true
|
||||||
|
|
||||||
|
value, ok = jsonParsed.Search("outer", "inner", "value1").Data().(float64)
|
||||||
|
// value == 10.0, ok == true
|
||||||
|
|
||||||
|
value, ok = jsonParsed.Search("outer", "alsoInner", "array1", "1").Data().(float64)
|
||||||
|
// value == 40.0, ok == true
|
||||||
|
|
||||||
|
gObj, err := jsonParsed.JSONPointer("/outer/alsoInner/array1/1")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
value, ok = gObj.Data().(float64)
|
||||||
|
// value == 40.0, ok == true
|
||||||
|
|
||||||
|
value, ok = jsonParsed.Path("does.not.exist").Data().(float64)
|
||||||
|
// value == 0.0, ok == false
|
||||||
|
|
||||||
|
exists := jsonParsed.Exists("outer", "inner", "value1")
|
||||||
|
// exists == true
|
||||||
|
|
||||||
|
exists = jsonParsed.ExistsP("does.not.exist")
|
||||||
|
// exists == false
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iterating objects
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsed, err := gabs.ParseJSON([]byte(`{"object":{"first":1,"second":2,"third":3}}`))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// S is shorthand for Search
|
||||||
|
for key, child := range jsonParsed.S("object").ChildrenMap() {
|
||||||
|
fmt.Printf("key: %v, value: %v\n", key, child.Data().(float64))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Iterating arrays
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsed, err := gabs.ParseJSON([]byte(`{"array":["first","second","third"]}`))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range jsonParsed.S("array").Children() {
|
||||||
|
fmt.Println(child.Data().(string))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print:
|
||||||
|
|
||||||
|
```
|
||||||
|
first
|
||||||
|
second
|
||||||
|
third
|
||||||
|
```
|
||||||
|
|
||||||
|
Children() will return all children of an array in order. This also works on objects, however, the children will be returned in a random order.
|
||||||
|
|
||||||
|
### Searching through arrays
|
||||||
|
|
||||||
|
If your structure contains arrays you must target an index in your search.
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsed, err := gabs.ParseJSON([]byte(`{"array":[{"value":1},{"value":2},{"value":3}]}`))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(jsonParsed.Path("array.1.value").String())
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print `2`.
|
||||||
|
|
||||||
|
### Generating JSON
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonObj := gabs.New()
|
||||||
|
// or gabs.Wrap(jsonObject) to work on an existing map[string]interface{}
|
||||||
|
|
||||||
|
jsonObj.Set(10, "outer", "inner", "value")
|
||||||
|
jsonObj.SetP(20, "outer.inner.value2")
|
||||||
|
jsonObj.Set(30, "outer", "inner2", "value3")
|
||||||
|
|
||||||
|
fmt.Println(jsonObj.String())
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print:
|
||||||
|
|
||||||
|
```
|
||||||
|
{"outer":{"inner":{"value":10,"value2":20},"inner2":{"value3":30}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
To pretty-print:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Println(jsonObj.StringIndent("", " "))
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"outer": {
|
||||||
|
"inner": {
|
||||||
|
"value": 10,
|
||||||
|
"value2": 20
|
||||||
|
},
|
||||||
|
"inner2": {
|
||||||
|
"value3": 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generating Arrays
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonObj := gabs.New()
|
||||||
|
|
||||||
|
jsonObj.Array("foo", "array")
|
||||||
|
// Or .ArrayP("foo.array")
|
||||||
|
|
||||||
|
jsonObj.ArrayAppend(10, "foo", "array")
|
||||||
|
jsonObj.ArrayAppend(20, "foo", "array")
|
||||||
|
jsonObj.ArrayAppend(30, "foo", "array")
|
||||||
|
|
||||||
|
fmt.Println(jsonObj.String())
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print:
|
||||||
|
|
||||||
|
```
|
||||||
|
{"foo":{"array":[10,20,30]}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Working with arrays by index:
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonObj := gabs.New()
|
||||||
|
|
||||||
|
// Create an array with the length of 3
|
||||||
|
jsonObj.ArrayOfSize(3, "foo")
|
||||||
|
|
||||||
|
jsonObj.S("foo").SetIndex("test1", 0)
|
||||||
|
jsonObj.S("foo").SetIndex("test2", 1)
|
||||||
|
|
||||||
|
// Create an embedded array with the length of 3
|
||||||
|
jsonObj.S("foo").ArrayOfSizeI(3, 2)
|
||||||
|
|
||||||
|
jsonObj.S("foo").Index(2).SetIndex(1, 0)
|
||||||
|
jsonObj.S("foo").Index(2).SetIndex(2, 1)
|
||||||
|
jsonObj.S("foo").Index(2).SetIndex(3, 2)
|
||||||
|
|
||||||
|
fmt.Println(jsonObj.String())
|
||||||
|
```
|
||||||
|
|
||||||
|
Will print:
|
||||||
|
|
||||||
|
```
|
||||||
|
{"foo":["test1","test2",[1,2,3]]}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Converting back to JSON
|
||||||
|
|
||||||
|
This is the easiest part:
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsedObj, _ := gabs.ParseJSON([]byte(`{
|
||||||
|
"outer":{
|
||||||
|
"values":{
|
||||||
|
"first":10,
|
||||||
|
"second":11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outer2":"hello world"
|
||||||
|
}`))
|
||||||
|
|
||||||
|
jsonOutput := jsonParsedObj.String()
|
||||||
|
// Becomes `{"outer":{"values":{"first":10,"second":11}},"outer2":"hello world"}`
|
||||||
|
```
|
||||||
|
|
||||||
|
And to serialize a specific segment is as simple as:
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsedObj := gabs.ParseJSON([]byte(`{
|
||||||
|
"outer":{
|
||||||
|
"values":{
|
||||||
|
"first":10,
|
||||||
|
"second":11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"outer2":"hello world"
|
||||||
|
}`))
|
||||||
|
|
||||||
|
jsonOutput := jsonParsedObj.Search("outer").String()
|
||||||
|
// Becomes `{"values":{"first":10,"second":11}}`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Merge two containers
|
||||||
|
|
||||||
|
You can merge a JSON structure into an existing one, where collisions will be converted into a JSON array.
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsed1, _ := ParseJSON([]byte(`{"outer":{"value1":"one"}}`))
|
||||||
|
jsonParsed2, _ := ParseJSON([]byte(`{"outer":{"inner":{"value3":"three"}},"outer2":{"value2":"two"}}`))
|
||||||
|
|
||||||
|
jsonParsed1.Merge(jsonParsed2)
|
||||||
|
// Becomes `{"outer":{"inner":{"value3":"three"},"value1":"one"},"outer2":{"value2":"two"}}`
|
||||||
|
```
|
||||||
|
|
||||||
|
Arrays are merged:
|
||||||
|
|
||||||
|
```go
|
||||||
|
jsonParsed1, _ := ParseJSON([]byte(`{"array":["one"]}`))
|
||||||
|
jsonParsed2, _ := ParseJSON([]byte(`{"array":["two"]}`))
|
||||||
|
|
||||||
|
jsonParsed1.Merge(jsonParsed2)
|
||||||
|
// Becomes `{"array":["one", "two"]}`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parsing Numbers
|
||||||
|
|
||||||
|
Gabs uses the `json` package under the bonnet, which by default will parse all number values into `float64`. If you need to parse `Int` values then you should use a [`json.Decoder`](https://golang.org/pkg/encoding/json/#Decoder):
|
||||||
|
|
||||||
|
```go
|
||||||
|
sample := []byte(`{"test":{"int":10,"float":6.66}}`)
|
||||||
|
dec := json.NewDecoder(bytes.NewReader(sample))
|
||||||
|
dec.UseNumber()
|
||||||
|
|
||||||
|
val, err := gabs.ParseJSONDecoder(dec)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed to parse: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
intValue, err := val.Path("test.int").Data().(json.Number).Int64()
|
||||||
|
```
|
||||||
|
|
||||||
|
[godoc-badge]: https://godoc.org/github.com/Jeffail/gabs?status.svg
|
||||||
|
[godoc-url]: https://pkg.go.dev/github.com/Jeffail/gabs/v2
|
||||||
|
[migration-doc]: ./migration.md
|
||||||
934
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/gabs.go
generated
vendored
Normal file
934
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/gabs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,934 @@
|
||||||
|
// Copyright (c) 2019 Ashley Jeffs
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// Package gabs implements a wrapper around creating and parsing unknown or
|
||||||
|
// dynamic map structures resulting from JSON parsing.
|
||||||
|
package gabs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrOutOfBounds indicates an index was out of bounds.
|
||||||
|
ErrOutOfBounds = errors.New("out of bounds")
|
||||||
|
|
||||||
|
// ErrNotObjOrArray is returned when a target is not an object or array type
|
||||||
|
// but needs to be for the intended operation.
|
||||||
|
ErrNotObjOrArray = errors.New("not an object or array")
|
||||||
|
|
||||||
|
// ErrNotObj is returned when a target is not an object but needs to be for
|
||||||
|
// the intended operation.
|
||||||
|
ErrNotObj = errors.New("not an object")
|
||||||
|
|
||||||
|
// ErrInvalidQuery is returned when a seach query was not valid.
|
||||||
|
ErrInvalidQuery = errors.New("invalid search query")
|
||||||
|
|
||||||
|
// ErrNotArray is returned when a target is not an array but needs to be for
|
||||||
|
// the intended operation.
|
||||||
|
ErrNotArray = errors.New("not an array")
|
||||||
|
|
||||||
|
// ErrPathCollision is returned when creating a path failed because an
|
||||||
|
// element collided with an existing value.
|
||||||
|
ErrPathCollision = errors.New("encountered value collision whilst building path")
|
||||||
|
|
||||||
|
// ErrInvalidInputObj is returned when the input value was not a
|
||||||
|
// map[string]interface{}.
|
||||||
|
ErrInvalidInputObj = errors.New("invalid input object")
|
||||||
|
|
||||||
|
// ErrInvalidInputText is returned when the input data could not be parsed.
|
||||||
|
ErrInvalidInputText = errors.New("input text could not be parsed")
|
||||||
|
|
||||||
|
// ErrNotFound is returned when a query leaf is not found.
|
||||||
|
ErrNotFound = errors.New("field not found")
|
||||||
|
|
||||||
|
// ErrInvalidPath is returned when the filepath was not valid.
|
||||||
|
ErrInvalidPath = errors.New("invalid file path")
|
||||||
|
|
||||||
|
// ErrInvalidBuffer is returned when the input buffer contained an invalid
|
||||||
|
// JSON string.
|
||||||
|
ErrInvalidBuffer = errors.New("input buffer contained invalid JSON")
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
r1 *strings.Replacer
|
||||||
|
r2 *strings.Replacer
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
r1 = strings.NewReplacer("~1", "/", "~0", "~")
|
||||||
|
r2 = strings.NewReplacer("~1", ".", "~0", "~")
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// JSONPointerToSlice parses a JSON pointer path
|
||||||
|
// (https://tools.ietf.org/html/rfc6901) and returns the path segments as a
|
||||||
|
// slice.
|
||||||
|
//
|
||||||
|
// Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in
|
||||||
|
// gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as
|
||||||
|
// '~1' when these characters appear in a reference key.
|
||||||
|
func JSONPointerToSlice(path string) ([]string, error) {
|
||||||
|
if path == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if path[0] != '/' {
|
||||||
|
return nil, errors.New("failed to resolve JSON pointer: path must begin with '/'")
|
||||||
|
}
|
||||||
|
if path == "/" {
|
||||||
|
return []string{""}, nil
|
||||||
|
}
|
||||||
|
hierarchy := strings.Split(path, "/")[1:]
|
||||||
|
for i, v := range hierarchy {
|
||||||
|
hierarchy[i] = r1.Replace(v)
|
||||||
|
}
|
||||||
|
return hierarchy, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DotPathToSlice returns a slice of path segments parsed out of a dot path.
|
||||||
|
//
|
||||||
|
// Because '.' (%x2E) is the segment separator, it must be encoded as '~1'
|
||||||
|
// if it appears in the reference key. Likewise, '~' (%x7E) must be encoded
|
||||||
|
// as '~0' since it is the escape character for encoding '.'.
|
||||||
|
func DotPathToSlice(path string) []string {
|
||||||
|
hierarchy := strings.Split(path, ".")
|
||||||
|
for i, v := range hierarchy {
|
||||||
|
hierarchy[i] = r2.Replace(v)
|
||||||
|
}
|
||||||
|
return hierarchy
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Container references a specific element within a wrapped structure.
|
||||||
|
type Container struct {
|
||||||
|
object interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data returns the underlying value of the target element in the wrapped
|
||||||
|
// structure.
|
||||||
|
func (g *Container) Data() interface{} {
|
||||||
|
if g == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return g.object
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func (g *Container) searchStrict(allowWildcard bool, hierarchy ...string) (*Container, error) {
|
||||||
|
object := g.Data()
|
||||||
|
for target := 0; target < len(hierarchy); target++ {
|
||||||
|
pathSeg := hierarchy[target]
|
||||||
|
switch typedObj := object.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
var ok bool
|
||||||
|
if object, ok = typedObj[pathSeg]; !ok {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': key '%v' was not found", target, pathSeg)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if allowWildcard && pathSeg == "*" {
|
||||||
|
var tmpArray []interface{}
|
||||||
|
if (target + 1) >= len(hierarchy) {
|
||||||
|
tmpArray = typedObj
|
||||||
|
} else {
|
||||||
|
tmpArray = make([]interface{}, 0, len(typedObj))
|
||||||
|
for _, val := range typedObj {
|
||||||
|
if res := Wrap(val).Search(hierarchy[target+1:]...); res != nil {
|
||||||
|
tmpArray = append(tmpArray, res.Data())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tmpArray) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Container{tmpArray}, nil
|
||||||
|
}
|
||||||
|
index, err := strconv.Atoi(pathSeg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err)
|
||||||
|
}
|
||||||
|
if index < 0 {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' is invalid", target, pathSeg)
|
||||||
|
}
|
||||||
|
if len(typedObj) <= index {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(typedObj))
|
||||||
|
}
|
||||||
|
object = typedObj[index]
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Container{object}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search attempts to find and return an object within the wrapped structure by
|
||||||
|
// following a provided hierarchy of field names to locate the target.
|
||||||
|
//
|
||||||
|
// If the search encounters an array then the next hierarchy field name must be
|
||||||
|
// either a an integer which is interpreted as the index of the target, or the
|
||||||
|
// character '*', in which case all elements are searched with the remaining
|
||||||
|
// search hierarchy and the results returned within an array.
|
||||||
|
func (g *Container) Search(hierarchy ...string) *Container {
|
||||||
|
c, _ := g.searchStrict(true, hierarchy...)
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path searches the wrapped structure following a path in dot notation,
|
||||||
|
// segments of this path are searched according to the same rules as Search.
|
||||||
|
//
|
||||||
|
// Because the characters '~' (%x7E) and '.' (%x2E) have special meanings in
|
||||||
|
// gabs paths, '~' needs to be encoded as '~0' and '.' needs to be encoded as
|
||||||
|
// '~1' when these characters appear in a reference key.
|
||||||
|
func (g *Container) Path(path string) *Container {
|
||||||
|
return g.Search(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONPointer parses a JSON pointer path (https://tools.ietf.org/html/rfc6901)
|
||||||
|
// and either returns a *gabs.Container containing the result or an error if the
|
||||||
|
// referenced item could not be found.
|
||||||
|
//
|
||||||
|
// Because the characters '~' (%x7E) and '/' (%x2F) have special meanings in
|
||||||
|
// gabs paths, '~' needs to be encoded as '~0' and '/' needs to be encoded as
|
||||||
|
// '~1' when these characters appear in a reference key.
|
||||||
|
func (g *Container) JSONPointer(path string) (*Container, error) {
|
||||||
|
hierarchy, err := JSONPointerToSlice(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return g.searchStrict(false, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// S is a shorthand alias for Search.
|
||||||
|
func (g *Container) S(hierarchy ...string) *Container {
|
||||||
|
return g.Search(hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists checks whether a field exists within the hierarchy.
|
||||||
|
func (g *Container) Exists(hierarchy ...string) bool {
|
||||||
|
return g.Search(hierarchy...) != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExistsP checks whether a dot notation path exists.
|
||||||
|
func (g *Container) ExistsP(path string) bool {
|
||||||
|
return g.Exists(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index attempts to find and return an element within a JSON array by an index.
|
||||||
|
func (g *Container) Index(index int) *Container {
|
||||||
|
if array, ok := g.Data().([]interface{}); ok {
|
||||||
|
if index >= len(array) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Container{array[index]}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children returns a slice of all children of an array element. This also works
|
||||||
|
// for objects, however, the children returned for an object will be in a random
|
||||||
|
// order and you lose the names of the returned objects this way. If the
|
||||||
|
// underlying container value isn't an array or map nil is returned.
|
||||||
|
func (g *Container) Children() []*Container {
|
||||||
|
if array, ok := g.Data().([]interface{}); ok {
|
||||||
|
children := make([]*Container, len(array))
|
||||||
|
for i := 0; i < len(array); i++ {
|
||||||
|
children[i] = &Container{array[i]}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||||
|
children := make([]*Container, 0, len(mmap))
|
||||||
|
for _, obj := range mmap {
|
||||||
|
children = append(children, &Container{obj})
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChildrenMap returns a map of all the children of an object element. IF the
|
||||||
|
// underlying value isn't a object then an empty map is returned.
|
||||||
|
func (g *Container) ChildrenMap() map[string]*Container {
|
||||||
|
if mmap, ok := g.Data().(map[string]interface{}); ok {
|
||||||
|
children := make(map[string]*Container, len(mmap))
|
||||||
|
for name, obj := range mmap {
|
||||||
|
children[name] = &Container{obj}
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
return map[string]*Container{}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Set attempts to set the value of a field located by a hierarchy of field
|
||||||
|
// names. If the search encounters an array then the next hierarchy field name
|
||||||
|
// is interpreted as an integer index of an existing element, or the character
|
||||||
|
// '-', which indicates a new element appended to the end of the array.
|
||||||
|
//
|
||||||
|
// Any parts of the hierarchy that do not exist will be constructed as objects.
|
||||||
|
// This includes parts that could be interpreted as array indexes.
|
||||||
|
//
|
||||||
|
// Returns a container of the new value or an error.
|
||||||
|
func (g *Container) Set(value interface{}, hierarchy ...string) (*Container, error) {
|
||||||
|
if g == nil {
|
||||||
|
return nil, errors.New("failed to resolve path, container is nil")
|
||||||
|
}
|
||||||
|
if len(hierarchy) == 0 {
|
||||||
|
g.object = value
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
if g.object == nil {
|
||||||
|
g.object = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
object := g.object
|
||||||
|
|
||||||
|
for target := 0; target < len(hierarchy); target++ {
|
||||||
|
pathSeg := hierarchy[target]
|
||||||
|
switch typedObj := object.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
if target == len(hierarchy)-1 {
|
||||||
|
object = value
|
||||||
|
typedObj[pathSeg] = object
|
||||||
|
} else if object = typedObj[pathSeg]; object == nil {
|
||||||
|
typedObj[pathSeg] = map[string]interface{}{}
|
||||||
|
object = typedObj[pathSeg]
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if pathSeg == "-" {
|
||||||
|
if target < 1 {
|
||||||
|
return nil, errors.New("unable to append new array index at root of path")
|
||||||
|
}
|
||||||
|
if target == len(hierarchy)-1 {
|
||||||
|
object = value
|
||||||
|
} else {
|
||||||
|
object = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
typedObj = append(typedObj, object)
|
||||||
|
if _, err := g.Set(typedObj, hierarchy[:target]...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
index, err := strconv.Atoi(pathSeg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but segment value '%v' could not be parsed into array index: %v", target, pathSeg, err)
|
||||||
|
}
|
||||||
|
if index < 0 {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' is invalid", target, pathSeg)
|
||||||
|
}
|
||||||
|
if len(typedObj) <= index {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': found array but index '%v' exceeded target array size of '%v'", target, pathSeg, len(typedObj))
|
||||||
|
}
|
||||||
|
if target == len(hierarchy)-1 {
|
||||||
|
object = value
|
||||||
|
typedObj[index] = object
|
||||||
|
} else if object = typedObj[index]; object == nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve path segment '%v': field '%v' was not found", target, pathSeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return nil, ErrPathCollision
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Container{object}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetP sets the value of a field at a path using dot notation, any parts
|
||||||
|
// of the path that do not exist will be constructed, and if a collision occurs
|
||||||
|
// with a non object type whilst iterating the path an error is returned.
|
||||||
|
func (g *Container) SetP(value interface{}, path string) (*Container, error) {
|
||||||
|
return g.Set(value, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIndex attempts to set a value of an array element based on an index.
|
||||||
|
func (g *Container) SetIndex(value interface{}, index int) (*Container, error) {
|
||||||
|
if array, ok := g.Data().([]interface{}); ok {
|
||||||
|
if index >= len(array) {
|
||||||
|
return nil, ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array[index] = value
|
||||||
|
return &Container{array[index]}, nil
|
||||||
|
}
|
||||||
|
return nil, ErrNotArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetJSONPointer parses a JSON pointer path
|
||||||
|
// (https://tools.ietf.org/html/rfc6901) and sets the leaf to a value. Returns
|
||||||
|
// an error if the pointer could not be resolved due to missing fields.
|
||||||
|
func (g *Container) SetJSONPointer(value interface{}, path string) (*Container, error) {
|
||||||
|
hierarchy, err := JSONPointerToSlice(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return g.Set(value, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object creates a new JSON object at a target path. Returns an error if the
|
||||||
|
// path contains a collision with a non object type.
|
||||||
|
func (g *Container) Object(hierarchy ...string) (*Container, error) {
|
||||||
|
return g.Set(map[string]interface{}{}, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectP creates a new JSON object at a target path using dot notation.
|
||||||
|
// Returns an error if the path contains a collision with a non object type.
|
||||||
|
func (g *Container) ObjectP(path string) (*Container, error) {
|
||||||
|
return g.Object(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ObjectI creates a new JSON object at an array index. Returns an error if the
|
||||||
|
// object is not an array or the index is out of bounds.
|
||||||
|
func (g *Container) ObjectI(index int) (*Container, error) {
|
||||||
|
return g.SetIndex(map[string]interface{}{}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array creates a new JSON array at a path. Returns an error if the path
|
||||||
|
// contains a collision with a non object type.
|
||||||
|
func (g *Container) Array(hierarchy ...string) (*Container, error) {
|
||||||
|
return g.Set([]interface{}{}, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayP creates a new JSON array at a path using dot notation. Returns an
|
||||||
|
// error if the path contains a collision with a non object type.
|
||||||
|
func (g *Container) ArrayP(path string) (*Container, error) {
|
||||||
|
return g.Array(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayI creates a new JSON array within an array at an index. Returns an error
|
||||||
|
// if the element is not an array or the index is out of bounds.
|
||||||
|
func (g *Container) ArrayI(index int) (*Container, error) {
|
||||||
|
return g.SetIndex([]interface{}{}, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOfSize creates a new JSON array of a particular size at a path. Returns
|
||||||
|
// an error if the path contains a collision with a non object type.
|
||||||
|
func (g *Container) ArrayOfSize(size int, hierarchy ...string) (*Container, error) {
|
||||||
|
a := make([]interface{}, size)
|
||||||
|
return g.Set(a, hierarchy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOfSizeP creates a new JSON array of a particular size at a path using
|
||||||
|
// dot notation. Returns an error if the path contains a collision with a non
|
||||||
|
// object type.
|
||||||
|
func (g *Container) ArrayOfSizeP(size int, path string) (*Container, error) {
|
||||||
|
return g.ArrayOfSize(size, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayOfSizeI create a new JSON array of a particular size within an array at
|
||||||
|
// an index. Returns an error if the element is not an array or the index is out
|
||||||
|
// of bounds.
|
||||||
|
func (g *Container) ArrayOfSizeI(size, index int) (*Container, error) {
|
||||||
|
a := make([]interface{}, size)
|
||||||
|
return g.SetIndex(a, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete an element at a path, an error is returned if the element does not
|
||||||
|
// exist or is not an object. In order to remove an array element please use
|
||||||
|
// ArrayRemove.
|
||||||
|
func (g *Container) Delete(hierarchy ...string) error {
|
||||||
|
if g == nil || g.object == nil {
|
||||||
|
return ErrNotObj
|
||||||
|
}
|
||||||
|
if len(hierarchy) == 0 {
|
||||||
|
return ErrInvalidQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
object := g.object
|
||||||
|
target := hierarchy[len(hierarchy)-1]
|
||||||
|
if len(hierarchy) > 1 {
|
||||||
|
object = g.Search(hierarchy[:len(hierarchy)-1]...).Data()
|
||||||
|
}
|
||||||
|
|
||||||
|
if obj, ok := object.(map[string]interface{}); ok {
|
||||||
|
if _, ok = obj[target]; !ok {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
delete(obj, target)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if array, ok := object.([]interface{}); ok {
|
||||||
|
if len(hierarchy) < 2 {
|
||||||
|
return errors.New("unable to delete array index at root of path")
|
||||||
|
}
|
||||||
|
index, err := strconv.Atoi(target)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse array index '%v': %v", target, err)
|
||||||
|
}
|
||||||
|
if index >= len(array) {
|
||||||
|
return ErrOutOfBounds
|
||||||
|
}
|
||||||
|
if index < 0 {
|
||||||
|
return ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array = append(array[:index], array[index+1:]...)
|
||||||
|
g.Set(array, hierarchy[:len(hierarchy)-1]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrNotObjOrArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteP deletes an element at a path using dot notation, an error is returned
|
||||||
|
// if the element does not exist.
|
||||||
|
func (g *Container) DeleteP(path string) error {
|
||||||
|
return g.Delete(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MergeFn merges two objects using a provided function to resolve collisions.
|
||||||
|
//
|
||||||
|
// The collision function receives two interface{} arguments, destination (the
|
||||||
|
// original object) and source (the object being merged into the destination).
|
||||||
|
// Which ever value is returned becomes the new value in the destination object
|
||||||
|
// at the location of the collision.
|
||||||
|
func (g *Container) MergeFn(source *Container, collisionFn func(destination, source interface{}) interface{}) error {
|
||||||
|
var recursiveFnc func(map[string]interface{}, []string) error
|
||||||
|
recursiveFnc = func(mmap map[string]interface{}, path []string) error {
|
||||||
|
for key, value := range mmap {
|
||||||
|
newPath := make([]string, len(path))
|
||||||
|
copy(newPath, path)
|
||||||
|
newPath = append(newPath, key)
|
||||||
|
if g.Exists(newPath...) {
|
||||||
|
existingData := g.Search(newPath...).Data()
|
||||||
|
switch t := value.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
switch existingVal := existingData.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
if err := recursiveFnc(t, newPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := g.Set(collisionFn(existingVal, t), newPath...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if _, err := g.Set(collisionFn(existingData, t), newPath...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if _, err := g.Set(value, newPath...); err != nil {
|
||||||
|
// path doesn't exist. So set the value
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mmap, ok := source.Data().(map[string]interface{}); ok {
|
||||||
|
return recursiveFnc(mmap, []string{})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge a source object into an existing destination object. When a collision
|
||||||
|
// is found within the merged structures (both a source and destination object
|
||||||
|
// contain the same non-object keys) the result will be an array containing both
|
||||||
|
// values, where values that are already arrays will be expanded into the
|
||||||
|
// resulting array.
|
||||||
|
//
|
||||||
|
// It is possible to merge structures will different collision behaviours with
|
||||||
|
// MergeFn.
|
||||||
|
func (g *Container) Merge(source *Container) error {
|
||||||
|
return g.MergeFn(source, func(dest, source interface{}) interface{} {
|
||||||
|
destArr, destIsArray := dest.([]interface{})
|
||||||
|
sourceArr, sourceIsArray := source.([]interface{})
|
||||||
|
if destIsArray {
|
||||||
|
if sourceIsArray {
|
||||||
|
return append(destArr, sourceArr...)
|
||||||
|
}
|
||||||
|
return append(destArr, source)
|
||||||
|
}
|
||||||
|
if sourceIsArray {
|
||||||
|
return append(append([]interface{}{}, dest), sourceArr...)
|
||||||
|
}
|
||||||
|
return []interface{}{dest, source}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
|
Array modification/search - Keeping these options simple right now, no need for
|
||||||
|
anything more complicated since you can just cast to []interface{}, modify and
|
||||||
|
then reassign with Set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// ArrayAppend attempts to append a value onto a JSON array at a path. If the
|
||||||
|
// target is not a JSON array then it will be converted into one, with its
|
||||||
|
// original contents set to the first element of the array.
|
||||||
|
func (g *Container) ArrayAppend(value interface{}, hierarchy ...string) error {
|
||||||
|
if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok {
|
||||||
|
array = append(array, value)
|
||||||
|
_, err := g.Set(array, hierarchy...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newArray := []interface{}{}
|
||||||
|
if d := g.Search(hierarchy...).Data(); d != nil {
|
||||||
|
newArray = append(newArray, d)
|
||||||
|
}
|
||||||
|
newArray = append(newArray, value)
|
||||||
|
|
||||||
|
_, err := g.Set(newArray, hierarchy...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayAppendP attempts to append a value onto a JSON array at a path using dot
|
||||||
|
// notation. If the target is not a JSON array then it will be converted into
|
||||||
|
// one, with its original contents set to the first element of the array.
|
||||||
|
func (g *Container) ArrayAppendP(value interface{}, path string) error {
|
||||||
|
return g.ArrayAppend(value, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayConcat attempts to append a value onto a JSON array at a path. If the
|
||||||
|
// target is not a JSON array then it will be converted into one, with its
|
||||||
|
// original contents set to the first element of the array.
|
||||||
|
//
|
||||||
|
// ArrayConcat differs from ArrayAppend in that it will expand a value type
|
||||||
|
// []interface{} during the append operation, resulting in concatenation of each
|
||||||
|
// element, rather than append as a single element of []interface{}.
|
||||||
|
func (g *Container) ArrayConcat(value interface{}, hierarchy ...string) error {
|
||||||
|
var array []interface{}
|
||||||
|
if d := g.Search(hierarchy...).Data(); d != nil {
|
||||||
|
if targetArray, ok := d.([]interface{}); !ok {
|
||||||
|
// If the data exists, and it is not a slice of interface,
|
||||||
|
// append it as the first element of our new array.
|
||||||
|
array = append(array, d)
|
||||||
|
} else {
|
||||||
|
// If the data exists, and it is a slice of interface,
|
||||||
|
// assign it to our variable.
|
||||||
|
array = targetArray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := value.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
// If we have been given a slice of interface, expand it when appending.
|
||||||
|
array = append(array, v...)
|
||||||
|
default:
|
||||||
|
array = append(array, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := g.Set(array, hierarchy...)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayConcatP attempts to append a value onto a JSON array at a path using dot
|
||||||
|
// notation. If the target is not a JSON array then it will be converted into one,
|
||||||
|
// with its original contents set to the first element of the array.
|
||||||
|
//
|
||||||
|
// ArrayConcatP differs from ArrayAppendP in that it will expand a value type
|
||||||
|
// []interface{} during the append operation, resulting in concatenation of each
|
||||||
|
// element, rather than append as a single element of []interface{}.
|
||||||
|
func (g *Container) ArrayConcatP(value interface{}, path string) error {
|
||||||
|
return g.ArrayConcat(value, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayRemove attempts to remove an element identified by an index from a JSON
|
||||||
|
// array at a path.
|
||||||
|
func (g *Container) ArrayRemove(index int, hierarchy ...string) error {
|
||||||
|
if index < 0 {
|
||||||
|
return ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array, ok := g.Search(hierarchy...).Data().([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return ErrNotArray
|
||||||
|
}
|
||||||
|
if index < len(array) {
|
||||||
|
array = append(array[:index], array[index+1:]...)
|
||||||
|
} else {
|
||||||
|
return ErrOutOfBounds
|
||||||
|
}
|
||||||
|
_, err := g.Set(array, hierarchy...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayRemoveP attempts to remove an element identified by an index from a JSON
|
||||||
|
// array at a path using dot notation.
|
||||||
|
func (g *Container) ArrayRemoveP(index int, path string) error {
|
||||||
|
return g.ArrayRemove(index, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayElement attempts to access an element by an index from a JSON array at a
|
||||||
|
// path.
|
||||||
|
func (g *Container) ArrayElement(index int, hierarchy ...string) (*Container, error) {
|
||||||
|
if index < 0 {
|
||||||
|
return nil, ErrOutOfBounds
|
||||||
|
}
|
||||||
|
array, ok := g.Search(hierarchy...).Data().([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, ErrNotArray
|
||||||
|
}
|
||||||
|
if index < len(array) {
|
||||||
|
return &Container{array[index]}, nil
|
||||||
|
}
|
||||||
|
return nil, ErrOutOfBounds
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayElementP attempts to access an element by an index from a JSON array at
|
||||||
|
// a path using dot notation.
|
||||||
|
func (g *Container) ArrayElementP(index int, path string) (*Container, error) {
|
||||||
|
return g.ArrayElement(index, DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayCount counts the number of elements in a JSON array at a path.
|
||||||
|
func (g *Container) ArrayCount(hierarchy ...string) (int, error) {
|
||||||
|
if array, ok := g.Search(hierarchy...).Data().([]interface{}); ok {
|
||||||
|
return len(array), nil
|
||||||
|
}
|
||||||
|
return 0, ErrNotArray
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayCountP counts the number of elements in a JSON array at a path using dot
|
||||||
|
// notation.
|
||||||
|
func (g *Container) ArrayCountP(path string) (int, error) {
|
||||||
|
return g.ArrayCount(DotPathToSlice(path)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
func walkObject(path string, obj, flat map[string]interface{}, includeEmpty bool) {
|
||||||
|
if includeEmpty && len(obj) == 0 {
|
||||||
|
flat[path] = struct{}{}
|
||||||
|
}
|
||||||
|
for elePath, v := range obj {
|
||||||
|
if len(path) > 0 {
|
||||||
|
elePath = path + "." + elePath
|
||||||
|
}
|
||||||
|
switch t := v.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
walkObject(elePath, t, flat, includeEmpty)
|
||||||
|
case []interface{}:
|
||||||
|
walkArray(elePath, t, flat, includeEmpty)
|
||||||
|
default:
|
||||||
|
flat[elePath] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkArray(path string, arr []interface{}, flat map[string]interface{}, includeEmpty bool) {
|
||||||
|
if includeEmpty && len(arr) == 0 {
|
||||||
|
flat[path] = []struct{}{}
|
||||||
|
}
|
||||||
|
for i, ele := range arr {
|
||||||
|
elePath := strconv.Itoa(i)
|
||||||
|
if len(path) > 0 {
|
||||||
|
elePath = path + "." + elePath
|
||||||
|
}
|
||||||
|
switch t := ele.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
walkObject(elePath, t, flat, includeEmpty)
|
||||||
|
case []interface{}:
|
||||||
|
walkArray(elePath, t, flat, includeEmpty)
|
||||||
|
default:
|
||||||
|
flat[elePath] = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flatten a JSON array or object into an object of key/value pairs for each
|
||||||
|
// field, where the key is the full path of the structured field in dot path
|
||||||
|
// notation matching the spec for the method Path.
|
||||||
|
//
|
||||||
|
// E.g. the structure `{"foo":[{"bar":"1"},{"bar":"2"}]}` would flatten into the
|
||||||
|
// object: `{"foo.0.bar":"1","foo.1.bar":"2"}`. `{"foo": [{"bar":[]},{"bar":{}}]}`
|
||||||
|
// would flatten into the object `{}`
|
||||||
|
//
|
||||||
|
// Returns an error if the target is not a JSON object or array.
|
||||||
|
func (g *Container) Flatten() (map[string]interface{}, error) {
|
||||||
|
return g.flatten(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlattenIncludeEmpty a JSON array or object into an object of key/value pairs
|
||||||
|
// for each field, just as Flatten, but includes empty arrays and objects, where
|
||||||
|
// the key is the full path of the structured field in dot path notation matching
|
||||||
|
// the spec for the method Path.
|
||||||
|
//
|
||||||
|
// E.g. the structure `{"foo": [{"bar":[]},{"bar":{}}]}` would flatten into the
|
||||||
|
// object: `{"foo.0.bar":[],"foo.1.bar":{}}`.
|
||||||
|
//
|
||||||
|
// Returns an error if the target is not a JSON object or array.
|
||||||
|
func (g *Container) FlattenIncludeEmpty() (map[string]interface{}, error) {
|
||||||
|
return g.flatten(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Container) flatten(includeEmpty bool) (map[string]interface{}, error) {
|
||||||
|
flattened := map[string]interface{}{}
|
||||||
|
switch t := g.Data().(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
walkObject("", t, flattened, includeEmpty)
|
||||||
|
case []interface{}:
|
||||||
|
walkArray("", t, flattened, includeEmpty)
|
||||||
|
default:
|
||||||
|
return nil, ErrNotObjOrArray
|
||||||
|
}
|
||||||
|
return flattened, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Bytes marshals an element to a JSON []byte blob.
|
||||||
|
func (g *Container) Bytes() []byte {
|
||||||
|
if data, err := json.Marshal(g.Data()); err == nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return []byte("null")
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesIndent marshals an element to a JSON []byte blob formatted with a prefix
|
||||||
|
// and indent string.
|
||||||
|
func (g *Container) BytesIndent(prefix, indent string) []byte {
|
||||||
|
if g.object != nil {
|
||||||
|
if data, err := json.MarshalIndent(g.Data(), prefix, indent); err == nil {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []byte("null")
|
||||||
|
}
|
||||||
|
|
||||||
|
// String marshals an element to a JSON formatted string.
|
||||||
|
func (g *Container) String() string {
|
||||||
|
return string(g.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringIndent marshals an element to a JSON string formatted with a prefix and
|
||||||
|
// indent string.
|
||||||
|
func (g *Container) StringIndent(prefix, indent string) string {
|
||||||
|
return string(g.BytesIndent(prefix, indent))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeOpt is a functional option for the EncodeJSON method.
|
||||||
|
type EncodeOpt func(e *json.Encoder)
|
||||||
|
|
||||||
|
// EncodeOptHTMLEscape sets the encoder to escape the JSON for html.
|
||||||
|
func EncodeOptHTMLEscape(doEscape bool) EncodeOpt {
|
||||||
|
return func(e *json.Encoder) {
|
||||||
|
e.SetEscapeHTML(doEscape)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeOptIndent sets the encoder to indent the JSON output.
|
||||||
|
func EncodeOptIndent(prefix, indent string) EncodeOpt {
|
||||||
|
return func(e *json.Encoder) {
|
||||||
|
e.SetIndent(prefix, indent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeJSON marshals an element to a JSON formatted []byte using a variant
|
||||||
|
// list of modifier functions for the encoder being used. Functions for
|
||||||
|
// modifying the output are prefixed with EncodeOpt, e.g. EncodeOptHTMLEscape.
|
||||||
|
func (g *Container) EncodeJSON(encodeOpts ...EncodeOpt) []byte {
|
||||||
|
var b bytes.Buffer
|
||||||
|
encoder := json.NewEncoder(&b)
|
||||||
|
encoder.SetEscapeHTML(false) // Do not escape by default.
|
||||||
|
for _, opt := range encodeOpts {
|
||||||
|
opt(encoder)
|
||||||
|
}
|
||||||
|
if err := encoder.Encode(g.object); err != nil {
|
||||||
|
return []byte("null")
|
||||||
|
}
|
||||||
|
result := b.Bytes()
|
||||||
|
if len(result) > 0 {
|
||||||
|
result = result[:len(result)-1]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new gabs JSON object.
|
||||||
|
func New() *Container {
|
||||||
|
return &Container{map[string]interface{}{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap an already unmarshalled JSON object (or a new map[string]interface{})
|
||||||
|
// into a *Container.
|
||||||
|
func Wrap(root interface{}) *Container {
|
||||||
|
return &Container{root}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSON unmarshals a JSON byte slice into a *Container.
|
||||||
|
func ParseJSON(sample []byte) (*Container, error) {
|
||||||
|
var gabs Container
|
||||||
|
|
||||||
|
if err := json.Unmarshal(sample, &gabs.object); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gabs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONDecoder applies a json.Decoder to a *Container.
|
||||||
|
func ParseJSONDecoder(decoder *json.Decoder) (*Container, error) {
|
||||||
|
var gabs Container
|
||||||
|
|
||||||
|
if err := decoder.Decode(&gabs.object); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gabs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONFile reads a file and unmarshals the contents into a *Container.
|
||||||
|
func ParseJSONFile(path string) (*Container, error) {
|
||||||
|
if len(path) > 0 {
|
||||||
|
cBytes, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
container, err := ParseJSON(cBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return container, nil
|
||||||
|
}
|
||||||
|
return nil, ErrInvalidPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseJSONBuffer reads a buffer and unmarshals the contents into a *Container.
|
||||||
|
func ParseJSONBuffer(buffer io.Reader) (*Container, error) {
|
||||||
|
var gabs Container
|
||||||
|
jsonDecoder := json.NewDecoder(buffer)
|
||||||
|
if err := jsonDecoder.Decode(&gabs.object); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gabs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON returns the JSON encoding of this container. This allows
|
||||||
|
// structs which contain Container instances to be marshaled using
|
||||||
|
// json.Marshal().
|
||||||
|
func (g *Container) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(g.Data())
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
BIN
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/gabs_logo.png
generated
vendored
Normal file
BIN
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/gabs_logo.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
44
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/migration.md
generated
vendored
Normal file
44
.gear/predownloaded-development/vendor/github.com/Jeffail/gabs/v2/migration.md
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
Migration Guides
|
||||||
|
================
|
||||||
|
|
||||||
|
## Migrating to Version 2
|
||||||
|
|
||||||
|
### Path
|
||||||
|
|
||||||
|
Previously it was not possible to specify a dot path where a key itself contains a dot. In v2 it is now possible with the escape sequence `~1`. For example, given the JSON doc `{"foo":{"bar.baz":10}}`, the path `foo.bar~1baz` would return `10`. This escape sequence means the character `~` is also a special case, therefore it must also be escaped to the sequence `~0`.
|
||||||
|
|
||||||
|
### Consume
|
||||||
|
|
||||||
|
Calls to `Consume(root interface{}) (*Container, error)` should be replaced with `Wrap(root interface{}) *Container`.
|
||||||
|
|
||||||
|
The error response was removed in order to avoid unnecessary duplicate type checks on `root`. This also allows shorthand chained queries like `gabs.Wrap(foo).S("bar","baz").Data()`.
|
||||||
|
|
||||||
|
### Search Across Arrays
|
||||||
|
|
||||||
|
All query functions (`Search`, `Path`, `Set`, `SetP`, etc) now attempt to resolve a specific index when they encounter an array. This means path queries must specify an integer index at the level of arrays within the content.
|
||||||
|
|
||||||
|
For example, given the sample document:
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{
|
||||||
|
"foo": [
|
||||||
|
{
|
||||||
|
"bar": {
|
||||||
|
"baz": 45
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In v1 the query `Search("foo", "bar", "baz")` would propagate the array in the result giving us `[45]`. In v2 we can access the field directly with `Search("foo", "0", "bar", "baz")`. The index is _required_, otherwise the query fails.
|
||||||
|
|
||||||
|
In query functions that do not set a value it is possible to specify `*` instead of an index in order to obtain all elements of the array, this produces the equivalent result as the behaviour from v1. For example, in v2 the query `Search("foo", "*", "bar", "baz")` would return `[45]`.
|
||||||
|
|
||||||
|
### Children and ChildrenMap
|
||||||
|
|
||||||
|
The `Children` and `ChildrenMap` methods no longer return errors. Instead, in the event of the underlying value being invalid (not an array or object), a `nil` slice and empty map are returned respectively. If explicit type checking is required the recommended approach would be casting on the value, e.g. `foo, ok := obj.Data().([]interface)`.
|
||||||
|
|
||||||
|
### Serialising Invalid Types
|
||||||
|
|
||||||
|
In v1 attempting to serialise with `Bytes`, `String`, etc, with an invalid structure would result in an empty object `{}`. This behaviour was unintuitive and in v2 `null` will be returned instead. If explicit marshalling is required with proper error propagation it is still recommended to use the `json` package directly on the underlying value.
|
||||||
1
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/.gitattributes
generated
vendored
Normal file
1
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/.gitattributes
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
* text=auto eol=lf
|
||||||
10
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/.gitignore
generated
vendored
Normal file
10
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/.golangci.yml
generated
vendored
Normal file
147
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
1
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/CODEOWNERS
generated
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
* @microsoft/containerplat
|
||||||
22
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/LICENSE
generated
vendored
Normal file
22
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/README.md
generated
vendored
Normal file
89
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/SECURITY.md
generated
vendored
Normal file
41
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/backup.go
generated
vendored
Normal file
287
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/doc.go
generated
vendored
Normal file
22
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/ea.go
generated
vendored
Normal file
137
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
Normal file
320
.gear/predownloaded-development/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
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/fileinfo.go
generated
vendored
Normal file
106
.gear/predownloaded-development/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
|
||||||
|
}
|
||||||
582
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
582
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/hvsock.go
generated
vendored
Normal file
|
|
@ -0,0 +1,582 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/internal/socket"
|
||||||
|
"github.com/Microsoft/go-winio/pkg/guid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const afHVSock = 34 // AF_HYPERV
|
||||||
|
|
||||||
|
// Well known Service and VM IDs
|
||||||
|
// https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service#vmid-wildcards
|
||||||
|
|
||||||
|
// HvsockGUIDWildcard is the wildcard VmId for accepting connections from all partitions.
|
||||||
|
func HvsockGUIDWildcard() guid.GUID { // 00000000-0000-0000-0000-000000000000
|
||||||
|
return guid.GUID{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDBroadcast is the wildcard VmId for broadcasting sends to all partitions.
|
||||||
|
func HvsockGUIDBroadcast() guid.GUID { // ffffffff-ffff-ffff-ffff-ffffffffffff
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0xffffffff,
|
||||||
|
Data2: 0xffff,
|
||||||
|
Data3: 0xffff,
|
||||||
|
Data4: [8]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDLoopback is the Loopback VmId for accepting connections to the same partition as the connector.
|
||||||
|
func HvsockGUIDLoopback() guid.GUID { // e0e16197-dd56-4a10-9195-5ee7a155a838
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0xe0e16197,
|
||||||
|
Data2: 0xdd56,
|
||||||
|
Data3: 0x4a10,
|
||||||
|
Data4: [8]uint8{0x91, 0x95, 0x5e, 0xe7, 0xa1, 0x55, 0xa8, 0x38},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDSiloHost is the address of a silo's host partition:
|
||||||
|
// - The silo host of a hosted silo is the utility VM.
|
||||||
|
// - The silo host of a silo on a physical host is the physical host.
|
||||||
|
func HvsockGUIDSiloHost() guid.GUID { // 36bd0c5c-7276-4223-88ba-7d03b654c568
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0x36bd0c5c,
|
||||||
|
Data2: 0x7276,
|
||||||
|
Data3: 0x4223,
|
||||||
|
Data4: [8]byte{0x88, 0xba, 0x7d, 0x03, 0xb6, 0x54, 0xc5, 0x68},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDChildren is the wildcard VmId for accepting connections from the connector's child partitions.
|
||||||
|
func HvsockGUIDChildren() guid.GUID { // 90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0x90db8b89,
|
||||||
|
Data2: 0xd35,
|
||||||
|
Data3: 0x4f79,
|
||||||
|
Data4: [8]uint8{0x8c, 0xe9, 0x49, 0xea, 0xa, 0xc8, 0xb7, 0xcd},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockGUIDParent is the wildcard VmId for accepting connections from the connector's parent partition.
|
||||||
|
// Listening on this VmId accepts connection from:
|
||||||
|
// - Inside silos: silo host partition.
|
||||||
|
// - Inside hosted silo: host of the VM.
|
||||||
|
// - Inside VM: VM host.
|
||||||
|
// - Physical host: Not supported.
|
||||||
|
func HvsockGUIDParent() guid.GUID { // a42e7cda-d03f-480c-9cc2-a4de20abb878
|
||||||
|
return guid.GUID{
|
||||||
|
Data1: 0xa42e7cda,
|
||||||
|
Data2: 0xd03f,
|
||||||
|
Data3: 0x480c,
|
||||||
|
Data4: [8]uint8{0x9c, 0xc2, 0xa4, 0xde, 0x20, 0xab, 0xb8, 0x78},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// hvsockVsockServiceTemplate is the Service GUID used for the VSOCK protocol.
|
||||||
|
func hvsockVsockServiceTemplate() guid.GUID { // 00000000-facb-11e6-bd58-64006a7986d3
|
||||||
|
return guid.GUID{
|
||||||
|
Data2: 0xfacb,
|
||||||
|
Data3: 0x11e6,
|
||||||
|
Data4: [8]uint8{0xbd, 0x58, 0x64, 0x00, 0x6a, 0x79, 0x86, 0xd3},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An HvsockAddr is an address for a AF_HYPERV socket.
|
||||||
|
type HvsockAddr struct {
|
||||||
|
VMID guid.GUID
|
||||||
|
ServiceID guid.GUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawHvsockAddr struct {
|
||||||
|
Family uint16
|
||||||
|
_ uint16
|
||||||
|
VMID guid.GUID
|
||||||
|
ServiceID guid.GUID
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ socket.RawSockaddr = &rawHvsockAddr{}
|
||||||
|
|
||||||
|
// Network returns the address's network name, "hvsock".
|
||||||
|
func (*HvsockAddr) Network() string {
|
||||||
|
return "hvsock"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) String() string {
|
||||||
|
return fmt.Sprintf("%s:%s", &addr.VMID, &addr.ServiceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VsockServiceID returns an hvsock service ID corresponding to the specified AF_VSOCK port.
|
||||||
|
func VsockServiceID(port uint32) guid.GUID {
|
||||||
|
g := hvsockVsockServiceTemplate() // make a copy
|
||||||
|
g.Data1 = port
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) raw() rawHvsockAddr {
|
||||||
|
return rawHvsockAddr{
|
||||||
|
Family: afHVSock,
|
||||||
|
VMID: addr.VMID,
|
||||||
|
ServiceID: addr.ServiceID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (addr *HvsockAddr) fromRaw(raw *rawHvsockAddr) {
|
||||||
|
addr.VMID = raw.VMID
|
||||||
|
addr.ServiceID = raw.ServiceID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sockaddr returns a pointer to and the size of this struct.
|
||||||
|
//
|
||||||
|
// Implements the [socket.RawSockaddr] interface, and allows use in
|
||||||
|
// [socket.Bind] and [socket.ConnectEx].
|
||||||
|
func (r *rawHvsockAddr) Sockaddr() (unsafe.Pointer, int32, error) {
|
||||||
|
return unsafe.Pointer(r), int32(unsafe.Sizeof(rawHvsockAddr{})), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sockaddr interface allows use with `sockets.Bind()` and `.ConnectEx()`.
|
||||||
|
func (r *rawHvsockAddr) FromBytes(b []byte) error {
|
||||||
|
n := int(unsafe.Sizeof(rawHvsockAddr{}))
|
||||||
|
|
||||||
|
if len(b) < n {
|
||||||
|
return fmt.Errorf("got %d, want %d: %w", len(b), n, socket.ErrBufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
copy(unsafe.Slice((*byte)(unsafe.Pointer(r)), n), b[:n])
|
||||||
|
if r.Family != afHVSock {
|
||||||
|
return fmt.Errorf("got %d, want %d: %w", r.Family, afHVSock, socket.ErrAddrFamily)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockListener is a socket listener for the AF_HYPERV address family.
|
||||||
|
type HvsockListener struct {
|
||||||
|
sock *win32File
|
||||||
|
addr HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Listener = &HvsockListener{}
|
||||||
|
|
||||||
|
// HvsockConn is a connected socket of the AF_HYPERV address family.
|
||||||
|
type HvsockConn struct {
|
||||||
|
sock *win32File
|
||||||
|
local, remote HvsockAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ net.Conn = &HvsockConn{}
|
||||||
|
|
||||||
|
func newHVSocket() (*win32File, error) {
|
||||||
|
fd, err := windows.Socket(afHVSock, windows.SOCK_STREAM, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, os.NewSyscallError("socket", err)
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(fd)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(fd)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f.socket = true
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenHvsock listens for connections on the specified hvsock address.
|
||||||
|
func ListenHvsock(addr *HvsockAddr) (_ *HvsockListener, err error) {
|
||||||
|
l := &HvsockListener{addr: *addr}
|
||||||
|
|
||||||
|
var sock *win32File
|
||||||
|
sock, err = newHVSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
_ = sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sa := addr.raw()
|
||||||
|
err = socket.Bind(sock.handle, &sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("socket", err))
|
||||||
|
}
|
||||||
|
err = windows.Listen(sock.handle, 16)
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("listen", os.NewSyscallError("listen", err))
|
||||||
|
}
|
||||||
|
return &HvsockListener{sock: sock, addr: *addr}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *HvsockListener) opErr(op string, err error) error {
|
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Addr: &l.addr, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addr returns the listener's network address.
|
||||||
|
func (l *HvsockListener) Addr() net.Addr {
|
||||||
|
return &l.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accept waits for the next connection and returns it.
|
||||||
|
func (l *HvsockListener) Accept() (_ net.Conn, err error) {
|
||||||
|
sock, err := newHVSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if sock != nil {
|
||||||
|
sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
c, err := l.sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return nil, l.opErr("accept", err)
|
||||||
|
}
|
||||||
|
defer l.sock.wg.Done()
|
||||||
|
|
||||||
|
// AcceptEx, per documentation, requires an extra 16 bytes per address.
|
||||||
|
//
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-acceptex
|
||||||
|
const addrlen = uint32(16 + unsafe.Sizeof(rawHvsockAddr{}))
|
||||||
|
var addrbuf [addrlen * 2]byte
|
||||||
|
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.AcceptEx(l.sock.handle, sock.handle, &addrbuf[0], 0 /* rxdatalen */, addrlen, addrlen, &bytes, &c.o)
|
||||||
|
if _, err = l.sock.asyncIO(c, nil, bytes, err); err != nil {
|
||||||
|
return nil, l.opErr("accept", os.NewSyscallError("acceptex", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
conn := &HvsockConn{
|
||||||
|
sock: sock,
|
||||||
|
}
|
||||||
|
// The local address returned in the AcceptEx buffer is the same as the Listener socket's
|
||||||
|
// address. However, the service GUID reported by GetSockName is different from the Listeners
|
||||||
|
// socket, and is sometimes the same as the local address of the socket that dialed the
|
||||||
|
// address, with the service GUID.Data1 incremented, but othertimes is different.
|
||||||
|
// todo: does the local address matter? is the listener's address or the actual address appropriate?
|
||||||
|
conn.local.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[0])))
|
||||||
|
conn.remote.fromRaw((*rawHvsockAddr)(unsafe.Pointer(&addrbuf[addrlen])))
|
||||||
|
|
||||||
|
// initialize the accepted socket and update its properties with those of the listening socket
|
||||||
|
if err = windows.Setsockopt(sock.handle,
|
||||||
|
windows.SOL_SOCKET, windows.SO_UPDATE_ACCEPT_CONTEXT,
|
||||||
|
(*byte)(unsafe.Pointer(&l.sock.handle)), int32(unsafe.Sizeof(l.sock.handle))); err != nil {
|
||||||
|
return nil, conn.opErr("accept", os.NewSyscallError("setsockopt", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
sock = nil
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the listener, causing any pending Accept calls to fail.
|
||||||
|
func (l *HvsockListener) Close() error {
|
||||||
|
return l.sock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HvsockDialer configures and dials a Hyper-V Socket (ie, [HvsockConn]).
|
||||||
|
type HvsockDialer struct {
|
||||||
|
// Deadline is the time the Dial operation must connect before erroring.
|
||||||
|
Deadline time.Time
|
||||||
|
|
||||||
|
// Retries is the number of additional connects to try if the connection times out, is refused,
|
||||||
|
// or the host is unreachable
|
||||||
|
Retries uint
|
||||||
|
|
||||||
|
// RetryWait is the time to wait after a connection error to retry
|
||||||
|
RetryWait time.Duration
|
||||||
|
|
||||||
|
rt *time.Timer // redial wait timer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial the Hyper-V socket at addr.
|
||||||
|
//
|
||||||
|
// See [HvsockDialer.Dial] for more information.
|
||||||
|
func Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
||||||
|
return (&HvsockDialer{}).Dial(ctx, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial attempts to connect to the Hyper-V socket at addr, and returns a connection if successful.
|
||||||
|
// Will attempt (HvsockDialer).Retries if dialing fails, waiting (HvsockDialer).RetryWait between
|
||||||
|
// retries.
|
||||||
|
//
|
||||||
|
// Dialing can be cancelled either by providing (HvsockDialer).Deadline, or cancelling ctx.
|
||||||
|
func (d *HvsockDialer) Dial(ctx context.Context, addr *HvsockAddr) (conn *HvsockConn, err error) {
|
||||||
|
op := "dial"
|
||||||
|
// create the conn early to use opErr()
|
||||||
|
conn = &HvsockConn{
|
||||||
|
remote: *addr,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !d.Deadline.IsZero() {
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
ctx, cancel = context.WithDeadline(ctx, d.Deadline)
|
||||||
|
defer cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// preemptive timeout/cancellation check
|
||||||
|
if err = ctx.Err(); err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sock, err := newHVSocket()
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if sock != nil {
|
||||||
|
sock.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
sa := addr.raw()
|
||||||
|
err = socket.Bind(sock.handle, &sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("bind", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
defer sock.wg.Done()
|
||||||
|
var bytes uint32
|
||||||
|
for i := uint(0); i <= d.Retries; i++ {
|
||||||
|
err = socket.ConnectEx(
|
||||||
|
sock.handle,
|
||||||
|
&sa,
|
||||||
|
nil, // sendBuf
|
||||||
|
0, // sendDataLen
|
||||||
|
&bytes,
|
||||||
|
(*windows.Overlapped)(unsafe.Pointer(&c.o)))
|
||||||
|
_, err = sock.asyncIO(c, nil, bytes, err)
|
||||||
|
if i < d.Retries && canRedial(err) {
|
||||||
|
if err = d.redialWait(ctx); err == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("connectex", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the connection properties, so shutdown can be used
|
||||||
|
if err = windows.Setsockopt(
|
||||||
|
sock.handle,
|
||||||
|
windows.SOL_SOCKET,
|
||||||
|
windows.SO_UPDATE_CONNECT_CONTEXT,
|
||||||
|
nil, // optvalue
|
||||||
|
0, // optlen
|
||||||
|
); err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("setsockopt", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the local name
|
||||||
|
var sal rawHvsockAddr
|
||||||
|
err = socket.GetSockName(sock.handle, &sal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, conn.opErr(op, os.NewSyscallError("getsockname", err))
|
||||||
|
}
|
||||||
|
conn.local.fromRaw(&sal)
|
||||||
|
|
||||||
|
// one last check for timeout, since asyncIO doesn't check the context
|
||||||
|
if err = ctx.Err(); err != nil {
|
||||||
|
return nil, conn.opErr(op, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.sock = sock
|
||||||
|
sock = nil
|
||||||
|
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// redialWait waits before attempting to redial, resetting the timer as appropriate.
|
||||||
|
func (d *HvsockDialer) redialWait(ctx context.Context) (err error) {
|
||||||
|
if d.RetryWait == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.rt == nil {
|
||||||
|
d.rt = time.NewTimer(d.RetryWait)
|
||||||
|
} else {
|
||||||
|
// should already be stopped and drained
|
||||||
|
d.rt.Reset(d.RetryWait)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-d.rt.C:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stop and drain the timer
|
||||||
|
if !d.rt.Stop() {
|
||||||
|
<-d.rt.C
|
||||||
|
}
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes error is a plain, unwrapped windows.Errno provided by direct syscall.
|
||||||
|
func canRedial(err error) bool {
|
||||||
|
//nolint:errorlint // guaranteed to be an Errno
|
||||||
|
switch err {
|
||||||
|
case windows.WSAECONNREFUSED, windows.WSAENETUNREACH, windows.WSAETIMEDOUT,
|
||||||
|
windows.ERROR_CONNECTION_REFUSED, windows.ERROR_CONNECTION_UNAVAIL:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) opErr(op string, err error) error {
|
||||||
|
// translate from "file closed" to "socket closed"
|
||||||
|
if errors.Is(err, ErrFileClosed) {
|
||||||
|
err = socket.ErrSocketClosed
|
||||||
|
}
|
||||||
|
return &net.OpError{Op: op, Net: "hvsock", Source: &conn.local, Addr: &conn.remote, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) Read(b []byte) (int, error) {
|
||||||
|
c, err := conn.sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return 0, conn.opErr("read", err)
|
||||||
|
}
|
||||||
|
defer conn.sock.wg.Done()
|
||||||
|
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var flags, bytes uint32
|
||||||
|
err = windows.WSARecv(conn.sock.handle, &buf, 1, &bytes, &flags, &c.o, nil)
|
||||||
|
n, err := conn.sock.asyncIO(c, &conn.sock.readDeadline, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
var eno windows.Errno
|
||||||
|
if errors.As(err, &eno) {
|
||||||
|
err = os.NewSyscallError("wsarecv", eno)
|
||||||
|
}
|
||||||
|
return 0, conn.opErr("read", err)
|
||||||
|
} else if n == 0 {
|
||||||
|
err = io.EOF
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) Write(b []byte) (int, error) {
|
||||||
|
t := 0
|
||||||
|
for len(b) != 0 {
|
||||||
|
n, err := conn.write(b)
|
||||||
|
if err != nil {
|
||||||
|
return t + n, err
|
||||||
|
}
|
||||||
|
t += n
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) write(b []byte) (int, error) {
|
||||||
|
c, err := conn.sock.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return 0, conn.opErr("write", err)
|
||||||
|
}
|
||||||
|
defer conn.sock.wg.Done()
|
||||||
|
buf := windows.WSABuf{Buf: &b[0], Len: uint32(len(b))}
|
||||||
|
var bytes uint32
|
||||||
|
err = windows.WSASend(conn.sock.handle, &buf, 1, &bytes, 0, &c.o, nil)
|
||||||
|
n, err := conn.sock.asyncIO(c, &conn.sock.writeDeadline, bytes, err)
|
||||||
|
if err != nil {
|
||||||
|
var eno windows.Errno
|
||||||
|
if errors.As(err, &eno) {
|
||||||
|
err = os.NewSyscallError("wsasend", eno)
|
||||||
|
}
|
||||||
|
return 0, conn.opErr("write", err)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the socket connection, failing any pending read or write calls.
|
||||||
|
func (conn *HvsockConn) Close() error {
|
||||||
|
return conn.sock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (conn *HvsockConn) IsClosed() bool {
|
||||||
|
return conn.sock.IsClosed()
|
||||||
|
}
|
||||||
|
|
||||||
|
// shutdown disables sending or receiving on a socket.
|
||||||
|
func (conn *HvsockConn) shutdown(how int) error {
|
||||||
|
if conn.IsClosed() {
|
||||||
|
return socket.ErrSocketClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
err := windows.Shutdown(conn.sock.handle, how)
|
||||||
|
if err != nil {
|
||||||
|
// If the connection was closed, shutdowns fail with "not connected"
|
||||||
|
if errors.Is(err, windows.WSAENOTCONN) ||
|
||||||
|
errors.Is(err, windows.WSAESHUTDOWN) {
|
||||||
|
err = socket.ErrSocketClosed
|
||||||
|
}
|
||||||
|
return os.NewSyscallError("shutdown", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseRead shuts down the read end of the socket, preventing future read operations.
|
||||||
|
func (conn *HvsockConn) CloseRead() error {
|
||||||
|
err := conn.shutdown(windows.SHUT_RD)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("closeread", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite shuts down the write end of the socket, preventing future write operations and
|
||||||
|
// notifying the other endpoint that no more data will be written.
|
||||||
|
func (conn *HvsockConn) CloseWrite() error {
|
||||||
|
err := conn.shutdown(windows.SHUT_WR)
|
||||||
|
if err != nil {
|
||||||
|
return conn.opErr("closewrite", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the local address of the connection.
|
||||||
|
func (conn *HvsockConn) LocalAddr() net.Addr {
|
||||||
|
return &conn.local
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the remote address of the connection.
|
||||||
|
func (conn *HvsockConn) RemoteAddr() net.Addr {
|
||||||
|
return &conn.remote
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDeadline implements the net.Conn SetDeadline method.
|
||||||
|
func (conn *HvsockConn) SetDeadline(t time.Time) error {
|
||||||
|
// todo: implement `SetDeadline` for `win32File`
|
||||||
|
if err := conn.SetReadDeadline(t); err != nil {
|
||||||
|
return fmt.Errorf("set read deadline: %w", err)
|
||||||
|
}
|
||||||
|
if err := conn.SetWriteDeadline(t); err != nil {
|
||||||
|
return fmt.Errorf("set write deadline: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReadDeadline implements the net.Conn SetReadDeadline method.
|
||||||
|
func (conn *HvsockConn) SetReadDeadline(t time.Time) error {
|
||||||
|
return conn.sock.SetReadDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteDeadline implements the net.Conn SetWriteDeadline method.
|
||||||
|
func (conn *HvsockConn) SetWriteDeadline(t time.Time) error {
|
||||||
|
return conn.sock.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
2
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/doc.go
generated
vendored
Normal file
2
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// This package contains Win32 filesystem functionality.
|
||||||
|
package fs
|
||||||
262
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/fs.go
generated
vendored
Normal file
262
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/fs.go
generated
vendored
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/internal/stringbuffer"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go fs.go
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
|
||||||
|
//sys CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateFileW
|
||||||
|
|
||||||
|
const NullHandle windows.Handle = 0
|
||||||
|
|
||||||
|
// AccessMask defines standard, specific, and generic rights.
|
||||||
|
//
|
||||||
|
// Used with CreateFile and NtCreateFile (and co.).
|
||||||
|
//
|
||||||
|
// Bitmask:
|
||||||
|
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
|
||||||
|
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|
||||||
|
// +---------------+---------------+-------------------------------+
|
||||||
|
// |G|G|G|G|Resvd|A| StandardRights| SpecificRights |
|
||||||
|
// |R|W|E|A| |S| | |
|
||||||
|
// +-+-------------+---------------+-------------------------------+
|
||||||
|
//
|
||||||
|
// GR Generic Read
|
||||||
|
// GW Generic Write
|
||||||
|
// GE Generic Exectue
|
||||||
|
// GA Generic All
|
||||||
|
// Resvd Reserved
|
||||||
|
// AS Access Security System
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/secauthz/access-mask
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/secauthz/generic-access-rights
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-access-rights-constants
|
||||||
|
type AccessMask = windows.ACCESS_MASK
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// Not actually any.
|
||||||
|
//
|
||||||
|
// For CreateFile: "query certain metadata such as file, directory, or device attributes without accessing that file or device"
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#parameters
|
||||||
|
FILE_ANY_ACCESS AccessMask = 0
|
||||||
|
|
||||||
|
GENERIC_READ AccessMask = 0x8000_0000
|
||||||
|
GENERIC_WRITE AccessMask = 0x4000_0000
|
||||||
|
GENERIC_EXECUTE AccessMask = 0x2000_0000
|
||||||
|
GENERIC_ALL AccessMask = 0x1000_0000
|
||||||
|
ACCESS_SYSTEM_SECURITY AccessMask = 0x0100_0000
|
||||||
|
|
||||||
|
// Specific Object Access
|
||||||
|
// from ntioapi.h
|
||||||
|
|
||||||
|
FILE_READ_DATA AccessMask = (0x0001) // file & pipe
|
||||||
|
FILE_LIST_DIRECTORY AccessMask = (0x0001) // directory
|
||||||
|
|
||||||
|
FILE_WRITE_DATA AccessMask = (0x0002) // file & pipe
|
||||||
|
FILE_ADD_FILE AccessMask = (0x0002) // directory
|
||||||
|
|
||||||
|
FILE_APPEND_DATA AccessMask = (0x0004) // file
|
||||||
|
FILE_ADD_SUBDIRECTORY AccessMask = (0x0004) // directory
|
||||||
|
FILE_CREATE_PIPE_INSTANCE AccessMask = (0x0004) // named pipe
|
||||||
|
|
||||||
|
FILE_READ_EA AccessMask = (0x0008) // file & directory
|
||||||
|
FILE_READ_PROPERTIES AccessMask = FILE_READ_EA
|
||||||
|
|
||||||
|
FILE_WRITE_EA AccessMask = (0x0010) // file & directory
|
||||||
|
FILE_WRITE_PROPERTIES AccessMask = FILE_WRITE_EA
|
||||||
|
|
||||||
|
FILE_EXECUTE AccessMask = (0x0020) // file
|
||||||
|
FILE_TRAVERSE AccessMask = (0x0020) // directory
|
||||||
|
|
||||||
|
FILE_DELETE_CHILD AccessMask = (0x0040) // directory
|
||||||
|
|
||||||
|
FILE_READ_ATTRIBUTES AccessMask = (0x0080) // all
|
||||||
|
|
||||||
|
FILE_WRITE_ATTRIBUTES AccessMask = (0x0100) // all
|
||||||
|
|
||||||
|
FILE_ALL_ACCESS AccessMask = (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x1FF)
|
||||||
|
FILE_GENERIC_READ AccessMask = (STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE)
|
||||||
|
FILE_GENERIC_WRITE AccessMask = (STANDARD_RIGHTS_WRITE | FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA | FILE_APPEND_DATA | SYNCHRONIZE)
|
||||||
|
FILE_GENERIC_EXECUTE AccessMask = (STANDARD_RIGHTS_EXECUTE | FILE_READ_ATTRIBUTES | FILE_EXECUTE | SYNCHRONIZE)
|
||||||
|
|
||||||
|
SPECIFIC_RIGHTS_ALL AccessMask = 0x0000FFFF
|
||||||
|
|
||||||
|
// Standard Access
|
||||||
|
// from ntseapi.h
|
||||||
|
|
||||||
|
DELETE AccessMask = 0x0001_0000
|
||||||
|
READ_CONTROL AccessMask = 0x0002_0000
|
||||||
|
WRITE_DAC AccessMask = 0x0004_0000
|
||||||
|
WRITE_OWNER AccessMask = 0x0008_0000
|
||||||
|
SYNCHRONIZE AccessMask = 0x0010_0000
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_REQUIRED AccessMask = 0x000F_0000
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_READ AccessMask = READ_CONTROL
|
||||||
|
STANDARD_RIGHTS_WRITE AccessMask = READ_CONTROL
|
||||||
|
STANDARD_RIGHTS_EXECUTE AccessMask = READ_CONTROL
|
||||||
|
|
||||||
|
STANDARD_RIGHTS_ALL AccessMask = 0x001F_0000
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileShareMode uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
FILE_SHARE_NONE FileShareMode = 0x00
|
||||||
|
FILE_SHARE_READ FileShareMode = 0x01
|
||||||
|
FILE_SHARE_WRITE FileShareMode = 0x02
|
||||||
|
FILE_SHARE_DELETE FileShareMode = 0x04
|
||||||
|
FILE_SHARE_VALID_FLAGS FileShareMode = 0x07
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileCreationDisposition uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// from winbase.h
|
||||||
|
|
||||||
|
CREATE_NEW FileCreationDisposition = 0x01
|
||||||
|
CREATE_ALWAYS FileCreationDisposition = 0x02
|
||||||
|
OPEN_EXISTING FileCreationDisposition = 0x03
|
||||||
|
OPEN_ALWAYS FileCreationDisposition = 0x04
|
||||||
|
TRUNCATE_EXISTING FileCreationDisposition = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create disposition values for NtCreate*
|
||||||
|
type NTFileCreationDisposition uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// From ntioapi.h
|
||||||
|
|
||||||
|
FILE_SUPERSEDE NTFileCreationDisposition = 0x00
|
||||||
|
FILE_OPEN NTFileCreationDisposition = 0x01
|
||||||
|
FILE_CREATE NTFileCreationDisposition = 0x02
|
||||||
|
FILE_OPEN_IF NTFileCreationDisposition = 0x03
|
||||||
|
FILE_OVERWRITE NTFileCreationDisposition = 0x04
|
||||||
|
FILE_OVERWRITE_IF NTFileCreationDisposition = 0x05
|
||||||
|
FILE_MAXIMUM_DISPOSITION NTFileCreationDisposition = 0x05
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateFile and co. take flags or attributes together as one parameter.
|
||||||
|
// Define alias until we can use generics to allow both
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
|
||||||
|
type FileFlagOrAttribute uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// from winnt.h
|
||||||
|
|
||||||
|
FILE_FLAG_WRITE_THROUGH FileFlagOrAttribute = 0x8000_0000
|
||||||
|
FILE_FLAG_OVERLAPPED FileFlagOrAttribute = 0x4000_0000
|
||||||
|
FILE_FLAG_NO_BUFFERING FileFlagOrAttribute = 0x2000_0000
|
||||||
|
FILE_FLAG_RANDOM_ACCESS FileFlagOrAttribute = 0x1000_0000
|
||||||
|
FILE_FLAG_SEQUENTIAL_SCAN FileFlagOrAttribute = 0x0800_0000
|
||||||
|
FILE_FLAG_DELETE_ON_CLOSE FileFlagOrAttribute = 0x0400_0000
|
||||||
|
FILE_FLAG_BACKUP_SEMANTICS FileFlagOrAttribute = 0x0200_0000
|
||||||
|
FILE_FLAG_POSIX_SEMANTICS FileFlagOrAttribute = 0x0100_0000
|
||||||
|
FILE_FLAG_OPEN_REPARSE_POINT FileFlagOrAttribute = 0x0020_0000
|
||||||
|
FILE_FLAG_OPEN_NO_RECALL FileFlagOrAttribute = 0x0010_0000
|
||||||
|
FILE_FLAG_FIRST_PIPE_INSTANCE FileFlagOrAttribute = 0x0008_0000
|
||||||
|
)
|
||||||
|
|
||||||
|
// NtCreate* functions take a dedicated CreateOptions parameter.
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/Winternl/nf-winternl-ntcreatefile
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/devnotes/nt-create-named-pipe-file
|
||||||
|
type NTCreateOptions uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// From ntioapi.h
|
||||||
|
|
||||||
|
FILE_DIRECTORY_FILE NTCreateOptions = 0x0000_0001
|
||||||
|
FILE_WRITE_THROUGH NTCreateOptions = 0x0000_0002
|
||||||
|
FILE_SEQUENTIAL_ONLY NTCreateOptions = 0x0000_0004
|
||||||
|
FILE_NO_INTERMEDIATE_BUFFERING NTCreateOptions = 0x0000_0008
|
||||||
|
|
||||||
|
FILE_SYNCHRONOUS_IO_ALERT NTCreateOptions = 0x0000_0010
|
||||||
|
FILE_SYNCHRONOUS_IO_NONALERT NTCreateOptions = 0x0000_0020
|
||||||
|
FILE_NON_DIRECTORY_FILE NTCreateOptions = 0x0000_0040
|
||||||
|
FILE_CREATE_TREE_CONNECTION NTCreateOptions = 0x0000_0080
|
||||||
|
|
||||||
|
FILE_COMPLETE_IF_OPLOCKED NTCreateOptions = 0x0000_0100
|
||||||
|
FILE_NO_EA_KNOWLEDGE NTCreateOptions = 0x0000_0200
|
||||||
|
FILE_DISABLE_TUNNELING NTCreateOptions = 0x0000_0400
|
||||||
|
FILE_RANDOM_ACCESS NTCreateOptions = 0x0000_0800
|
||||||
|
|
||||||
|
FILE_DELETE_ON_CLOSE NTCreateOptions = 0x0000_1000
|
||||||
|
FILE_OPEN_BY_FILE_ID NTCreateOptions = 0x0000_2000
|
||||||
|
FILE_OPEN_FOR_BACKUP_INTENT NTCreateOptions = 0x0000_4000
|
||||||
|
FILE_NO_COMPRESSION NTCreateOptions = 0x0000_8000
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileSQSFlag = FileFlagOrAttribute
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
// from winbase.h
|
||||||
|
|
||||||
|
SECURITY_ANONYMOUS FileSQSFlag = FileSQSFlag(SecurityAnonymous << 16)
|
||||||
|
SECURITY_IDENTIFICATION FileSQSFlag = FileSQSFlag(SecurityIdentification << 16)
|
||||||
|
SECURITY_IMPERSONATION FileSQSFlag = FileSQSFlag(SecurityImpersonation << 16)
|
||||||
|
SECURITY_DELEGATION FileSQSFlag = FileSQSFlag(SecurityDelegation << 16)
|
||||||
|
|
||||||
|
SECURITY_SQOS_PRESENT FileSQSFlag = 0x0010_0000
|
||||||
|
SECURITY_VALID_SQOS_FLAGS FileSQSFlag = 0x001F_0000
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetFinalPathNameByHandle flags
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew#parameters
|
||||||
|
type GetFinalPathFlag uint32
|
||||||
|
|
||||||
|
//nolint:revive // SNAKE_CASE is not idiomatic in Go, but aligned with Win32 API.
|
||||||
|
const (
|
||||||
|
GetFinalPathDefaultFlag GetFinalPathFlag = 0x0
|
||||||
|
|
||||||
|
FILE_NAME_NORMALIZED GetFinalPathFlag = 0x0
|
||||||
|
FILE_NAME_OPENED GetFinalPathFlag = 0x8
|
||||||
|
|
||||||
|
VOLUME_NAME_DOS GetFinalPathFlag = 0x0
|
||||||
|
VOLUME_NAME_GUID GetFinalPathFlag = 0x1
|
||||||
|
VOLUME_NAME_NT GetFinalPathFlag = 0x2
|
||||||
|
VOLUME_NAME_NONE GetFinalPathFlag = 0x4
|
||||||
|
)
|
||||||
|
|
||||||
|
// getFinalPathNameByHandle facilitates calling the Windows API GetFinalPathNameByHandle
|
||||||
|
// with the given handle and flags. It transparently takes care of creating a buffer of the
|
||||||
|
// correct size for the call.
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew
|
||||||
|
func GetFinalPathNameByHandle(h windows.Handle, flags GetFinalPathFlag) (string, error) {
|
||||||
|
b := stringbuffer.NewWString()
|
||||||
|
//TODO: can loop infinitely if Win32 keeps returning the same (or a larger) n?
|
||||||
|
for {
|
||||||
|
n, err := windows.GetFinalPathNameByHandle(h, b.Pointer(), b.Cap(), uint32(flags))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// If the buffer wasn't large enough, n will be the total size needed (including null terminator).
|
||||||
|
// Resize and try again.
|
||||||
|
if n > b.Cap() {
|
||||||
|
b.ResizeTo(n)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the buffer is large enough, n will be the size not including the null terminator.
|
||||||
|
// Convert to a Go string and return.
|
||||||
|
return b.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
12
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/security.go
generated
vendored
Normal file
12
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/security.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ne-winnt-security_impersonation_level
|
||||||
|
type SecurityImpersonationLevel int32 // C default enums underlying type is `int`, which is Go `int32`
|
||||||
|
|
||||||
|
// Impersonation levels
|
||||||
|
const (
|
||||||
|
SecurityAnonymous SecurityImpersonationLevel = 0
|
||||||
|
SecurityIdentification SecurityImpersonationLevel = 1
|
||||||
|
SecurityImpersonation SecurityImpersonationLevel = 2
|
||||||
|
SecurityDelegation SecurityImpersonationLevel = 3
|
||||||
|
)
|
||||||
61
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go
generated
vendored
Normal file
61
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/fs/zsyscall_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
|
||||||
|
procCreateFileW = modkernel32.NewProc("CreateFileW")
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateFile(name string, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _CreateFile(_p0, access, mode, sa, createmode, attrs, templatefile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _CreateFile(name *uint16, access AccessMask, mode FileShareMode, sa *windows.SecurityAttributes, createmode FileCreationDisposition, attrs FileFlagOrAttribute, templatefile windows.Handle) (handle windows.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.SyscallN(procCreateFileW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile))
|
||||||
|
handle = windows.Handle(r0)
|
||||||
|
if handle == windows.InvalidHandle {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
20
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.go
generated
vendored
Normal file
20
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/socket/rawaddr.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RawSockaddr allows structs to be used with [Bind] and [ConnectEx]. The
|
||||||
|
// struct must meet the Win32 sockaddr requirements specified here:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/winsock/sockaddr-2
|
||||||
|
//
|
||||||
|
// Specifically, the struct size must be least larger than an int16 (unsigned short)
|
||||||
|
// for the address family.
|
||||||
|
type RawSockaddr interface {
|
||||||
|
// Sockaddr returns a pointer to the RawSockaddr and its struct size, allowing
|
||||||
|
// for the RawSockaddr's data to be overwritten by syscalls (if necessary).
|
||||||
|
//
|
||||||
|
// It is the callers responsibility to validate that the values are valid; invalid
|
||||||
|
// pointers or size can cause a panic.
|
||||||
|
Sockaddr() (unsafe.Pointer, int32, error)
|
||||||
|
}
|
||||||
177
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/socket/socket.go
generated
vendored
Normal file
177
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/socket/socket.go
generated
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/pkg/guid"
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go socket.go
|
||||||
|
|
||||||
|
//sys getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getsockname
|
||||||
|
//sys getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) [failretval==socketError] = ws2_32.getpeername
|
||||||
|
//sys bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) [failretval==socketError] = ws2_32.bind
|
||||||
|
|
||||||
|
const socketError = uintptr(^uint32(0))
|
||||||
|
|
||||||
|
var (
|
||||||
|
// todo(helsaawy): create custom error types to store the desired vs actual size and addr family?
|
||||||
|
|
||||||
|
ErrBufferSize = errors.New("buffer size")
|
||||||
|
ErrAddrFamily = errors.New("address family")
|
||||||
|
ErrInvalidPointer = errors.New("invalid pointer")
|
||||||
|
ErrSocketClosed = fmt.Errorf("socket closed: %w", net.ErrClosed)
|
||||||
|
)
|
||||||
|
|
||||||
|
// todo(helsaawy): replace these with generics, ie: GetSockName[S RawSockaddr](s windows.Handle) (S, error)
|
||||||
|
|
||||||
|
// GetSockName writes the local address of socket s to the [RawSockaddr] rsa.
|
||||||
|
// If rsa is not large enough, the [windows.WSAEFAULT] is returned.
|
||||||
|
func GetSockName(s windows.Handle, rsa RawSockaddr) error {
|
||||||
|
ptr, l, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// although getsockname returns WSAEFAULT if the buffer is too small, it does not set
|
||||||
|
// &l to the correct size, so--apart from doubling the buffer repeatedly--there is no remedy
|
||||||
|
return getsockname(s, ptr, &l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPeerName returns the remote address the socket is connected to.
|
||||||
|
//
|
||||||
|
// See [GetSockName] for more information.
|
||||||
|
func GetPeerName(s windows.Handle, rsa RawSockaddr) error {
|
||||||
|
ptr, l, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getpeername(s, ptr, &l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Bind(s windows.Handle, rsa RawSockaddr) (err error) {
|
||||||
|
ptr, l, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not retrieve socket pointer and size: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return bind(s, ptr, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// "golang.org/x/sys/windows".ConnectEx and .Bind only accept internal implementations of the
|
||||||
|
// their sockaddr interface, so they cannot be used with HvsockAddr
|
||||||
|
// Replicate functionality here from
|
||||||
|
// https://cs.opensource.google/go/x/sys/+/master:windows/syscall_windows.go
|
||||||
|
|
||||||
|
// The function pointers to `AcceptEx`, `ConnectEx` and `GetAcceptExSockaddrs` must be loaded at
|
||||||
|
// runtime via a WSAIoctl call:
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/api/Mswsock/nc-mswsock-lpfn_connectex#remarks
|
||||||
|
|
||||||
|
type runtimeFunc struct {
|
||||||
|
id guid.GUID
|
||||||
|
once sync.Once
|
||||||
|
addr uintptr
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *runtimeFunc) Load() error {
|
||||||
|
f.once.Do(func() {
|
||||||
|
var s windows.Handle
|
||||||
|
s, f.err = windows.Socket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP)
|
||||||
|
if f.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer windows.CloseHandle(s) //nolint:errcheck
|
||||||
|
|
||||||
|
var n uint32
|
||||||
|
f.err = windows.WSAIoctl(s,
|
||||||
|
windows.SIO_GET_EXTENSION_FUNCTION_POINTER,
|
||||||
|
(*byte)(unsafe.Pointer(&f.id)),
|
||||||
|
uint32(unsafe.Sizeof(f.id)),
|
||||||
|
(*byte)(unsafe.Pointer(&f.addr)),
|
||||||
|
uint32(unsafe.Sizeof(f.addr)),
|
||||||
|
&n,
|
||||||
|
nil, // overlapped
|
||||||
|
0, // completionRoutine
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// todo: add `AcceptEx` and `GetAcceptExSockaddrs`
|
||||||
|
WSAID_CONNECTEX = guid.GUID{ //revive:disable-line:var-naming ALL_CAPS
|
||||||
|
Data1: 0x25a207b9,
|
||||||
|
Data2: 0xddf3,
|
||||||
|
Data3: 0x4660,
|
||||||
|
Data4: [8]byte{0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e},
|
||||||
|
}
|
||||||
|
|
||||||
|
connectExFunc = runtimeFunc{id: WSAID_CONNECTEX}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConnectEx(
|
||||||
|
fd windows.Handle,
|
||||||
|
rsa RawSockaddr,
|
||||||
|
sendBuf *byte,
|
||||||
|
sendDataLen uint32,
|
||||||
|
bytesSent *uint32,
|
||||||
|
overlapped *windows.Overlapped,
|
||||||
|
) error {
|
||||||
|
if err := connectExFunc.Load(); err != nil {
|
||||||
|
return fmt.Errorf("failed to load ConnectEx function pointer: %w", err)
|
||||||
|
}
|
||||||
|
ptr, n, err := rsa.Sockaddr()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return connectEx(fd, ptr, n, sendBuf, sendDataLen, bytesSent, overlapped)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BOOL LpfnConnectex(
|
||||||
|
// [in] SOCKET s,
|
||||||
|
// [in] const sockaddr *name,
|
||||||
|
// [in] int namelen,
|
||||||
|
// [in, optional] PVOID lpSendBuffer,
|
||||||
|
// [in] DWORD dwSendDataLength,
|
||||||
|
// [out] LPDWORD lpdwBytesSent,
|
||||||
|
// [in] LPOVERLAPPED lpOverlapped
|
||||||
|
// )
|
||||||
|
|
||||||
|
func connectEx(
|
||||||
|
s windows.Handle,
|
||||||
|
name unsafe.Pointer,
|
||||||
|
namelen int32,
|
||||||
|
sendBuf *byte,
|
||||||
|
sendDataLen uint32,
|
||||||
|
bytesSent *uint32,
|
||||||
|
overlapped *windows.Overlapped,
|
||||||
|
) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(connectExFunc.addr,
|
||||||
|
uintptr(s),
|
||||||
|
uintptr(name),
|
||||||
|
uintptr(namelen),
|
||||||
|
uintptr(unsafe.Pointer(sendBuf)),
|
||||||
|
uintptr(sendDataLen),
|
||||||
|
uintptr(unsafe.Pointer(bytesSent)),
|
||||||
|
uintptr(unsafe.Pointer(overlapped)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if r1 == 0 {
|
||||||
|
if e1 != 0 {
|
||||||
|
err = error(e1)
|
||||||
|
} else {
|
||||||
|
err = syscall.EINVAL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
69
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go
generated
vendored
Normal file
69
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/socket/zsyscall_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package socket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||||
|
|
||||||
|
procbind = modws2_32.NewProc("bind")
|
||||||
|
procgetpeername = modws2_32.NewProc("getpeername")
|
||||||
|
procgetsockname = modws2_32.NewProc("getsockname")
|
||||||
|
)
|
||||||
|
|
||||||
|
func bind(s windows.Handle, name unsafe.Pointer, namelen int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procbind.Addr(), uintptr(s), uintptr(name), uintptr(namelen))
|
||||||
|
if r1 == socketError {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getpeername(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procgetpeername.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
|
||||||
|
if r1 == socketError {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getsockname(s windows.Handle, name unsafe.Pointer, namelen *int32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procgetsockname.Addr(), uintptr(s), uintptr(name), uintptr(unsafe.Pointer(namelen)))
|
||||||
|
if r1 == socketError {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
132
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go
generated
vendored
Normal file
132
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/internal/stringbuffer/wstring.go
generated
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
package stringbuffer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: worth exporting and using in mkwinsyscall?
|
||||||
|
|
||||||
|
// Uint16BufferSize is the buffer size in the pool, chosen somewhat arbitrarily to accommodate
|
||||||
|
// large path strings:
|
||||||
|
// MAX_PATH (260) + size of volume GUID prefix (49) + null terminator = 310.
|
||||||
|
const MinWStringCap = 310
|
||||||
|
|
||||||
|
// use *[]uint16 since []uint16 creates an extra allocation where the slice header
|
||||||
|
// is copied to heap and then referenced via pointer in the interface header that sync.Pool
|
||||||
|
// stores.
|
||||||
|
var pathPool = sync.Pool{ // if go1.18+ adds Pool[T], use that to store []uint16 directly
|
||||||
|
New: func() interface{} {
|
||||||
|
b := make([]uint16, MinWStringCap)
|
||||||
|
return &b
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuffer() []uint16 { return *(pathPool.Get().(*[]uint16)) }
|
||||||
|
|
||||||
|
// freeBuffer copies the slice header data, and puts a pointer to that in the pool.
|
||||||
|
// This avoids taking a pointer to the slice header in WString, which can be set to nil.
|
||||||
|
func freeBuffer(b []uint16) { pathPool.Put(&b) }
|
||||||
|
|
||||||
|
// WString is a wide string buffer ([]uint16) meant for storing UTF-16 encoded strings
|
||||||
|
// for interacting with Win32 APIs.
|
||||||
|
// Sizes are specified as uint32 and not int.
|
||||||
|
//
|
||||||
|
// It is not thread safe.
|
||||||
|
type WString struct {
|
||||||
|
// type-def allows casting to []uint16 directly, use struct to prevent that and allow adding fields in the future.
|
||||||
|
|
||||||
|
// raw buffer
|
||||||
|
b []uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWString returns a [WString] allocated from a shared pool with an
|
||||||
|
// initial capacity of at least [MinWStringCap].
|
||||||
|
// Since the buffer may have been previously used, its contents are not guaranteed to be empty.
|
||||||
|
//
|
||||||
|
// The buffer should be freed via [WString.Free]
|
||||||
|
func NewWString() *WString {
|
||||||
|
return &WString{
|
||||||
|
b: newBuffer(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WString) Free() {
|
||||||
|
if b.empty() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
freeBuffer(b.b)
|
||||||
|
b.b = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResizeTo grows the buffer to at least c and returns the new capacity, freeing the
|
||||||
|
// previous buffer back into pool.
|
||||||
|
func (b *WString) ResizeTo(c uint32) uint32 {
|
||||||
|
// already sufficient (or n is 0)
|
||||||
|
if c <= b.Cap() {
|
||||||
|
return b.Cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
if c <= MinWStringCap {
|
||||||
|
c = MinWStringCap
|
||||||
|
}
|
||||||
|
// allocate at-least double buffer size, as is done in [bytes.Buffer] and other places
|
||||||
|
if c <= 2*b.Cap() {
|
||||||
|
c = 2 * b.Cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
b2 := make([]uint16, c)
|
||||||
|
if !b.empty() {
|
||||||
|
copy(b2, b.b)
|
||||||
|
freeBuffer(b.b)
|
||||||
|
}
|
||||||
|
b.b = b2
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer returns the underlying []uint16 buffer.
|
||||||
|
func (b *WString) Buffer() []uint16 {
|
||||||
|
if b.empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return b.b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer returns a pointer to the first uint16 in the buffer.
|
||||||
|
// If the [WString.Free] has already been called, the pointer will be nil.
|
||||||
|
func (b *WString) Pointer() *uint16 {
|
||||||
|
if b.empty() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &b.b[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the returns the UTF-8 encoding of the UTF-16 string in the buffer.
|
||||||
|
//
|
||||||
|
// It assumes that the data is null-terminated.
|
||||||
|
func (b *WString) String() string {
|
||||||
|
// Using [windows.UTF16ToString] would require importing "golang.org/x/sys/windows"
|
||||||
|
// and would make this code Windows-only, which makes no sense.
|
||||||
|
// So copy UTF16ToString code into here.
|
||||||
|
// If other windows-specific code is added, switch to [windows.UTF16ToString]
|
||||||
|
|
||||||
|
s := b.b
|
||||||
|
for i, v := range s {
|
||||||
|
if v == 0 {
|
||||||
|
s = s[:i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(utf16.Decode(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap returns the underlying buffer capacity.
|
||||||
|
func (b *WString) Cap() uint32 {
|
||||||
|
if b.empty() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return b.cap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *WString) cap() uint32 { return uint32(cap(b.b)) }
|
||||||
|
func (b *WString) empty() bool { return b == nil || b.cap() == 0 }
|
||||||
586
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
586
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pipe.go
generated
vendored
Normal file
|
|
@ -0,0 +1,586 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
"github.com/Microsoft/go-winio/internal/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) = ConnectNamedPipe
|
||||||
|
//sys createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) [failretval==windows.InvalidHandle] = CreateNamedPipeW
|
||||||
|
//sys disconnectNamedPipe(pipe windows.Handle) (err error) = DisconnectNamedPipe
|
||||||
|
//sys getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) = GetNamedPipeInfo
|
||||||
|
//sys getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) = GetNamedPipeHandleStateW
|
||||||
|
//sys ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) = ntdll.NtCreateNamedPipeFile
|
||||||
|
//sys rtlNtStatusToDosError(status ntStatus) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb
|
||||||
|
//sys rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) = ntdll.RtlDosPathNameToNtPathName_U
|
||||||
|
//sys rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) = ntdll.RtlDefaultNpAcl
|
||||||
|
|
||||||
|
type PipeConn interface {
|
||||||
|
net.Conn
|
||||||
|
Disconnect() error
|
||||||
|
Flush() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// type aliases for mkwinsyscall code
|
||||||
|
type (
|
||||||
|
ntAccessMask = fs.AccessMask
|
||||||
|
ntFileShareMode = fs.FileShareMode
|
||||||
|
ntFileCreationDisposition = fs.NTFileCreationDisposition
|
||||||
|
ntFileOptions = fs.NTCreateOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
type ioStatusBlock struct {
|
||||||
|
Status, Information uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// typedef struct _OBJECT_ATTRIBUTES {
|
||||||
|
// ULONG Length;
|
||||||
|
// HANDLE RootDirectory;
|
||||||
|
// PUNICODE_STRING ObjectName;
|
||||||
|
// ULONG Attributes;
|
||||||
|
// PVOID SecurityDescriptor;
|
||||||
|
// PVOID SecurityQualityOfService;
|
||||||
|
// } OBJECT_ATTRIBUTES;
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes
|
||||||
|
type objectAttributes struct {
|
||||||
|
Length uintptr
|
||||||
|
RootDirectory uintptr
|
||||||
|
ObjectName *unicodeString
|
||||||
|
Attributes uintptr
|
||||||
|
SecurityDescriptor *securityDescriptor
|
||||||
|
SecurityQoS uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
type unicodeString struct {
|
||||||
|
Length uint16
|
||||||
|
MaximumLength uint16
|
||||||
|
Buffer uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// typedef struct _SECURITY_DESCRIPTOR {
|
||||||
|
// BYTE Revision;
|
||||||
|
// BYTE Sbz1;
|
||||||
|
// SECURITY_DESCRIPTOR_CONTROL Control;
|
||||||
|
// PSID Owner;
|
||||||
|
// PSID Group;
|
||||||
|
// PACL Sacl;
|
||||||
|
// PACL Dacl;
|
||||||
|
// } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
|
||||||
|
//
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-security_descriptor
|
||||||
|
type securityDescriptor struct {
|
||||||
|
Revision byte
|
||||||
|
Sbz1 byte
|
||||||
|
Control uint16
|
||||||
|
Owner uintptr
|
||||||
|
Group uintptr
|
||||||
|
Sacl uintptr //revive:disable-line:var-naming SACL, not Sacl
|
||||||
|
Dacl uintptr //revive:disable-line:var-naming DACL, not Dacl
|
||||||
|
}
|
||||||
|
|
||||||
|
type ntStatus int32
|
||||||
|
|
||||||
|
func (status ntStatus) Err() error {
|
||||||
|
if status >= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return rtlNtStatusToDosError(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPipeListenerClosed is returned for pipe operations on listeners that have been closed.
|
||||||
|
ErrPipeListenerClosed = net.ErrClosed
|
||||||
|
|
||||||
|
errPipeWriteClosed = errors.New("pipe has been closed for write")
|
||||||
|
)
|
||||||
|
|
||||||
|
type win32Pipe struct {
|
||||||
|
*win32File
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PipeConn = (*win32Pipe)(nil)
|
||||||
|
|
||||||
|
type win32MessageBytePipe struct {
|
||||||
|
win32Pipe
|
||||||
|
writeClosed bool
|
||||||
|
readEOF bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pipeAddress string
|
||||||
|
|
||||||
|
func (f *win32Pipe) LocalAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) RemoteAddr() net.Addr {
|
||||||
|
return pipeAddress(f.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) SetDeadline(t time.Time) error {
|
||||||
|
if err := f.SetReadDeadline(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return f.SetWriteDeadline(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *win32Pipe) Disconnect() error {
|
||||||
|
return disconnectNamedPipe(f.win32File.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseWrite closes the write side of a message pipe in byte mode.
|
||||||
|
func (f *win32MessageBytePipe) CloseWrite() error {
|
||||||
|
if f.writeClosed {
|
||||||
|
return errPipeWriteClosed
|
||||||
|
}
|
||||||
|
err := f.win32File.Flush()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = f.win32File.Write(nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.writeClosed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes bytes to a message pipe in byte mode. Zero-byte writes are ignored, since
|
||||||
|
// they are used to implement CloseWrite().
|
||||||
|
func (f *win32MessageBytePipe) Write(b []byte) (int, error) {
|
||||||
|
if f.writeClosed {
|
||||||
|
return 0, errPipeWriteClosed
|
||||||
|
}
|
||||||
|
if len(b) == 0 {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return f.win32File.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads bytes from a message pipe in byte mode. A read of a zero-byte message on a message
|
||||||
|
// mode pipe will return io.EOF, as will all subsequent reads.
|
||||||
|
func (f *win32MessageBytePipe) Read(b []byte) (int, error) {
|
||||||
|
if f.readEOF {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
n, err := f.win32File.Read(b)
|
||||||
|
if err == io.EOF { //nolint:errorlint
|
||||||
|
// If this was the result of a zero-byte read, then
|
||||||
|
// it is possible that the read was due to a zero-size
|
||||||
|
// message. Since we are simulating CloseWrite with a
|
||||||
|
// zero-byte message, ensure that all future Read() calls
|
||||||
|
// also return EOF.
|
||||||
|
f.readEOF = true
|
||||||
|
} else if err == windows.ERROR_MORE_DATA { //nolint:errorlint // err is Errno
|
||||||
|
// ERROR_MORE_DATA indicates that the pipe's read mode is message mode
|
||||||
|
// and the message still has more bytes. Treat this as a success, since
|
||||||
|
// this package presents all named pipes as byte streams.
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pipeAddress) Network() string {
|
||||||
|
return "pipe"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s pipeAddress) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryDialPipe attempts to dial the pipe at `path` until `ctx` cancellation or timeout.
|
||||||
|
func tryDialPipe(ctx context.Context, path *string, access fs.AccessMask, impLevel PipeImpLevel) (windows.Handle, error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return windows.Handle(0), ctx.Err()
|
||||||
|
default:
|
||||||
|
h, err := fs.CreateFile(*path,
|
||||||
|
access,
|
||||||
|
0, // mode
|
||||||
|
nil, // security attributes
|
||||||
|
fs.OPEN_EXISTING,
|
||||||
|
fs.FILE_FLAG_OVERLAPPED|fs.SECURITY_SQOS_PRESENT|fs.FileSQSFlag(impLevel),
|
||||||
|
0, // template file handle
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
if err != windows.ERROR_PIPE_BUSY { //nolint:errorlint // err is Errno
|
||||||
|
return h, &os.PathError{Err: err, Op: "open", Path: *path}
|
||||||
|
}
|
||||||
|
// Wait 10 msec and try again. This is a rather simplistic
|
||||||
|
// view, as we always try each 10 milliseconds.
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipe connects to a named pipe by path, timing out if the connection
|
||||||
|
// takes longer than the specified duration. If timeout is nil, then we use
|
||||||
|
// a default timeout of 2 seconds. (We do not use WaitNamedPipe.)
|
||||||
|
func DialPipe(path string, timeout *time.Duration) (net.Conn, error) {
|
||||||
|
var absTimeout time.Time
|
||||||
|
if timeout != nil {
|
||||||
|
absTimeout = time.Now().Add(*timeout)
|
||||||
|
} else {
|
||||||
|
absTimeout = time.Now().Add(2 * time.Second)
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), absTimeout)
|
||||||
|
defer cancel()
|
||||||
|
conn, err := DialPipeContext(ctx, path)
|
||||||
|
if errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return nil, ErrTimeout
|
||||||
|
}
|
||||||
|
return conn, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipeContext attempts to connect to a named pipe by `path` until `ctx`
|
||||||
|
// cancellation or timeout.
|
||||||
|
func DialPipeContext(ctx context.Context, path string) (net.Conn, error) {
|
||||||
|
return DialPipeAccess(ctx, path, uint32(fs.GENERIC_READ|fs.GENERIC_WRITE))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeImpLevel is an enumeration of impersonation levels that may be set
|
||||||
|
// when calling DialPipeAccessImpersonation.
|
||||||
|
type PipeImpLevel uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
PipeImpLevelAnonymous = PipeImpLevel(fs.SECURITY_ANONYMOUS)
|
||||||
|
PipeImpLevelIdentification = PipeImpLevel(fs.SECURITY_IDENTIFICATION)
|
||||||
|
PipeImpLevelImpersonation = PipeImpLevel(fs.SECURITY_IMPERSONATION)
|
||||||
|
PipeImpLevelDelegation = PipeImpLevel(fs.SECURITY_DELEGATION)
|
||||||
|
)
|
||||||
|
|
||||||
|
// DialPipeAccess attempts to connect to a named pipe by `path` with `access` until `ctx`
|
||||||
|
// cancellation or timeout.
|
||||||
|
func DialPipeAccess(ctx context.Context, path string, access uint32) (net.Conn, error) {
|
||||||
|
return DialPipeAccessImpLevel(ctx, path, access, PipeImpLevelAnonymous)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialPipeAccessImpLevel attempts to connect to a named pipe by `path` with
|
||||||
|
// `access` at `impLevel` until `ctx` cancellation or timeout. The other
|
||||||
|
// DialPipe* implementations use PipeImpLevelAnonymous.
|
||||||
|
func DialPipeAccessImpLevel(ctx context.Context, path string, access uint32, impLevel PipeImpLevel) (net.Conn, error) {
|
||||||
|
var err error
|
||||||
|
var h windows.Handle
|
||||||
|
h, err = tryDialPipe(ctx, &path, fs.AccessMask(access), impLevel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags uint32
|
||||||
|
err = getNamedPipeInfo(h, &flags, nil, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pipe is in message mode, return a message byte pipe, which
|
||||||
|
// supports CloseWrite().
|
||||||
|
if flags&windows.PIPE_TYPE_MESSAGE != 0 {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: f, path: path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: f, path: path}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type acceptResponse struct {
|
||||||
|
f *win32File
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type win32PipeListener struct {
|
||||||
|
firstHandle windows.Handle
|
||||||
|
path string
|
||||||
|
config PipeConfig
|
||||||
|
acceptCh chan (chan acceptResponse)
|
||||||
|
closeCh chan int
|
||||||
|
doneCh chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerPipeHandle(path string, sd []byte, c *PipeConfig, first bool) (windows.Handle, error) {
|
||||||
|
path16, err := windows.UTF16FromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var oa objectAttributes
|
||||||
|
oa.Length = unsafe.Sizeof(oa)
|
||||||
|
|
||||||
|
var ntPath unicodeString
|
||||||
|
if err := rtlDosPathNameToNtPathName(&path16[0],
|
||||||
|
&ntPath,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
).Err(); err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(ntPath.Buffer)) //nolint:errcheck
|
||||||
|
oa.ObjectName = &ntPath
|
||||||
|
oa.Attributes = windows.OBJ_CASE_INSENSITIVE
|
||||||
|
|
||||||
|
// The security descriptor is only needed for the first pipe.
|
||||||
|
if first {
|
||||||
|
if sd != nil {
|
||||||
|
//todo: does `sdb` need to be allocated on the heap, or can go allocate it?
|
||||||
|
l := uint32(len(sd))
|
||||||
|
sdb, err := windows.LocalAlloc(0, l)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("LocalAlloc for security descriptor with of length %d: %w", l, err)
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(sdb)) //nolint:errcheck
|
||||||
|
copy((*[0xffff]byte)(unsafe.Pointer(sdb))[:], sd)
|
||||||
|
oa.SecurityDescriptor = (*securityDescriptor)(unsafe.Pointer(sdb))
|
||||||
|
} else {
|
||||||
|
// Construct the default named pipe security descriptor.
|
||||||
|
var dacl uintptr
|
||||||
|
if err := rtlDefaultNpAcl(&dacl).Err(); err != nil {
|
||||||
|
return 0, fmt.Errorf("getting default named pipe ACL: %w", err)
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(dacl)) //nolint:errcheck
|
||||||
|
|
||||||
|
sdb := &securityDescriptor{
|
||||||
|
Revision: 1,
|
||||||
|
Control: windows.SE_DACL_PRESENT,
|
||||||
|
Dacl: dacl,
|
||||||
|
}
|
||||||
|
oa.SecurityDescriptor = sdb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := uint32(windows.FILE_PIPE_REJECT_REMOTE_CLIENTS)
|
||||||
|
if c.MessageMode {
|
||||||
|
typ |= windows.FILE_PIPE_MESSAGE_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
disposition := fs.FILE_OPEN
|
||||||
|
access := fs.GENERIC_READ | fs.GENERIC_WRITE | fs.SYNCHRONIZE
|
||||||
|
if first {
|
||||||
|
disposition = fs.FILE_CREATE
|
||||||
|
// By not asking for read or write access, the named pipe file system
|
||||||
|
// will put this pipe into an initially disconnected state, blocking
|
||||||
|
// client connections until the next call with first == false.
|
||||||
|
access = fs.SYNCHRONIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := int64(-50 * 10000) // 50ms
|
||||||
|
|
||||||
|
var (
|
||||||
|
h windows.Handle
|
||||||
|
iosb ioStatusBlock
|
||||||
|
)
|
||||||
|
err = ntCreateNamedPipeFile(&h,
|
||||||
|
access,
|
||||||
|
&oa,
|
||||||
|
&iosb,
|
||||||
|
fs.FILE_SHARE_READ|fs.FILE_SHARE_WRITE,
|
||||||
|
disposition,
|
||||||
|
0,
|
||||||
|
typ,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0xffffffff,
|
||||||
|
uint32(c.InputBufferSize),
|
||||||
|
uint32(c.OutputBufferSize),
|
||||||
|
&timeout).Err()
|
||||||
|
if err != nil {
|
||||||
|
return 0, &os.PathError{Op: "open", Path: path, Err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.KeepAlive(ntPath)
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeServerPipe() (*win32File, error) {
|
||||||
|
h, err := makeServerPipeHandle(l.path, nil, &l.config, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f, err := makeWin32File(h)
|
||||||
|
if err != nil {
|
||||||
|
windows.Close(h)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) makeConnectedServerPipe() (*win32File, error) {
|
||||||
|
p, err := l.makeServerPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the client to connect.
|
||||||
|
ch := make(chan error)
|
||||||
|
go func(p *win32File) {
|
||||||
|
ch <- connectPipe(p)
|
||||||
|
}(p)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err = <-ch:
|
||||||
|
if err != nil {
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
}
|
||||||
|
case <-l.closeCh:
|
||||||
|
// Abort the connect request by closing the handle.
|
||||||
|
p.Close()
|
||||||
|
p = nil
|
||||||
|
err = <-ch
|
||||||
|
if err == nil || err == ErrFileClosed { //nolint:errorlint // err is Errno
|
||||||
|
err = ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) listenerRoutine() {
|
||||||
|
closed := false
|
||||||
|
for !closed {
|
||||||
|
select {
|
||||||
|
case <-l.closeCh:
|
||||||
|
closed = true
|
||||||
|
case responseCh := <-l.acceptCh:
|
||||||
|
var (
|
||||||
|
p *win32File
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
p, err = l.makeConnectedServerPipe()
|
||||||
|
// If the connection was immediately closed by the client, try
|
||||||
|
// again.
|
||||||
|
if err != windows.ERROR_NO_DATA { //nolint:errorlint // err is Errno
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responseCh <- acceptResponse{p, err}
|
||||||
|
closed = err == ErrPipeListenerClosed //nolint:errorlint // err is Errno
|
||||||
|
}
|
||||||
|
}
|
||||||
|
windows.Close(l.firstHandle)
|
||||||
|
l.firstHandle = 0
|
||||||
|
// Notify Close() and Accept() callers that the handle has been closed.
|
||||||
|
close(l.doneCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PipeConfig contain configuration for the pipe listener.
|
||||||
|
type PipeConfig struct {
|
||||||
|
// SecurityDescriptor contains a Windows security descriptor in SDDL format.
|
||||||
|
SecurityDescriptor string
|
||||||
|
|
||||||
|
// MessageMode determines whether the pipe is in byte or message mode. In either
|
||||||
|
// case the pipe is read in byte mode by default. The only practical difference in
|
||||||
|
// this implementation is that CloseWrite() is only supported for message mode pipes;
|
||||||
|
// CloseWrite() is implemented as a zero-byte write, but zero-byte writes are only
|
||||||
|
// transferred to the reader (and returned as io.EOF in this implementation)
|
||||||
|
// when the pipe is in message mode.
|
||||||
|
MessageMode bool
|
||||||
|
|
||||||
|
// InputBufferSize specifies the size of the input buffer, in bytes.
|
||||||
|
InputBufferSize int32
|
||||||
|
|
||||||
|
// OutputBufferSize specifies the size of the output buffer, in bytes.
|
||||||
|
OutputBufferSize int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListenPipe creates a listener on a Windows named pipe path, e.g. \\.\pipe\mypipe.
|
||||||
|
// The pipe must not already exist.
|
||||||
|
func ListenPipe(path string, c *PipeConfig) (net.Listener, error) {
|
||||||
|
var (
|
||||||
|
sd []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if c == nil {
|
||||||
|
c = &PipeConfig{}
|
||||||
|
}
|
||||||
|
if c.SecurityDescriptor != "" {
|
||||||
|
sd, err = SddlToSecurityDescriptor(c.SecurityDescriptor)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h, err := makeServerPipeHandle(path, sd, c, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
l := &win32PipeListener{
|
||||||
|
firstHandle: h,
|
||||||
|
path: path,
|
||||||
|
config: *c,
|
||||||
|
acceptCh: make(chan (chan acceptResponse)),
|
||||||
|
closeCh: make(chan int),
|
||||||
|
doneCh: make(chan int),
|
||||||
|
}
|
||||||
|
go l.listenerRoutine()
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectPipe(p *win32File) error {
|
||||||
|
c, err := p.prepareIO()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer p.wg.Done()
|
||||||
|
|
||||||
|
err = connectNamedPipe(p.handle, &c.o)
|
||||||
|
_, err = p.asyncIO(c, nil, 0, err)
|
||||||
|
if err != nil && err != windows.ERROR_PIPE_CONNECTED { //nolint:errorlint // err is Errno
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Accept() (net.Conn, error) {
|
||||||
|
ch := make(chan acceptResponse)
|
||||||
|
select {
|
||||||
|
case l.acceptCh <- ch:
|
||||||
|
response := <-ch
|
||||||
|
err := response.err
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if l.config.MessageMode {
|
||||||
|
return &win32MessageBytePipe{
|
||||||
|
win32Pipe: win32Pipe{win32File: response.f, path: l.path},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return &win32Pipe{win32File: response.f, path: l.path}, nil
|
||||||
|
case <-l.doneCh:
|
||||||
|
return nil, ErrPipeListenerClosed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Close() error {
|
||||||
|
select {
|
||||||
|
case l.closeCh <- 1:
|
||||||
|
<-l.doneCh
|
||||||
|
case <-l.doneCh:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *win32PipeListener) Addr() net.Addr {
|
||||||
|
return pipeAddress(l.path)
|
||||||
|
}
|
||||||
232
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
232
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/guid.go
generated
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
||||||
|
// Package guid provides a GUID type. The backing structure for a GUID is
|
||||||
|
// identical to that used by the golang.org/x/sys/windows GUID type.
|
||||||
|
// There are two main binary encodings used for a GUID, the big-endian encoding,
|
||||||
|
// and the Windows (mixed-endian) encoding. See here for details:
|
||||||
|
// https://en.wikipedia.org/wiki/Universally_unique_identifier#Encoding
|
||||||
|
package guid
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha1" //nolint:gosec // not used for secure application
|
||||||
|
"encoding"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate go run golang.org/x/tools/cmd/stringer -type=Variant -trimprefix=Variant -linecomment
|
||||||
|
|
||||||
|
// Variant specifies which GUID variant (or "type") of the GUID. It determines
|
||||||
|
// how the entirety of the rest of the GUID is interpreted.
|
||||||
|
type Variant uint8
|
||||||
|
|
||||||
|
// The variants specified by RFC 4122 section 4.1.1.
|
||||||
|
const (
|
||||||
|
// VariantUnknown specifies a GUID variant which does not conform to one of
|
||||||
|
// the variant encodings specified in RFC 4122.
|
||||||
|
VariantUnknown Variant = iota
|
||||||
|
VariantNCS
|
||||||
|
VariantRFC4122 // RFC 4122
|
||||||
|
VariantMicrosoft
|
||||||
|
VariantFuture
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version specifies how the bits in the GUID were generated. For instance, a
|
||||||
|
// version 4 GUID is randomly generated, and a version 5 is generated from the
|
||||||
|
// hash of an input string.
|
||||||
|
type Version uint8
|
||||||
|
|
||||||
|
func (v Version) String() string {
|
||||||
|
return strconv.FormatUint(uint64(v), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = (encoding.TextMarshaler)(GUID{})
|
||||||
|
var _ = (encoding.TextUnmarshaler)(&GUID{})
|
||||||
|
|
||||||
|
// NewV4 returns a new version 4 (pseudorandom) GUID, as defined by RFC 4122.
|
||||||
|
func NewV4() (GUID, error) {
|
||||||
|
var b [16]byte
|
||||||
|
if _, err := rand.Read(b[:]); err != nil {
|
||||||
|
return GUID{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g := FromArray(b)
|
||||||
|
g.setVersion(4) // Version 4 means randomly generated.
|
||||||
|
g.setVariant(VariantRFC4122)
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewV5 returns a new version 5 (generated from a string via SHA-1 hashing)
|
||||||
|
// GUID, as defined by RFC 4122. The RFC is unclear on the encoding of the name,
|
||||||
|
// and the sample code treats it as a series of bytes, so we do the same here.
|
||||||
|
//
|
||||||
|
// Some implementations, such as those found on Windows, treat the name as a
|
||||||
|
// big-endian UTF16 stream of bytes. If that is desired, the string can be
|
||||||
|
// encoded as such before being passed to this function.
|
||||||
|
func NewV5(namespace GUID, name []byte) (GUID, error) {
|
||||||
|
b := sha1.New() //nolint:gosec // not used for secure application
|
||||||
|
namespaceBytes := namespace.ToArray()
|
||||||
|
b.Write(namespaceBytes[:])
|
||||||
|
b.Write(name)
|
||||||
|
|
||||||
|
a := [16]byte{}
|
||||||
|
copy(a[:], b.Sum(nil))
|
||||||
|
|
||||||
|
g := FromArray(a)
|
||||||
|
g.setVersion(5) // Version 5 means generated from a string.
|
||||||
|
g.setVariant(VariantRFC4122)
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fromArray(b [16]byte, order binary.ByteOrder) GUID {
|
||||||
|
var g GUID
|
||||||
|
g.Data1 = order.Uint32(b[0:4])
|
||||||
|
g.Data2 = order.Uint16(b[4:6])
|
||||||
|
g.Data3 = order.Uint16(b[6:8])
|
||||||
|
copy(g.Data4[:], b[8:16])
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GUID) toArray(order binary.ByteOrder) [16]byte {
|
||||||
|
b := [16]byte{}
|
||||||
|
order.PutUint32(b[0:4], g.Data1)
|
||||||
|
order.PutUint16(b[4:6], g.Data2)
|
||||||
|
order.PutUint16(b[6:8], g.Data3)
|
||||||
|
copy(b[8:16], g.Data4[:])
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromArray constructs a GUID from a big-endian encoding array of 16 bytes.
|
||||||
|
func FromArray(b [16]byte) GUID {
|
||||||
|
return fromArray(b, binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToArray returns an array of 16 bytes representing the GUID in big-endian
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToArray() [16]byte {
|
||||||
|
return g.toArray(binary.BigEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromWindowsArray constructs a GUID from a Windows encoding array of bytes.
|
||||||
|
func FromWindowsArray(b [16]byte) GUID {
|
||||||
|
return fromArray(b, binary.LittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToWindowsArray returns an array of 16 bytes representing the GUID in Windows
|
||||||
|
// encoding.
|
||||||
|
func (g GUID) ToWindowsArray() [16]byte {
|
||||||
|
return g.toArray(binary.LittleEndian)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g GUID) String() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%08x-%04x-%04x-%04x-%012x",
|
||||||
|
g.Data1,
|
||||||
|
g.Data2,
|
||||||
|
g.Data3,
|
||||||
|
g.Data4[:2],
|
||||||
|
g.Data4[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromString parses a string containing a GUID and returns the GUID. The only
|
||||||
|
// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||||
|
// format.
|
||||||
|
func FromString(s string) (GUID, error) {
|
||||||
|
if len(s) != 36 {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
var g GUID
|
||||||
|
|
||||||
|
data1, err := strconv.ParseUint(s[0:8], 16, 32)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data1 = uint32(data1)
|
||||||
|
|
||||||
|
data2, err := strconv.ParseUint(s[9:13], 16, 16)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data2 = uint16(data2)
|
||||||
|
|
||||||
|
data3, err := strconv.ParseUint(s[14:18], 16, 16)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data3 = uint16(data3)
|
||||||
|
|
||||||
|
for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} {
|
||||||
|
v, err := strconv.ParseUint(s[x:x+2], 16, 8)
|
||||||
|
if err != nil {
|
||||||
|
return GUID{}, fmt.Errorf("invalid GUID %q", s)
|
||||||
|
}
|
||||||
|
g.Data4[i] = uint8(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GUID) setVariant(v Variant) {
|
||||||
|
d := g.Data4[0]
|
||||||
|
switch v {
|
||||||
|
case VariantNCS:
|
||||||
|
d = (d & 0x7f)
|
||||||
|
case VariantRFC4122:
|
||||||
|
d = (d & 0x3f) | 0x80
|
||||||
|
case VariantMicrosoft:
|
||||||
|
d = (d & 0x1f) | 0xc0
|
||||||
|
case VariantFuture:
|
||||||
|
d = (d & 0x0f) | 0xe0
|
||||||
|
case VariantUnknown:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("invalid variant: %d", v))
|
||||||
|
}
|
||||||
|
g.Data4[0] = d
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant returns the GUID variant, as defined in RFC 4122.
|
||||||
|
func (g GUID) Variant() Variant {
|
||||||
|
b := g.Data4[0]
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
return VariantNCS
|
||||||
|
} else if b&0xc0 == 0x80 {
|
||||||
|
return VariantRFC4122
|
||||||
|
} else if b&0xe0 == 0xc0 {
|
||||||
|
return VariantMicrosoft
|
||||||
|
} else if b&0xe0 == 0xe0 {
|
||||||
|
return VariantFuture
|
||||||
|
}
|
||||||
|
return VariantUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GUID) setVersion(v Version) {
|
||||||
|
g.Data3 = (g.Data3 & 0x0fff) | (uint16(v) << 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the GUID version, as defined in RFC 4122.
|
||||||
|
func (g GUID) Version() Version {
|
||||||
|
return Version((g.Data3 & 0xF000) >> 12)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalText returns the textual representation of the GUID.
|
||||||
|
func (g GUID) MarshalText() ([]byte, error) {
|
||||||
|
return []byte(g.String()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText takes the textual representation of a GUID, and unmarhals it
|
||||||
|
// into this GUID.
|
||||||
|
func (g *GUID) UnmarshalText(text []byte) error {
|
||||||
|
g2, err := FromString(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*g = g2
|
||||||
|
return nil
|
||||||
|
}
|
||||||
16
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go
generated
vendored
Normal file
16
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_nonwindows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package guid
|
||||||
|
|
||||||
|
// GUID represents a GUID/UUID. It has the same structure as
|
||||||
|
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||||
|
// that type. It is defined as its own type as that is only available to builds
|
||||||
|
// targeted at `windows`. The representation matches that used by native Windows
|
||||||
|
// code.
|
||||||
|
type GUID struct {
|
||||||
|
Data1 uint32
|
||||||
|
Data2 uint16
|
||||||
|
Data3 uint16
|
||||||
|
Data4 [8]byte
|
||||||
|
}
|
||||||
13
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go
generated
vendored
Normal file
13
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/guid_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package guid
|
||||||
|
|
||||||
|
import "golang.org/x/sys/windows"
|
||||||
|
|
||||||
|
// GUID represents a GUID/UUID. It has the same structure as
|
||||||
|
// golang.org/x/sys/windows.GUID so that it can be used with functions expecting
|
||||||
|
// that type. It is defined as its own type so that stringification and
|
||||||
|
// marshaling can be supported. The representation matches that used by native
|
||||||
|
// Windows code.
|
||||||
|
type GUID windows.GUID
|
||||||
27
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.go
generated
vendored
Normal file
27
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/pkg/guid/variant_string.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Code generated by "stringer -type=Variant -trimprefix=Variant -linecomment"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package guid
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[VariantUnknown-0]
|
||||||
|
_ = x[VariantNCS-1]
|
||||||
|
_ = x[VariantRFC4122-2]
|
||||||
|
_ = x[VariantMicrosoft-3]
|
||||||
|
_ = x[VariantFuture-4]
|
||||||
|
}
|
||||||
|
|
||||||
|
const _Variant_name = "UnknownNCSRFC 4122MicrosoftFuture"
|
||||||
|
|
||||||
|
var _Variant_index = [...]uint8{0, 7, 10, 18, 27, 33}
|
||||||
|
|
||||||
|
func (i Variant) String() string {
|
||||||
|
if i >= Variant(len(_Variant_index)-1) {
|
||||||
|
return "Variant(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
return _Variant_name[_Variant_index[i]:_Variant_index[i+1]]
|
||||||
|
}
|
||||||
196
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
196
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/privilege.go
generated
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
"unicode/utf16"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) [true] = advapi32.AdjustTokenPrivileges
|
||||||
|
//sys impersonateSelf(level uint32) (err error) = advapi32.ImpersonateSelf
|
||||||
|
//sys revertToSelf() (err error) = advapi32.RevertToSelf
|
||||||
|
//sys openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) = advapi32.OpenThreadToken
|
||||||
|
//sys getCurrentThread() (h windows.Handle) = GetCurrentThread
|
||||||
|
//sys lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) = advapi32.LookupPrivilegeValueW
|
||||||
|
//sys lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) = advapi32.LookupPrivilegeNameW
|
||||||
|
//sys lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) = advapi32.LookupPrivilegeDisplayNameW
|
||||||
|
|
||||||
|
const (
|
||||||
|
//revive:disable-next-line:var-naming ALL_CAPS
|
||||||
|
SE_PRIVILEGE_ENABLED = windows.SE_PRIVILEGE_ENABLED
|
||||||
|
|
||||||
|
//revive:disable-next-line:var-naming ALL_CAPS
|
||||||
|
ERROR_NOT_ALL_ASSIGNED windows.Errno = windows.ERROR_NOT_ALL_ASSIGNED
|
||||||
|
|
||||||
|
SeBackupPrivilege = "SeBackupPrivilege"
|
||||||
|
SeRestorePrivilege = "SeRestorePrivilege"
|
||||||
|
SeSecurityPrivilege = "SeSecurityPrivilege"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
privNames = make(map[string]uint64)
|
||||||
|
privNameMutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivilegeError represents an error enabling privileges.
|
||||||
|
type PrivilegeError struct {
|
||||||
|
privileges []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PrivilegeError) Error() string {
|
||||||
|
s := "Could not enable privilege "
|
||||||
|
if len(e.privileges) > 1 {
|
||||||
|
s = "Could not enable privileges "
|
||||||
|
}
|
||||||
|
for i, p := range e.privileges {
|
||||||
|
if i != 0 {
|
||||||
|
s += ", "
|
||||||
|
}
|
||||||
|
s += `"`
|
||||||
|
s += getPrivilegeName(p)
|
||||||
|
s += `"`
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivilege enables a single privilege for a function call.
|
||||||
|
func RunWithPrivilege(name string, fn func() error) error {
|
||||||
|
return RunWithPrivileges([]string{name}, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunWithPrivileges enables privileges for a function call.
|
||||||
|
func RunWithPrivileges(names []string, fn func() error) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
token, err := newThreadToken()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer releaseThreadToken(token)
|
||||||
|
err = adjustPrivileges(token, privileges, SE_PRIVILEGE_ENABLED)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapPrivileges(names []string) ([]uint64, error) {
|
||||||
|
privileges := make([]uint64, 0, len(names))
|
||||||
|
privNameMutex.Lock()
|
||||||
|
defer privNameMutex.Unlock()
|
||||||
|
for _, name := range names {
|
||||||
|
p, ok := privNames[name]
|
||||||
|
if !ok {
|
||||||
|
err := lookupPrivilegeValue("", name, &p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privNames[name] = p
|
||||||
|
}
|
||||||
|
privileges = append(privileges, p)
|
||||||
|
}
|
||||||
|
return privileges, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableProcessPrivileges enables privileges globally for the process.
|
||||||
|
func EnableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, SE_PRIVILEGE_ENABLED)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableProcessPrivileges disables privileges globally for the process.
|
||||||
|
func DisableProcessPrivileges(names []string) error {
|
||||||
|
return enableDisableProcessPrivilege(names, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func enableDisableProcessPrivilege(names []string, action uint32) error {
|
||||||
|
privileges, err := mapPrivileges(names)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := windows.CurrentProcess()
|
||||||
|
var token windows.Token
|
||||||
|
err = windows.OpenProcessToken(p, windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, &token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer token.Close()
|
||||||
|
return adjustPrivileges(token, privileges, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustPrivileges(token windows.Token, privileges []uint64, action uint32) error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, uint32(len(privileges)))
|
||||||
|
for _, p := range privileges {
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, p)
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, action)
|
||||||
|
}
|
||||||
|
prevState := make([]byte, b.Len())
|
||||||
|
reqSize := uint32(0)
|
||||||
|
success, err := adjustTokenPrivileges(token, false, &b.Bytes()[0], uint32(len(prevState)), &prevState[0], &reqSize)
|
||||||
|
if !success {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err == ERROR_NOT_ALL_ASSIGNED { //nolint:errorlint // err is Errno
|
||||||
|
return &PrivilegeError{privileges}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPrivilegeName(luid uint64) string {
|
||||||
|
var nameBuffer [256]uint16
|
||||||
|
bufSize := uint32(len(nameBuffer))
|
||||||
|
err := lookupPrivilegeName("", &luid, &nameBuffer[0], &bufSize)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %d>", luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
var displayNameBuffer [256]uint16
|
||||||
|
displayBufSize := uint32(len(displayNameBuffer))
|
||||||
|
var langID uint32
|
||||||
|
err = lookupPrivilegeDisplayName("", &nameBuffer[0], &displayNameBuffer[0], &displayBufSize, &langID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("<unknown privilege %s>", string(utf16.Decode(nameBuffer[:bufSize])))
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(utf16.Decode(displayNameBuffer[:displayBufSize]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func newThreadToken() (windows.Token, error) {
|
||||||
|
err := impersonateSelf(windows.SecurityImpersonation)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var token windows.Token
|
||||||
|
err = openThreadToken(getCurrentThread(), windows.TOKEN_ADJUST_PRIVILEGES|windows.TOKEN_QUERY, false, &token)
|
||||||
|
if err != nil {
|
||||||
|
rerr := revertToSelf()
|
||||||
|
if rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseThreadToken(h windows.Token) {
|
||||||
|
err := revertToSelf()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
h.Close()
|
||||||
|
}
|
||||||
131
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
131
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/reparse.go
generated
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf16"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
reparseTagMountPoint = 0xA0000003
|
||||||
|
reparseTagSymlink = 0xA000000C
|
||||||
|
)
|
||||||
|
|
||||||
|
type reparseDataBuffer struct {
|
||||||
|
ReparseTag uint32
|
||||||
|
ReparseDataLength uint16
|
||||||
|
Reserved uint16
|
||||||
|
SubstituteNameOffset uint16
|
||||||
|
SubstituteNameLength uint16
|
||||||
|
PrintNameOffset uint16
|
||||||
|
PrintNameLength uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReparsePoint describes a Win32 symlink or mount point.
|
||||||
|
type ReparsePoint struct {
|
||||||
|
Target string
|
||||||
|
IsMountPoint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsupportedReparsePointError is returned when trying to decode a non-symlink or
|
||||||
|
// mount point reparse point.
|
||||||
|
type UnsupportedReparsePointError struct {
|
||||||
|
Tag uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnsupportedReparsePointError) Error() string {
|
||||||
|
return fmt.Sprintf("unsupported reparse point %x", e.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeReparsePoint decodes a Win32 REPARSE_DATA_BUFFER structure containing either a symlink
|
||||||
|
// or a mount point.
|
||||||
|
func DecodeReparsePoint(b []byte) (*ReparsePoint, error) {
|
||||||
|
tag := binary.LittleEndian.Uint32(b[0:4])
|
||||||
|
return DecodeReparsePointData(tag, b[8:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeReparsePointData(tag uint32, b []byte) (*ReparsePoint, error) {
|
||||||
|
isMountPoint := false
|
||||||
|
switch tag {
|
||||||
|
case reparseTagMountPoint:
|
||||||
|
isMountPoint = true
|
||||||
|
case reparseTagSymlink:
|
||||||
|
default:
|
||||||
|
return nil, &UnsupportedReparsePointError{tag}
|
||||||
|
}
|
||||||
|
nameOffset := 8 + binary.LittleEndian.Uint16(b[4:6])
|
||||||
|
if !isMountPoint {
|
||||||
|
nameOffset += 4
|
||||||
|
}
|
||||||
|
nameLength := binary.LittleEndian.Uint16(b[6:8])
|
||||||
|
name := make([]uint16, nameLength/2)
|
||||||
|
err := binary.Read(bytes.NewReader(b[nameOffset:nameOffset+nameLength]), binary.LittleEndian, &name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ReparsePoint{string(utf16.Decode(name)), isMountPoint}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDriveLetter(c byte) bool {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeReparsePoint encodes a Win32 REPARSE_DATA_BUFFER structure describing a symlink or
|
||||||
|
// mount point.
|
||||||
|
func EncodeReparsePoint(rp *ReparsePoint) []byte {
|
||||||
|
// Generate an NT path and determine if this is a relative path.
|
||||||
|
var ntTarget string
|
||||||
|
relative := false
|
||||||
|
if strings.HasPrefix(rp.Target, `\\?\`) {
|
||||||
|
ntTarget = `\??\` + rp.Target[4:]
|
||||||
|
} else if strings.HasPrefix(rp.Target, `\\`) {
|
||||||
|
ntTarget = `\??\UNC\` + rp.Target[2:]
|
||||||
|
} else if len(rp.Target) >= 2 && isDriveLetter(rp.Target[0]) && rp.Target[1] == ':' {
|
||||||
|
ntTarget = `\??\` + rp.Target
|
||||||
|
} else {
|
||||||
|
ntTarget = rp.Target
|
||||||
|
relative = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The paths must be NUL-terminated even though they are counted strings.
|
||||||
|
target16 := utf16.Encode([]rune(rp.Target + "\x00"))
|
||||||
|
ntTarget16 := utf16.Encode([]rune(ntTarget + "\x00"))
|
||||||
|
|
||||||
|
size := int(unsafe.Sizeof(reparseDataBuffer{})) - 8
|
||||||
|
size += len(ntTarget16)*2 + len(target16)*2
|
||||||
|
|
||||||
|
tag := uint32(reparseTagMountPoint)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
tag = reparseTagSymlink
|
||||||
|
size += 4 // Add room for symlink flags
|
||||||
|
}
|
||||||
|
|
||||||
|
data := reparseDataBuffer{
|
||||||
|
ReparseTag: tag,
|
||||||
|
ReparseDataLength: uint16(size),
|
||||||
|
SubstituteNameOffset: 0,
|
||||||
|
SubstituteNameLength: uint16((len(ntTarget16) - 1) * 2),
|
||||||
|
PrintNameOffset: uint16(len(ntTarget16) * 2),
|
||||||
|
PrintNameLength: uint16((len(target16) - 1) * 2),
|
||||||
|
}
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, &data)
|
||||||
|
if !rp.IsMountPoint {
|
||||||
|
flags := uint32(0)
|
||||||
|
if relative {
|
||||||
|
flags |= 1
|
||||||
|
}
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, ntTarget16)
|
||||||
|
_ = binary.Write(&b, binary.LittleEndian, target16)
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
133
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
133
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/sd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
//go:build windows
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
//sys lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountNameW
|
||||||
|
//sys lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) = advapi32.LookupAccountSidW
|
||||||
|
//sys convertSidToStringSid(sid *byte, str **uint16) (err error) = advapi32.ConvertSidToStringSidW
|
||||||
|
//sys convertStringSidToSid(str *uint16, sid **byte) (err error) = advapi32.ConvertStringSidToSidW
|
||||||
|
|
||||||
|
type AccountLookupError struct {
|
||||||
|
Name string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Error() string {
|
||||||
|
if e.Name == "" {
|
||||||
|
return "lookup account: empty account name specified"
|
||||||
|
}
|
||||||
|
var s string
|
||||||
|
switch {
|
||||||
|
case errors.Is(e.Err, windows.ERROR_INVALID_SID):
|
||||||
|
s = "the security ID structure is invalid"
|
||||||
|
case errors.Is(e.Err, windows.ERROR_NONE_MAPPED):
|
||||||
|
s = "not found"
|
||||||
|
default:
|
||||||
|
s = e.Err.Error()
|
||||||
|
}
|
||||||
|
return "lookup account " + e.Name + ": " + s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *AccountLookupError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
type SddlConversionError struct {
|
||||||
|
Sddl string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Error() string {
|
||||||
|
return "convert " + e.Sddl + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SddlConversionError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
|
// LookupSidByName looks up the SID of an account by name
|
||||||
|
//
|
||||||
|
//revive:disable-next-line:var-naming SID, not Sid
|
||||||
|
func LookupSidByName(name string) (sid string, err error) {
|
||||||
|
if name == "" {
|
||||||
|
return "", &AccountLookupError{name, windows.ERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidSize, sidNameUse, refDomainSize uint32
|
||||||
|
err = lookupAccountName(nil, name, nil, &sidSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sidBuffer := make([]byte, sidSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountName(nil, name, &sidBuffer[0], &sidSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
var strBuffer *uint16
|
||||||
|
err = convertSidToStringSid(&sidBuffer[0], &strBuffer)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{name, err}
|
||||||
|
}
|
||||||
|
sid = windows.UTF16ToString((*[0xffff]uint16)(unsafe.Pointer(strBuffer))[:])
|
||||||
|
_, _ = windows.LocalFree(windows.Handle(unsafe.Pointer(strBuffer)))
|
||||||
|
return sid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupNameBySid looks up the name of an account by SID
|
||||||
|
//
|
||||||
|
//revive:disable-next-line:var-naming SID, not Sid
|
||||||
|
func LookupNameBySid(sid string) (name string, err error) {
|
||||||
|
if sid == "" {
|
||||||
|
return "", &AccountLookupError{sid, windows.ERROR_NONE_MAPPED}
|
||||||
|
}
|
||||||
|
|
||||||
|
sidBuffer, err := windows.UTF16PtrFromString(sid)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sidPtr *byte
|
||||||
|
if err = convertStringSidToSid(sidBuffer, &sidPtr); err != nil {
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
defer windows.LocalFree(windows.Handle(unsafe.Pointer(sidPtr))) //nolint:errcheck
|
||||||
|
|
||||||
|
var nameSize, refDomainSize, sidNameUse uint32
|
||||||
|
err = lookupAccountSid(nil, sidPtr, nil, &nameSize, nil, &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil && err != windows.ERROR_INSUFFICIENT_BUFFER { //nolint:errorlint // err is Errno
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
nameBuffer := make([]uint16, nameSize)
|
||||||
|
refDomainBuffer := make([]uint16, refDomainSize)
|
||||||
|
err = lookupAccountSid(nil, sidPtr, &nameBuffer[0], &nameSize, &refDomainBuffer[0], &refDomainSize, &sidNameUse)
|
||||||
|
if err != nil {
|
||||||
|
return "", &AccountLookupError{sid, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
name = windows.UTF16ToString(nameBuffer)
|
||||||
|
return name, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SddlToSecurityDescriptor(sddl string) ([]byte, error) {
|
||||||
|
sd, err := windows.SecurityDescriptorFromString(sddl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, &SddlConversionError{Sddl: sddl, Err: err}
|
||||||
|
}
|
||||||
|
b := unsafe.Slice((*byte)(unsafe.Pointer(sd)), sd.Length())
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SecurityDescriptorToSddl(sd []byte) (string, error) {
|
||||||
|
if l := int(unsafe.Sizeof(windows.SECURITY_DESCRIPTOR{})); len(sd) < l {
|
||||||
|
return "", fmt.Errorf("SecurityDescriptor (%d) smaller than expected (%d): %w", len(sd), l, windows.ERROR_INCORRECT_SIZE)
|
||||||
|
}
|
||||||
|
s := (*windows.SECURITY_DESCRIPTOR)(unsafe.Pointer(&sd[0]))
|
||||||
|
return s.String(), nil
|
||||||
|
}
|
||||||
5
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
5
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/syscall.go
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zsyscall_windows.go ./*.go
|
||||||
378
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
378
.gear/predownloaded-development/vendor/github.com/Microsoft/go-winio/zsyscall_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,378 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package winio
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
|
// Do the interface allocations only once for common
|
||||||
|
// Errno values.
|
||||||
|
const (
|
||||||
|
errnoERROR_IO_PENDING = 997
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
|
||||||
|
errERROR_EINVAL error = syscall.EINVAL
|
||||||
|
)
|
||||||
|
|
||||||
|
// errnoErr returns common boxed Errno values, to prevent
|
||||||
|
// allocations at runtime.
|
||||||
|
func errnoErr(e syscall.Errno) error {
|
||||||
|
switch e {
|
||||||
|
case 0:
|
||||||
|
return errERROR_EINVAL
|
||||||
|
case errnoERROR_IO_PENDING:
|
||||||
|
return errERROR_IO_PENDING
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
|
||||||
|
modkernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
modntdll = windows.NewLazySystemDLL("ntdll.dll")
|
||||||
|
modws2_32 = windows.NewLazySystemDLL("ws2_32.dll")
|
||||||
|
|
||||||
|
procAdjustTokenPrivileges = modadvapi32.NewProc("AdjustTokenPrivileges")
|
||||||
|
procConvertSidToStringSidW = modadvapi32.NewProc("ConvertSidToStringSidW")
|
||||||
|
procConvertStringSidToSidW = modadvapi32.NewProc("ConvertStringSidToSidW")
|
||||||
|
procImpersonateSelf = modadvapi32.NewProc("ImpersonateSelf")
|
||||||
|
procLookupAccountNameW = modadvapi32.NewProc("LookupAccountNameW")
|
||||||
|
procLookupAccountSidW = modadvapi32.NewProc("LookupAccountSidW")
|
||||||
|
procLookupPrivilegeDisplayNameW = modadvapi32.NewProc("LookupPrivilegeDisplayNameW")
|
||||||
|
procLookupPrivilegeNameW = modadvapi32.NewProc("LookupPrivilegeNameW")
|
||||||
|
procLookupPrivilegeValueW = modadvapi32.NewProc("LookupPrivilegeValueW")
|
||||||
|
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
|
||||||
|
procRevertToSelf = modadvapi32.NewProc("RevertToSelf")
|
||||||
|
procBackupRead = modkernel32.NewProc("BackupRead")
|
||||||
|
procBackupWrite = modkernel32.NewProc("BackupWrite")
|
||||||
|
procCancelIoEx = modkernel32.NewProc("CancelIoEx")
|
||||||
|
procConnectNamedPipe = modkernel32.NewProc("ConnectNamedPipe")
|
||||||
|
procCreateIoCompletionPort = modkernel32.NewProc("CreateIoCompletionPort")
|
||||||
|
procCreateNamedPipeW = modkernel32.NewProc("CreateNamedPipeW")
|
||||||
|
procDisconnectNamedPipe = modkernel32.NewProc("DisconnectNamedPipe")
|
||||||
|
procGetCurrentThread = modkernel32.NewProc("GetCurrentThread")
|
||||||
|
procGetNamedPipeHandleStateW = modkernel32.NewProc("GetNamedPipeHandleStateW")
|
||||||
|
procGetNamedPipeInfo = modkernel32.NewProc("GetNamedPipeInfo")
|
||||||
|
procGetQueuedCompletionStatus = modkernel32.NewProc("GetQueuedCompletionStatus")
|
||||||
|
procSetFileCompletionNotificationModes = modkernel32.NewProc("SetFileCompletionNotificationModes")
|
||||||
|
procNtCreateNamedPipeFile = modntdll.NewProc("NtCreateNamedPipeFile")
|
||||||
|
procRtlDefaultNpAcl = modntdll.NewProc("RtlDefaultNpAcl")
|
||||||
|
procRtlDosPathNameToNtPathName_U = modntdll.NewProc("RtlDosPathNameToNtPathName_U")
|
||||||
|
procRtlNtStatusToDosErrorNoTeb = modntdll.NewProc("RtlNtStatusToDosErrorNoTeb")
|
||||||
|
procWSAGetOverlappedResult = modws2_32.NewProc("WSAGetOverlappedResult")
|
||||||
|
)
|
||||||
|
|
||||||
|
func adjustTokenPrivileges(token windows.Token, releaseAll bool, input *byte, outputSize uint32, output *byte, requiredSize *uint32) (success bool, err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if releaseAll {
|
||||||
|
_p0 = 1
|
||||||
|
}
|
||||||
|
r0, _, e1 := syscall.SyscallN(procAdjustTokenPrivileges.Addr(), uintptr(token), uintptr(_p0), uintptr(unsafe.Pointer(input)), uintptr(outputSize), uintptr(unsafe.Pointer(output)), uintptr(unsafe.Pointer(requiredSize)))
|
||||||
|
success = r0 != 0
|
||||||
|
if true {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertSidToStringSid(sid *byte, str **uint16) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procConvertSidToStringSidW.Addr(), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(str)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertStringSidToSid(str *uint16, sid **byte) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procConvertStringSidToSidW.Addr(), uintptr(unsafe.Pointer(str)), uintptr(unsafe.Pointer(sid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func impersonateSelf(level uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procImpersonateSelf.Addr(), uintptr(level))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountName(systemName *uint16, accountName string, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(accountName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupAccountName(systemName, _p0, sid, sidSize, refDomain, refDomainSize, sidNameUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupAccountName(systemName *uint16, accountName *uint16, sid *byte, sidSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupAccountNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(accountName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(sidSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupAccountSid(systemName *uint16, sid *byte, name *uint16, nameSize *uint32, refDomain *uint16, refDomainSize *uint32, sidNameUse *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupAccountSidW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameSize)), uintptr(unsafe.Pointer(refDomain)), uintptr(unsafe.Pointer(refDomainSize)), uintptr(unsafe.Pointer(sidNameUse)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeDisplayName(systemName string, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeDisplayName(_p0, name, buffer, size, languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeDisplayName(systemName *uint16, name *uint16, buffer *uint16, size *uint32, languageId *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeDisplayNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)), uintptr(unsafe.Pointer(languageId)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeName(systemName string, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeName(_p0, luid, buffer, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeName(systemName *uint16, luid *uint64, buffer *uint16, size *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeNameW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(luid)), uintptr(unsafe.Pointer(buffer)), uintptr(unsafe.Pointer(size)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func lookupPrivilegeValue(systemName string, name string, luid *uint64) (err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(systemName)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var _p1 *uint16
|
||||||
|
_p1, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _lookupPrivilegeValue(_p0, _p1, luid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _lookupPrivilegeValue(systemName *uint16, name *uint16, luid *uint64) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procLookupPrivilegeValueW.Addr(), uintptr(unsafe.Pointer(systemName)), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(luid)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func openThreadToken(thread windows.Handle, accessMask uint32, openAsSelf bool, token *windows.Token) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if openAsSelf {
|
||||||
|
_p0 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procOpenThreadToken.Addr(), uintptr(thread), uintptr(accessMask), uintptr(_p0), uintptr(unsafe.Pointer(token)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func revertToSelf() (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procRevertToSelf.Addr())
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRead(h windows.Handle, b []byte, bytesRead *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procBackupRead.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesRead)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupWrite(h windows.Handle, b []byte, bytesWritten *uint32, abort bool, processSecurity bool, context *uintptr) (err error) {
|
||||||
|
var _p0 *byte
|
||||||
|
if len(b) > 0 {
|
||||||
|
_p0 = &b[0]
|
||||||
|
}
|
||||||
|
var _p1 uint32
|
||||||
|
if abort {
|
||||||
|
_p1 = 1
|
||||||
|
}
|
||||||
|
var _p2 uint32
|
||||||
|
if processSecurity {
|
||||||
|
_p2 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procBackupWrite.Addr(), uintptr(h), uintptr(unsafe.Pointer(_p0)), uintptr(len(b)), uintptr(unsafe.Pointer(bytesWritten)), uintptr(_p1), uintptr(_p2), uintptr(unsafe.Pointer(context)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancelIoEx(file windows.Handle, o *windows.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procCancelIoEx.Addr(), uintptr(file), uintptr(unsafe.Pointer(o)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectNamedPipe(pipe windows.Handle, o *windows.Overlapped) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procConnectNamedPipe.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(o)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIoCompletionPort(file windows.Handle, port windows.Handle, key uintptr, threadCount uint32) (newport windows.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.SyscallN(procCreateIoCompletionPort.Addr(), uintptr(file), uintptr(port), uintptr(key), uintptr(threadCount))
|
||||||
|
newport = windows.Handle(r0)
|
||||||
|
if newport == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNamedPipe(name string, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) {
|
||||||
|
var _p0 *uint16
|
||||||
|
_p0, err = syscall.UTF16PtrFromString(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return _createNamedPipe(_p0, flags, pipeMode, maxInstances, outSize, inSize, defaultTimeout, sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createNamedPipe(name *uint16, flags uint32, pipeMode uint32, maxInstances uint32, outSize uint32, inSize uint32, defaultTimeout uint32, sa *windows.SecurityAttributes) (handle windows.Handle, err error) {
|
||||||
|
r0, _, e1 := syscall.SyscallN(procCreateNamedPipeW.Addr(), uintptr(unsafe.Pointer(name)), uintptr(flags), uintptr(pipeMode), uintptr(maxInstances), uintptr(outSize), uintptr(inSize), uintptr(defaultTimeout), uintptr(unsafe.Pointer(sa)))
|
||||||
|
handle = windows.Handle(r0)
|
||||||
|
if handle == windows.InvalidHandle {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func disconnectNamedPipe(pipe windows.Handle) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procDisconnectNamedPipe.Addr(), uintptr(pipe))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCurrentThread() (h windows.Handle) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procGetCurrentThread.Addr())
|
||||||
|
h = windows.Handle(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeHandleState(pipe windows.Handle, state *uint32, curInstances *uint32, maxCollectionCount *uint32, collectDataTimeout *uint32, userName *uint16, maxUserNameSize uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procGetNamedPipeHandleStateW.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(state)), uintptr(unsafe.Pointer(curInstances)), uintptr(unsafe.Pointer(maxCollectionCount)), uintptr(unsafe.Pointer(collectDataTimeout)), uintptr(unsafe.Pointer(userName)), uintptr(maxUserNameSize))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNamedPipeInfo(pipe windows.Handle, flags *uint32, outSize *uint32, inSize *uint32, maxInstances *uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procGetNamedPipeInfo.Addr(), uintptr(pipe), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(outSize)), uintptr(unsafe.Pointer(inSize)), uintptr(unsafe.Pointer(maxInstances)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func getQueuedCompletionStatus(port windows.Handle, bytes *uint32, key *uintptr, o **ioOperation, timeout uint32) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procGetQueuedCompletionStatus.Addr(), uintptr(port), uintptr(unsafe.Pointer(bytes)), uintptr(unsafe.Pointer(key)), uintptr(unsafe.Pointer(o)), uintptr(timeout))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func setFileCompletionNotificationModes(h windows.Handle, flags uint8) (err error) {
|
||||||
|
r1, _, e1 := syscall.SyscallN(procSetFileCompletionNotificationModes.Addr(), uintptr(h), uintptr(flags))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func ntCreateNamedPipeFile(pipe *windows.Handle, access ntAccessMask, oa *objectAttributes, iosb *ioStatusBlock, share ntFileShareMode, disposition ntFileCreationDisposition, options ntFileOptions, typ uint32, readMode uint32, completionMode uint32, maxInstances uint32, inboundQuota uint32, outputQuota uint32, timeout *int64) (status ntStatus) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procNtCreateNamedPipeFile.Addr(), uintptr(unsafe.Pointer(pipe)), uintptr(access), uintptr(unsafe.Pointer(oa)), uintptr(unsafe.Pointer(iosb)), uintptr(share), uintptr(disposition), uintptr(options), uintptr(typ), uintptr(readMode), uintptr(completionMode), uintptr(maxInstances), uintptr(inboundQuota), uintptr(outputQuota), uintptr(unsafe.Pointer(timeout)))
|
||||||
|
status = ntStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDefaultNpAcl(dacl *uintptr) (status ntStatus) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procRtlDefaultNpAcl.Addr(), uintptr(unsafe.Pointer(dacl)))
|
||||||
|
status = ntStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlDosPathNameToNtPathName(name *uint16, ntName *unicodeString, filePart uintptr, reserved uintptr) (status ntStatus) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procRtlDosPathNameToNtPathName_U.Addr(), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(ntName)), uintptr(filePart), uintptr(reserved))
|
||||||
|
status = ntStatus(r0)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func rtlNtStatusToDosError(status ntStatus) (winerr error) {
|
||||||
|
r0, _, _ := syscall.SyscallN(procRtlNtStatusToDosErrorNoTeb.Addr(), uintptr(status))
|
||||||
|
if r0 != 0 {
|
||||||
|
winerr = syscall.Errno(r0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func wsaGetOverlappedResult(h windows.Handle, o *windows.Overlapped, bytes *uint32, wait bool, flags *uint32) (err error) {
|
||||||
|
var _p0 uint32
|
||||||
|
if wait {
|
||||||
|
_p0 = 1
|
||||||
|
}
|
||||||
|
r1, _, e1 := syscall.SyscallN(procWSAGetOverlappedResult.Addr(), uintptr(h), uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(bytes)), uintptr(_p0), uintptr(unsafe.Pointer(flags)))
|
||||||
|
if r1 == 0 {
|
||||||
|
err = errnoErr(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
21
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/LICENSE
generated
vendored
Normal file
21
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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.
|
||||||
74
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/osversion/osversion_windows.go
generated
vendored
Normal file
74
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/osversion/osversion_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package osversion
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OSVersion is a wrapper for Windows version information
|
||||||
|
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx
|
||||||
|
type OSVersion struct {
|
||||||
|
Version uint32
|
||||||
|
MajorVersion uint8
|
||||||
|
MinorVersion uint8
|
||||||
|
Build uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
osv OSVersion
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get gets the operating system version on Windows.
|
||||||
|
// The calling application must be manifested to get the correct version information.
|
||||||
|
func Get() OSVersion {
|
||||||
|
once.Do(func() {
|
||||||
|
v := *windows.RtlGetVersion()
|
||||||
|
osv = OSVersion{}
|
||||||
|
osv.MajorVersion = uint8(v.MajorVersion)
|
||||||
|
osv.MinorVersion = uint8(v.MinorVersion)
|
||||||
|
osv.Build = uint16(v.BuildNumber)
|
||||||
|
// Fill version value so that existing clients don't break
|
||||||
|
osv.Version = v.BuildNumber << 16
|
||||||
|
osv.Version = osv.Version | (uint32(v.MinorVersion) << 8)
|
||||||
|
osv.Version = osv.Version | v.MajorVersion
|
||||||
|
})
|
||||||
|
return osv
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build gets the build-number on Windows
|
||||||
|
// The calling application must be manifested to get the correct version information.
|
||||||
|
func Build() uint16 {
|
||||||
|
return Get().Build
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the OSVersion formatted as a string. It implements the
|
||||||
|
// [fmt.Stringer] interface.
|
||||||
|
func (osv OSVersion) String() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString returns the OSVersion formatted as a string.
|
||||||
|
//
|
||||||
|
// Deprecated: use [OSVersion.String].
|
||||||
|
func (osv OSVersion) ToString() string {
|
||||||
|
return osv.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Running `cmd /c ver` shows something like "10.0.20348.1000". The last component ("1000") is the revision
|
||||||
|
// number
|
||||||
|
func BuildRevision() (uint32, error) {
|
||||||
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("open `CurrentVersion` registry key: %w", err)
|
||||||
|
}
|
||||||
|
defer k.Close()
|
||||||
|
s, _, err := k.GetIntegerValue("UBR")
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("read `UBR` from registry: %w", err)
|
||||||
|
}
|
||||||
|
return uint32(s), nil
|
||||||
|
}
|
||||||
35
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/osversion/platform_compat_windows.go
generated
vendored
Normal file
35
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/osversion/platform_compat_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package osversion
|
||||||
|
|
||||||
|
// List of stable ABI compliant ltsc releases
|
||||||
|
// Note: List must be sorted in ascending order
|
||||||
|
var compatLTSCReleases = []uint16{
|
||||||
|
V21H2Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckHostAndContainerCompat checks if given host and container
|
||||||
|
// OS versions are compatible.
|
||||||
|
// It includes support for stable ABI compliant versions as well.
|
||||||
|
// Every release after WS 2022 will support the previous ltsc
|
||||||
|
// container image. Stable ABI is in preview mode for windows 11 client.
|
||||||
|
// Refer: https://learn.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/version-compatibility?tabs=windows-server-2022%2Cwindows-10#windows-server-host-os-compatibility
|
||||||
|
func CheckHostAndContainerCompat(host, ctr OSVersion) bool {
|
||||||
|
// check major minor versions of host and guest
|
||||||
|
if host.MajorVersion != ctr.MajorVersion ||
|
||||||
|
host.MinorVersion != ctr.MinorVersion {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If host is < WS 2022, exact version match is required
|
||||||
|
if host.Build < V21H2Server {
|
||||||
|
return host.Build == ctr.Build
|
||||||
|
}
|
||||||
|
|
||||||
|
var supportedLtscRelease uint16
|
||||||
|
for i := len(compatLTSCReleases) - 1; i >= 0; i-- {
|
||||||
|
if host.Build >= compatLTSCReleases[i] {
|
||||||
|
supportedLtscRelease = compatLTSCReleases[i]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ctr.Build >= supportedLtscRelease && ctr.Build <= host.Build
|
||||||
|
}
|
||||||
84
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/osversion/windowsbuilds.go
generated
vendored
Normal file
84
.gear/predownloaded-development/vendor/github.com/Microsoft/hcsshim/osversion/windowsbuilds.go
generated
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
package osversion
|
||||||
|
|
||||||
|
// Windows Client and Server build numbers.
|
||||||
|
//
|
||||||
|
// See:
|
||||||
|
// https://learn.microsoft.com/en-us/windows/release-health/release-information
|
||||||
|
// https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info
|
||||||
|
// https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
|
||||||
|
const (
|
||||||
|
// RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server
|
||||||
|
// 2016 (ltsc2016) and Windows 10 (Anniversary Update).
|
||||||
|
RS1 = 14393
|
||||||
|
// V1607 (version 1607, codename "Redstone 1") is an alias for [RS1].
|
||||||
|
V1607 = RS1
|
||||||
|
// LTSC2016 (Windows Server 2016) is an alias for [RS1].
|
||||||
|
LTSC2016 = RS1
|
||||||
|
|
||||||
|
// RS2 (version 1703, codename "Redstone 2") was a client-only update, and
|
||||||
|
// corresponds to Windows 10 (Creators Update).
|
||||||
|
RS2 = 15063
|
||||||
|
// V1703 (version 1703, codename "Redstone 2") is an alias for [RS2].
|
||||||
|
V1703 = RS2
|
||||||
|
|
||||||
|
// RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server
|
||||||
|
// 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update).
|
||||||
|
RS3 = 16299
|
||||||
|
// V1709 (version 1709, codename "Redstone 3") is an alias for [RS3].
|
||||||
|
V1709 = RS3
|
||||||
|
|
||||||
|
// RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server
|
||||||
|
// 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update).
|
||||||
|
RS4 = 17134
|
||||||
|
// V1803 (version 1803, codename "Redstone 4") is an alias for [RS4].
|
||||||
|
V1803 = RS4
|
||||||
|
|
||||||
|
// RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server
|
||||||
|
// 2019 (ltsc2019), and Windows 10 (October 2018 Update).
|
||||||
|
RS5 = 17763
|
||||||
|
// V1809 (version 1809, codename "Redstone 5") is an alias for [RS5].
|
||||||
|
V1809 = RS5
|
||||||
|
// LTSC2019 (Windows Server 2019) is an alias for [RS5].
|
||||||
|
LTSC2019 = RS5
|
||||||
|
|
||||||
|
// V19H1 (version 1903, codename 19H1) corresponds to Windows Server 1903 (semi-annual
|
||||||
|
// channel).
|
||||||
|
V19H1 = 18362
|
||||||
|
// V1903 (version 1903) is an alias for [V19H1].
|
||||||
|
V1903 = V19H1
|
||||||
|
|
||||||
|
// V19H2 (version 1909, codename 19H2) corresponds to Windows Server 1909 (semi-annual
|
||||||
|
// channel).
|
||||||
|
V19H2 = 18363
|
||||||
|
// V1909 (version 1909) is an alias for [V19H2].
|
||||||
|
V1909 = V19H2
|
||||||
|
|
||||||
|
// V20H1 (version 2004, codename 20H1) corresponds to Windows Server 2004 (semi-annual
|
||||||
|
// channel).
|
||||||
|
V20H1 = 19041
|
||||||
|
// V2004 (version 2004) is an alias for [V20H1].
|
||||||
|
V2004 = V20H1
|
||||||
|
|
||||||
|
// V20H2 corresponds to Windows Server 20H2 (semi-annual channel).
|
||||||
|
V20H2 = 19042
|
||||||
|
|
||||||
|
// V21H1 corresponds to Windows Server 21H1 (semi-annual channel).
|
||||||
|
V21H1 = 19043
|
||||||
|
|
||||||
|
// V21H2Win10 corresponds to Windows 10 (November 2021 Update).
|
||||||
|
V21H2Win10 = 19044
|
||||||
|
|
||||||
|
// V21H2Server corresponds to Windows Server 2022 (ltsc2022).
|
||||||
|
V21H2Server = 20348
|
||||||
|
// LTSC2022 (Windows Server 2022) is an alias for [V21H2Server]
|
||||||
|
LTSC2022 = V21H2Server
|
||||||
|
|
||||||
|
// V21H2Win11 corresponds to Windows 11 (original release).
|
||||||
|
V21H2Win11 = 22000
|
||||||
|
|
||||||
|
// V22H2Win10 corresponds to Windows 10 (2022 Update).
|
||||||
|
V22H2Win10 = 19045
|
||||||
|
|
||||||
|
// V22H2Win11 corresponds to Windows 11 (2022 Update).
|
||||||
|
V22H2Win11 = 22621
|
||||||
|
)
|
||||||
4
.gear/predownloaded-development/vendor/github.com/NYTimes/logrotate/.travis.yml
generated
vendored
Normal file
4
.gear/predownloaded-development/vendor/github.com/NYTimes/logrotate/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.x
|
||||||
|
- tip
|
||||||
201
.gear/predownloaded-development/vendor/github.com/NYTimes/logrotate/LICENSE
generated
vendored
Normal file
201
.gear/predownloaded-development/vendor/github.com/NYTimes/logrotate/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2016-2017 The New York Times Company
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue