mirror of
https://github.com/luscis/openlan.git
synced 2025-10-06 17:17:00 +08:00
clone from danieldin95
This commit is contained in:
4
vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
4
vendor/github.com/Sirupsen/logrus/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
logrus
|
||||
vendor
|
||||
|
||||
.idea/
|
40
vendor/github.com/Sirupsen/logrus/.golangci.yml
generated
vendored
Normal file
40
vendor/github.com/Sirupsen/logrus/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
run:
|
||||
# do not run on test files yet
|
||||
tests: false
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: false
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
|
||||
lll:
|
||||
line-length: 100
|
||||
tab-width: 4
|
||||
|
||||
prealloc:
|
||||
simple: false
|
||||
range-loops: false
|
||||
for-loops: false
|
||||
|
||||
whitespace:
|
||||
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
||||
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- govet
|
||||
disable:
|
||||
- maligned
|
||||
- prealloc
|
||||
disable-all: false
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
15
vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/Sirupsen/logrus/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
language: go
|
||||
go_import_path: github.com/sirupsen/logrus
|
||||
git:
|
||||
depth: 1
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
go: 1.15.x
|
||||
os: linux
|
||||
install:
|
||||
- ./travis/install.sh
|
||||
script:
|
||||
- cd ci
|
||||
- go run mage.go -v -w ../ crossBuild
|
||||
- go run mage.go -v -w ../ lint
|
||||
- go run mage.go -v -w ../ test
|
259
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
259
vendor/github.com/Sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
# 1.8.1
|
||||
Code quality:
|
||||
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
|
||||
* improve timestamp format documentation
|
||||
|
||||
Fixes:
|
||||
* fix race condition on logger hooks
|
||||
|
||||
|
||||
# 1.8.0
|
||||
|
||||
Correct versioning number replacing v1.7.1.
|
||||
|
||||
# 1.7.1
|
||||
|
||||
Beware this release has introduced a new public API and its semver is therefore incorrect.
|
||||
|
||||
Code quality:
|
||||
* use go 1.15 in travis
|
||||
* use magefile as task runner
|
||||
|
||||
Fixes:
|
||||
* small fixes about new go 1.13 error formatting system
|
||||
* Fix for long time race condiction with mutating data hooks
|
||||
|
||||
Features:
|
||||
* build support for zos
|
||||
|
||||
# 1.7.0
|
||||
Fixes:
|
||||
* the dependency toward a windows terminal library has been removed
|
||||
|
||||
Features:
|
||||
* a new buffer pool management API has been added
|
||||
* a set of `<LogLevel>Fn()` functions have been added
|
||||
|
||||
# 1.6.0
|
||||
Fixes:
|
||||
* end of line cleanup
|
||||
* revert the entry concurrency bug fix whic leads to deadlock under some circumstances
|
||||
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
||||
|
||||
Features:
|
||||
* add an option to the `TextFormatter` to completely disable fields quoting
|
||||
|
||||
# 1.5.0
|
||||
Code quality:
|
||||
* add golangci linter run on travis
|
||||
|
||||
Fixes:
|
||||
* add mutex for hooks concurrent access on `Entry` data
|
||||
* caller function field for go1.14
|
||||
* fix build issue for gopherjs target
|
||||
|
||||
Feature:
|
||||
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
||||
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
||||
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
||||
|
||||
# 1.4.2
|
||||
* Fixes build break for plan9, nacl, solaris
|
||||
# 1.4.1
|
||||
This new release introduces:
|
||||
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||
|
||||
Fixes:
|
||||
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||
|
||||
# 1.4.0
|
||||
This new release introduces:
|
||||
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
||||
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
||||
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||
|
||||
Fixes:
|
||||
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
||||
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
||||
* Fix infinite recursion on unknown `Level.String()` (#907)
|
||||
* Fix race condition in `getCaller` (#916).
|
||||
|
||||
|
||||
# 1.3.0
|
||||
This new release introduces:
|
||||
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
||||
|
||||
Fixes:
|
||||
* Building prometheus node_exporter on AIX (#840)
|
||||
* Race condition in TextFormatter (#468)
|
||||
* Travis CI import path (#868)
|
||||
* Remove coloured output on Windows (#862)
|
||||
* Pointer to func as field in JSONFormatter (#870)
|
||||
* Properly marshal Levels (#873)
|
||||
|
||||
# 1.2.0
|
||||
This new release introduces:
|
||||
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
||||
* A new trace level named `Trace` whose level is below `Debug`
|
||||
* A configurable exit function to be called upon a Fatal trace
|
||||
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
||||
|
||||
# 1.1.1
|
||||
This is a bug fix release.
|
||||
* fix the build break on Solaris
|
||||
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
||||
|
||||
# 1.1.0
|
||||
This new release introduces:
|
||||
* several fixes:
|
||||
* a fix for a race condition on entry formatting
|
||||
* proper cleanup of previously used entries before putting them back in the pool
|
||||
* the extra new line at the end of message in text formatter has been removed
|
||||
* a new global public API to check if a level is activated: IsLevelEnabled
|
||||
* the following methods have been added to the Logger object
|
||||
* IsLevelEnabled
|
||||
* SetFormatter
|
||||
* SetOutput
|
||||
* ReplaceHooks
|
||||
* introduction of go module
|
||||
* an indent configuration for the json formatter
|
||||
* output colour support for windows
|
||||
* the field sort function is now configurable for text formatter
|
||||
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
||||
|
||||
# 1.0.6
|
||||
|
||||
This new release introduces:
|
||||
* a new api WithTime which allows to easily force the time of the log entry
|
||||
which is mostly useful for logger wrapper
|
||||
* a fix reverting the immutability of the entry given as parameter to the hooks
|
||||
a new configuration field of the json formatter in order to put all the fields
|
||||
in a nested dictionnary
|
||||
* a new SetOutput method in the Logger
|
||||
* a new configuration of the textformatter to configure the name of the default keys
|
||||
* a new configuration of the text formatter to disable the level truncation
|
||||
|
||||
# 1.0.5
|
||||
|
||||
* Fix hooks race (#707)
|
||||
* Fix panic deadlock (#695)
|
||||
|
||||
# 1.0.4
|
||||
|
||||
* Fix race when adding hooks (#612)
|
||||
* Fix terminal check in AppEngine (#635)
|
||||
|
||||
# 1.0.3
|
||||
|
||||
* Replace example files with testable examples
|
||||
|
||||
# 1.0.2
|
||||
|
||||
* bug: quote non-string values in text formatter (#583)
|
||||
* Make (*Logger) SetLevel a public method
|
||||
|
||||
# 1.0.1
|
||||
|
||||
* bug: fix escaping in text formatter (#575)
|
||||
|
||||
# 1.0.0
|
||||
|
||||
* Officially changed name to lower-case
|
||||
* bug: colors on Windows 10 (#541)
|
||||
* bug: fix race in accessing level (#512)
|
||||
|
||||
# 0.11.5
|
||||
|
||||
* feature: add writer and writerlevel to entry (#372)
|
||||
|
||||
# 0.11.4
|
||||
|
||||
* bug: fix undefined variable on solaris (#493)
|
||||
|
||||
# 0.11.3
|
||||
|
||||
* formatter: configure quoting of empty values (#484)
|
||||
* formatter: configure quoting character (default is `"`) (#484)
|
||||
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||
|
||||
# 0.11.2
|
||||
|
||||
* bug: fix windows terminal detection (#476)
|
||||
|
||||
# 0.11.1
|
||||
|
||||
* bug: fix tty detection with custom out (#471)
|
||||
|
||||
# 0.11.0
|
||||
|
||||
* performance: Use bufferpool to allocate (#370)
|
||||
* terminal: terminal detection for app-engine (#343)
|
||||
* feature: exit handler (#375)
|
||||
|
||||
# 0.10.0
|
||||
|
||||
* feature: Add a test hook (#180)
|
||||
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||
* performance: avoid re-allocations on `WithFields` (#335)
|
||||
|
||||
# 0.9.0
|
||||
|
||||
* logrus/text_formatter: don't emit empty msg
|
||||
* logrus/hooks/airbrake: move out of main repository
|
||||
* logrus/hooks/sentry: move out of main repository
|
||||
* logrus/hooks/papertrail: move out of main repository
|
||||
* logrus/hooks/bugsnag: move out of main repository
|
||||
* logrus/core: run tests with `-race`
|
||||
* logrus/core: detect TTY based on `stderr`
|
||||
* logrus/core: support `WithError` on logger
|
||||
* logrus/core: Solaris support
|
||||
|
||||
# 0.8.7
|
||||
|
||||
* logrus/core: fix possible race (#216)
|
||||
* logrus/doc: small typo fixes and doc improvements
|
||||
|
||||
|
||||
# 0.8.6
|
||||
|
||||
* hooks/raven: allow passing an initialized client
|
||||
|
||||
# 0.8.5
|
||||
|
||||
* logrus/core: revert #208
|
||||
|
||||
# 0.8.4
|
||||
|
||||
* formatter/text: fix data race (#218)
|
||||
|
||||
# 0.8.3
|
||||
|
||||
* logrus/core: fix entry log level (#208)
|
||||
* logrus/core: improve performance of text formatter by 40%
|
||||
* logrus/core: expose `LevelHooks` type
|
||||
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||
* formatter/text: print structs more verbosely
|
||||
|
||||
# 0.8.2
|
||||
|
||||
* logrus: fix more Fatal family functions
|
||||
|
||||
# 0.8.1
|
||||
|
||||
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||
|
||||
# 0.8.0
|
||||
|
||||
* logrus: defaults to stderr instead of stdout
|
||||
* hooks/sentry: add special field for `*http.Request`
|
||||
* formatter/text: ignore Windows for colors
|
||||
|
||||
# 0.7.3
|
||||
|
||||
* formatter/\*: allow configuration of timestamp layout
|
||||
|
||||
# 0.7.2
|
||||
|
||||
* formatter/text: Add configuration option for time format (#158)
|
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
21
vendor/github.com/Sirupsen/logrus/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Simon Eskildsen
|
||||
|
||||
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.
|
513
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
513
vendor/github.com/Sirupsen/logrus/README.md
generated
vendored
Normal file
@@ -0,0 +1,513 @@
|
||||
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://travis-ci.org/sirupsen/logrus) [](https://godoc.org/github.com/sirupsen/logrus)
|
||||
|
||||
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||
the standard library logger.
|
||||
|
||||
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
||||
simply too hard to do in a way that won't break many people's projects, which is
|
||||
the last thing you want from your Logging library (again...).
|
||||
|
||||
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
||||
security, (backwards compatible) bug fixes, and performance (where we are
|
||||
limited by the interface).
|
||||
|
||||
I believe Logrus' biggest contribution is to have played a part in today's
|
||||
widespread use of structured logging in Golang. There doesn't seem to be a
|
||||
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
|
||||
community has built those independently. Many fantastic alternatives have sprung
|
||||
up. Logrus would look like those, had it been re-designed with what we know
|
||||
about structured logging in Go today. Check out, for example,
|
||||
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
|
||||
|
||||
[zerolog]: https://github.com/rs/zerolog
|
||||
[zap]: https://github.com/uber-go/zap
|
||||
[apex]: https://github.com/apex/log
|
||||
|
||||
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||
this caused issues in the community and we needed a standard. Some environments
|
||||
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||
Everything using `logrus` will need to use the lower-case:
|
||||
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||
|
||||
To fix Glide, see [these
|
||||
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||
For an in-depth explanation of the casing issue, see [this
|
||||
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||
|
||||
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||
plain text):
|
||||
|
||||

|
||||
|
||||
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||
or Splunk:
|
||||
|
||||
```json
|
||||
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||
|
||||
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||
|
||||
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||
|
||||
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||
```
|
||||
|
||||
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||
attached, the output is compatible with the
|
||||
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||
```
|
||||
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||
|
||||
```go
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
DisableColors: true,
|
||||
FullTimestamp: true,
|
||||
})
|
||||
```
|
||||
|
||||
#### Logging Method Name
|
||||
|
||||
If you wish to add the calling method as a field, instruct the logger via:
|
||||
```go
|
||||
log.SetReportCaller(true)
|
||||
```
|
||||
This adds the caller as 'method' like so:
|
||||
|
||||
```json
|
||||
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
||||
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||
```
|
||||
|
||||
```text
|
||||
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
||||
```
|
||||
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
||||
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
||||
environment via benchmarks:
|
||||
```
|
||||
go test -bench=.*CallerTracing
|
||||
```
|
||||
|
||||
|
||||
#### Case-sensitivity
|
||||
|
||||
The organization's name was changed to lower-case--and this will not be changed
|
||||
back. If you are getting import conflicts due to case sensitivity, please use
|
||||
the lower-case import: `github.com/sirupsen/logrus`.
|
||||
|
||||
#### Example
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
```
|
||||
|
||||
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
||||
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||
want:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Log as JSON instead of the default ASCII formatter.
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
|
||||
// Output to stdout instead of the default stderr
|
||||
// Can be any io.Writer, see below for File example
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// Only log the warning severity or above.
|
||||
log.SetLevel(log.WarnLevel)
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 122,
|
||||
}).Warn("The group's number increased tremendously!")
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"omg": true,
|
||||
"number": 100,
|
||||
}).Fatal("The ice breaks!")
|
||||
|
||||
// A common pattern is to re-use fields between logging statements by re-using
|
||||
// the logrus.Entry returned from WithFields()
|
||||
contextLogger := log.WithFields(log.Fields{
|
||||
"common": "this is a common field",
|
||||
"other": "I also should be logged always",
|
||||
})
|
||||
|
||||
contextLogger.Info("I'll be logged with common and other field")
|
||||
contextLogger.Info("Me too")
|
||||
}
|
||||
```
|
||||
|
||||
For more advanced usage such as logging to multiple locations from the same
|
||||
application, you can also create an instance of the `logrus` Logger:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Create a new instance of the logger. You can have any number of instances.
|
||||
var log = logrus.New()
|
||||
|
||||
func main() {
|
||||
// The API for setting attributes is a little different than the package level
|
||||
// exported logger. See Godoc.
|
||||
log.Out = os.Stdout
|
||||
|
||||
// You could set this to any `io.Writer` such as a file
|
||||
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
// if err == nil {
|
||||
// log.Out = file
|
||||
// } else {
|
||||
// log.Info("Failed to log to file, using default stderr")
|
||||
// }
|
||||
|
||||
log.WithFields(logrus.Fields{
|
||||
"animal": "walrus",
|
||||
"size": 10,
|
||||
}).Info("A group of walrus emerges from the ocean")
|
||||
}
|
||||
```
|
||||
|
||||
#### Fields
|
||||
|
||||
Logrus encourages careful, structured logging through logging fields instead of
|
||||
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||
to send event %s to topic %s with key %d")`, you should log the much more
|
||||
discoverable:
|
||||
|
||||
```go
|
||||
log.WithFields(log.Fields{
|
||||
"event": event,
|
||||
"topic": topic,
|
||||
"key": key,
|
||||
}).Fatal("Failed to send event")
|
||||
```
|
||||
|
||||
We've found this API forces you to think about logging in a way that produces
|
||||
much more useful logging messages. We've been in countless situations where just
|
||||
a single added field to a log statement that was already there would've saved us
|
||||
hours. The `WithFields` call is optional.
|
||||
|
||||
In general, with Logrus using any of the `printf`-family functions should be
|
||||
seen as a hint you should add a field, however, you can still use the
|
||||
`printf`-family functions with Logrus.
|
||||
|
||||
#### Default Fields
|
||||
|
||||
Often it's helpful to have fields _always_ attached to log statements in an
|
||||
application or parts of one. For example, you may want to always log the
|
||||
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||
every line, you can create a `logrus.Entry` to pass around instead:
|
||||
|
||||
```go
|
||||
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||
requestLogger.Warn("something not great happened")
|
||||
```
|
||||
|
||||
#### Hooks
|
||||
|
||||
You can add hooks for logging levels. For example to send errors to an exception
|
||||
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||
multiple places simultaneously, e.g. syslog.
|
||||
|
||||
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||
`init`:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
||||
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||
|
||||
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||
if err != nil {
|
||||
log.Error("Unable to connect to local syslog daemon")
|
||||
} else {
|
||||
log.AddHook(hook)
|
||||
}
|
||||
}
|
||||
```
|
||||
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||
|
||||
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
||||
|
||||
|
||||
#### Level logging
|
||||
|
||||
Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
|
||||
|
||||
```go
|
||||
log.Trace("Something very low level.")
|
||||
log.Debug("Useful debugging information.")
|
||||
log.Info("Something noteworthy happened!")
|
||||
log.Warn("You should probably take a look at this.")
|
||||
log.Error("Something failed but I'm not quitting.")
|
||||
// Calls os.Exit(1) after logging
|
||||
log.Fatal("Bye.")
|
||||
// Calls panic() after logging
|
||||
log.Panic("I'm bailing.")
|
||||
```
|
||||
|
||||
You can set the logging level on a `Logger`, then it will only log entries with
|
||||
that severity or anything above it:
|
||||
|
||||
```go
|
||||
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||
log.SetLevel(log.InfoLevel)
|
||||
```
|
||||
|
||||
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||
environment if your application has that.
|
||||
|
||||
#### Entries
|
||||
|
||||
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||
automatically added to all logging events:
|
||||
|
||||
1. `time`. The timestamp when the entry was created.
|
||||
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||
the `AddFields` call. E.g. `Failed to send event.`
|
||||
3. `level`. The logging level. E.g. `info`.
|
||||
|
||||
#### Environments
|
||||
|
||||
Logrus has no notion of environment.
|
||||
|
||||
If you wish for hooks and formatters to only be used in specific environments,
|
||||
you should handle that yourself. For example, if your application has a global
|
||||
variable `Environment`, which is a string representation of the environment you
|
||||
could do:
|
||||
|
||||
```go
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
init() {
|
||||
// do something here to set environment depending on an environment variable
|
||||
// or command-line flag
|
||||
if Environment == "production" {
|
||||
log.SetFormatter(&log.JSONFormatter{})
|
||||
} else {
|
||||
// The TextFormatter is default, you don't actually have to do this.
|
||||
log.SetFormatter(&log.TextFormatter{})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This configuration is how `logrus` was intended to be used, but JSON in
|
||||
production is mostly only useful if you do log aggregation with tools like
|
||||
Splunk or Logstash.
|
||||
|
||||
#### Formatters
|
||||
|
||||
The built-in logging formatters are:
|
||||
|
||||
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||
without colors.
|
||||
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||
field to `true`. To force no colored output even if there is a TTY set the
|
||||
`DisableColors` field to `true`. For Windows, see
|
||||
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||
truncation set the `DisableLevelTruncation` field to `true`.
|
||||
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||
|
||||
Third party logging formatters:
|
||||
|
||||
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
||||
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
|
||||
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
|
||||
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
||||
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
|
||||
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
||||
|
||||
You can define your formatter by implementing the `Formatter` interface,
|
||||
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||
default ones (see Entries section above):
|
||||
|
||||
```go
|
||||
type MyJSONFormatter struct {
|
||||
}
|
||||
|
||||
log.SetFormatter(new(MyJSONFormatter))
|
||||
|
||||
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
// Note this doesn't include Time, Level and Message which are available on
|
||||
// the Entry. Consult `godoc` on information about those fields or read the
|
||||
// source of the official loggers.
|
||||
serialized, err := json.Marshal(entry.Data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
```
|
||||
|
||||
#### Logger as an `io.Writer`
|
||||
|
||||
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||
|
||||
```go
|
||||
w := logger.Writer()
|
||||
defer w.Close()
|
||||
|
||||
srv := http.Server{
|
||||
// create a stdlib log.Logger that writes to
|
||||
// logrus.Logger.
|
||||
ErrorLog: log.New(w, "", 0),
|
||||
}
|
||||
```
|
||||
|
||||
Each line written to that writer will be printed the usual way, using formatters
|
||||
and hooks. The level for those entries is `info`.
|
||||
|
||||
This means that we can override the standard library logger easily:
|
||||
|
||||
```go
|
||||
logger := logrus.New()
|
||||
logger.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
// Use logrus for standard log output
|
||||
// Note that `log` here references stdlib's log
|
||||
// Not logrus imported under the name `log`.
|
||||
log.SetOutput(logger.Writer())
|
||||
```
|
||||
|
||||
#### Rotation
|
||||
|
||||
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||
external program (like `logrotate(8)`) that can compress and delete old log
|
||||
entries. It should not be a feature of the application-level logger.
|
||||
|
||||
#### Tools
|
||||
|
||||
| Tool | Description |
|
||||
| ---- | ----------- |
|
||||
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|
||||
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||
|
||||
#### Testing
|
||||
|
||||
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||
|
||||
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
|
||||
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||
|
||||
```go
|
||||
import(
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus/hooks/test"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSomething(t*testing.T){
|
||||
logger, hook := test.NewNullLogger()
|
||||
logger.Error("Helloerror")
|
||||
|
||||
assert.Equal(t, 1, len(hook.Entries))
|
||||
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||
|
||||
hook.Reset()
|
||||
assert.Nil(t, hook.LastEntry())
|
||||
}
|
||||
```
|
||||
|
||||
#### Fatal handlers
|
||||
|
||||
Logrus can register one or more functions that will be called when any `fatal`
|
||||
level message is logged. The registered handlers will be executed before
|
||||
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
|
||||
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||
|
||||
```
|
||||
...
|
||||
handler := func() {
|
||||
// gracefully shutdown something...
|
||||
}
|
||||
logrus.RegisterExitHandler(handler)
|
||||
...
|
||||
```
|
||||
|
||||
#### Thread safety
|
||||
|
||||
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
||||
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||
|
||||
Situation when locking is not needed includes:
|
||||
|
||||
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||
|
||||
* Writing to logger.Out is already thread-safe, for example:
|
||||
|
||||
1) logger.Out is protected by locks.
|
||||
|
||||
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
||||
|
||||
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
76
vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
76
vendor/github.com/Sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
package logrus
|
||||
|
||||
// The following code was sourced and modified from the
|
||||
// https://github.com/tebeka/atexit package governed by the following license:
|
||||
//
|
||||
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@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.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var handlers = []func(){}
|
||||
|
||||
func runHandler(handler func()) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
handler()
|
||||
}
|
||||
|
||||
func runHandlers() {
|
||||
for _, handler := range handlers {
|
||||
runHandler(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
||||
func Exit(code int) {
|
||||
runHandlers()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
|
||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||
// any Fatal log entry is made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
// closing database connections, or sending a alert that the application is
|
||||
// closing.
|
||||
func RegisterExitHandler(handler func()) {
|
||||
handlers = append(handlers, handler)
|
||||
}
|
||||
|
||||
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
|
||||
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||
// any Fatal log entry is made.
|
||||
//
|
||||
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||
// message but also needs to gracefully shutdown. An example usecase could be
|
||||
// closing database connections, or sending a alert that the application is
|
||||
// closing.
|
||||
func DeferExitHandler(handler func()) {
|
||||
handlers = append([]func(){handler}, handlers...)
|
||||
}
|
14
vendor/github.com/Sirupsen/logrus/appveyor.yml
generated
vendored
Normal file
14
vendor/github.com/Sirupsen/logrus/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
version: "{build}"
|
||||
platform: x64
|
||||
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
install:
|
||||
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||
- go version
|
||||
build_script:
|
||||
- go get -t
|
||||
- go test
|
52
vendor/github.com/Sirupsen/logrus/buffer_pool.go
generated
vendored
Normal file
52
vendor/github.com/Sirupsen/logrus/buffer_pool.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
bufferPool BufferPool
|
||||
)
|
||||
|
||||
type BufferPool interface {
|
||||
Put(*bytes.Buffer)
|
||||
Get() *bytes.Buffer
|
||||
}
|
||||
|
||||
type defaultPool struct {
|
||||
pool *sync.Pool
|
||||
}
|
||||
|
||||
func (p *defaultPool) Put(buf *bytes.Buffer) {
|
||||
p.pool.Put(buf)
|
||||
}
|
||||
|
||||
func (p *defaultPool) Get() *bytes.Buffer {
|
||||
return p.pool.Get().(*bytes.Buffer)
|
||||
}
|
||||
|
||||
func getBuffer() *bytes.Buffer {
|
||||
return bufferPool.Get()
|
||||
}
|
||||
|
||||
func putBuffer(buf *bytes.Buffer) {
|
||||
buf.Reset()
|
||||
bufferPool.Put(buf)
|
||||
}
|
||||
|
||||
// SetBufferPool allows to replace the default logrus buffer pool
|
||||
// to better meets the specific needs of an application.
|
||||
func SetBufferPool(bp BufferPool) {
|
||||
bufferPool = bp
|
||||
}
|
||||
|
||||
func init() {
|
||||
SetBufferPool(&defaultPool{
|
||||
pool: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
Normal file
26
vendor/github.com/Sirupsen/logrus/doc.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||
|
||||
|
||||
The simplest way to use Logrus is simply the package-level exported logger:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.WithFields(log.Fields{
|
||||
"animal": "walrus",
|
||||
"number": 1,
|
||||
"size": 10,
|
||||
}).Info("A walrus appears")
|
||||
}
|
||||
|
||||
Output:
|
||||
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||
|
||||
For a full guide visit https://github.com/sirupsen/logrus
|
||||
*/
|
||||
package logrus
|
431
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
431
vendor/github.com/Sirupsen/logrus/entry.go
generated
vendored
Normal file
@@ -0,0 +1,431 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
// qualified package name, cached at first use
|
||||
logrusPackage string
|
||||
|
||||
// Positions in the call stack when tracing to report the calling method
|
||||
minimumCallerDepth int
|
||||
|
||||
// Used for caller information initialisation
|
||||
callerInitOnce sync.Once
|
||||
)
|
||||
|
||||
const (
|
||||
maximumCallerDepth int = 25
|
||||
knownLogrusFrames int = 4
|
||||
)
|
||||
|
||||
func init() {
|
||||
// start at the bottom of the stack before the package-name cache is primed
|
||||
minimumCallerDepth = 1
|
||||
}
|
||||
|
||||
// Defines the key when adding errors using WithError.
|
||||
var ErrorKey = "error"
|
||||
|
||||
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
||||
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
||||
// reused and passed around as much as you wish to avoid field duplication.
|
||||
type Entry struct {
|
||||
Logger *Logger
|
||||
|
||||
// Contains all the fields set by the user.
|
||||
Data Fields
|
||||
|
||||
// Time at which the log entry was created
|
||||
Time time.Time
|
||||
|
||||
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||
Level Level
|
||||
|
||||
// Calling method, with package name
|
||||
Caller *runtime.Frame
|
||||
|
||||
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||
Message string
|
||||
|
||||
// When formatter is called in entry.log(), a Buffer may be set to entry
|
||||
Buffer *bytes.Buffer
|
||||
|
||||
// Contains the context set by the user. Useful for hook processing etc.
|
||||
Context context.Context
|
||||
|
||||
// err may contain a field formatting error
|
||||
err string
|
||||
}
|
||||
|
||||
func NewEntry(logger *Logger) *Entry {
|
||||
return &Entry{
|
||||
Logger: logger,
|
||||
// Default is three fields, plus one optional. Give a little extra room.
|
||||
Data: make(Fields, 6),
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Dup() *Entry {
|
||||
data := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
|
||||
}
|
||||
|
||||
// Returns the bytes representation of this entry from the formatter.
|
||||
func (entry *Entry) Bytes() ([]byte, error) {
|
||||
return entry.Logger.Formatter.Format(entry)
|
||||
}
|
||||
|
||||
// Returns the string representation from the reader and ultimately the
|
||||
// formatter.
|
||||
func (entry *Entry) String() (string, error) {
|
||||
serialized, err := entry.Bytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
str := string(serialized)
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||
func (entry *Entry) WithError(err error) *Entry {
|
||||
return entry.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// Add a context to the Entry.
|
||||
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||
dataCopy := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
dataCopy[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
||||
}
|
||||
|
||||
// Add a single field to the Entry.
|
||||
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||
return entry.WithFields(Fields{key: value})
|
||||
}
|
||||
|
||||
// Add a map of fields to the Entry.
|
||||
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||
data := make(Fields, len(entry.Data)+len(fields))
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
fieldErr := entry.err
|
||||
for k, v := range fields {
|
||||
isErrField := false
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
switch {
|
||||
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
|
||||
isErrField = true
|
||||
}
|
||||
}
|
||||
if isErrField {
|
||||
tmp := fmt.Sprintf("can not add field %q", k)
|
||||
if fieldErr != "" {
|
||||
fieldErr = entry.err + ", " + tmp
|
||||
} else {
|
||||
fieldErr = tmp
|
||||
}
|
||||
} else {
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
||||
}
|
||||
|
||||
// Overrides the time of the Entry.
|
||||
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||
dataCopy := make(Fields, len(entry.Data))
|
||||
for k, v := range entry.Data {
|
||||
dataCopy[k] = v
|
||||
}
|
||||
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
||||
}
|
||||
|
||||
// getPackageName reduces a fully qualified function name to the package name
|
||||
// There really ought to be to be a better way...
|
||||
func getPackageName(f string) string {
|
||||
for {
|
||||
lastPeriod := strings.LastIndex(f, ".")
|
||||
lastSlash := strings.LastIndex(f, "/")
|
||||
if lastPeriod > lastSlash {
|
||||
f = f[:lastPeriod]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// getCaller retrieves the name of the first non-logrus calling function
|
||||
func getCaller() *runtime.Frame {
|
||||
// cache this package's fully-qualified name
|
||||
callerInitOnce.Do(func() {
|
||||
pcs := make([]uintptr, maximumCallerDepth)
|
||||
_ = runtime.Callers(0, pcs)
|
||||
|
||||
// dynamic get the package name and the minimum caller depth
|
||||
for i := 0; i < maximumCallerDepth; i++ {
|
||||
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||||
if strings.Contains(funcName, "getCaller") {
|
||||
logrusPackage = getPackageName(funcName)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
minimumCallerDepth = knownLogrusFrames
|
||||
})
|
||||
|
||||
// Restrict the lookback frames to avoid runaway lookups
|
||||
pcs := make([]uintptr, maximumCallerDepth)
|
||||
depth := runtime.Callers(minimumCallerDepth, pcs)
|
||||
frames := runtime.CallersFrames(pcs[:depth])
|
||||
|
||||
for f, again := frames.Next(); again; f, again = frames.Next() {
|
||||
pkg := getPackageName(f.Function)
|
||||
|
||||
// If the caller isn't part of this package, we're done
|
||||
if pkg != logrusPackage {
|
||||
return &f //nolint:scopelint
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, we failed to find the caller's context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (entry Entry) HasCaller() (has bool) {
|
||||
return entry.Logger != nil &&
|
||||
entry.Logger.ReportCaller &&
|
||||
entry.Caller != nil
|
||||
}
|
||||
|
||||
func (entry *Entry) log(level Level, msg string) {
|
||||
var buffer *bytes.Buffer
|
||||
|
||||
newEntry := entry.Dup()
|
||||
|
||||
if newEntry.Time.IsZero() {
|
||||
newEntry.Time = time.Now()
|
||||
}
|
||||
|
||||
newEntry.Level = level
|
||||
newEntry.Message = msg
|
||||
|
||||
newEntry.Logger.mu.Lock()
|
||||
reportCaller := newEntry.Logger.ReportCaller
|
||||
newEntry.Logger.mu.Unlock()
|
||||
|
||||
if reportCaller {
|
||||
newEntry.Caller = getCaller()
|
||||
}
|
||||
|
||||
newEntry.fireHooks()
|
||||
|
||||
buffer = getBuffer()
|
||||
defer func() {
|
||||
newEntry.Buffer = nil
|
||||
putBuffer(buffer)
|
||||
}()
|
||||
buffer.Reset()
|
||||
newEntry.Buffer = buffer
|
||||
|
||||
newEntry.write()
|
||||
|
||||
newEntry.Buffer = nil
|
||||
|
||||
// To avoid Entry#log() returning a value that only would make sense for
|
||||
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||
// directly here.
|
||||
if level <= PanicLevel {
|
||||
panic(newEntry)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) fireHooks() {
|
||||
var tmpHooks LevelHooks
|
||||
entry.Logger.mu.Lock()
|
||||
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
|
||||
for k, v := range entry.Logger.Hooks {
|
||||
tmpHooks[k] = v
|
||||
}
|
||||
entry.Logger.mu.Unlock()
|
||||
|
||||
err := tmpHooks.Fire(entry.Level, entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) write() {
|
||||
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||
return
|
||||
}
|
||||
entry.Logger.mu.Lock()
|
||||
defer entry.Logger.mu.Unlock()
|
||||
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Log(level Level, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.log(level, fmt.Sprint(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Trace(args ...interface{}) {
|
||||
entry.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debug(args ...interface{}) {
|
||||
entry.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Print(args ...interface{}) {
|
||||
entry.Info(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Info(args ...interface{}) {
|
||||
entry.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warn(args ...interface{}) {
|
||||
entry.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warning(args ...interface{}) {
|
||||
entry.Warn(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Error(args ...interface{}) {
|
||||
entry.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatal(args ...interface{}) {
|
||||
entry.Log(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panic(args ...interface{}) {
|
||||
entry.Log(PanicLevel, args...)
|
||||
}
|
||||
|
||||
// Entry Printf family functions
|
||||
|
||||
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
||||
entry.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||
entry.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||
entry.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||
entry.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||
entry.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||
entry.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||
entry.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||
entry.Logf(FatalLevel, format, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||
entry.Logf(PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
// Entry Println family functions
|
||||
|
||||
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
||||
if entry.Logger.IsLevelEnabled(level) {
|
||||
entry.Log(level, entry.sprintlnn(args...))
|
||||
}
|
||||
}
|
||||
|
||||
func (entry *Entry) Traceln(args ...interface{}) {
|
||||
entry.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Debugln(args ...interface{}) {
|
||||
entry.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Infoln(args ...interface{}) {
|
||||
entry.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Println(args ...interface{}) {
|
||||
entry.Infoln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warnln(args ...interface{}) {
|
||||
entry.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Warningln(args ...interface{}) {
|
||||
entry.Warnln(args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Errorln(args ...interface{}) {
|
||||
entry.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||
entry.Logln(FatalLevel, args...)
|
||||
entry.Logger.Exit(1)
|
||||
}
|
||||
|
||||
func (entry *Entry) Panicln(args ...interface{}) {
|
||||
entry.Logln(PanicLevel, args...)
|
||||
}
|
||||
|
||||
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||
// string allocation, we do the simplest thing.
|
||||
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||
msg := fmt.Sprintln(args...)
|
||||
return msg[:len(msg)-1]
|
||||
}
|
270
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
270
vendor/github.com/Sirupsen/logrus/exported.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// std is the name of the standard logger in stdlib `log`
|
||||
std = New()
|
||||
)
|
||||
|
||||
func StandardLogger() *Logger {
|
||||
return std
|
||||
}
|
||||
|
||||
// SetOutput sets the standard logger output.
|
||||
func SetOutput(out io.Writer) {
|
||||
std.SetOutput(out)
|
||||
}
|
||||
|
||||
// SetFormatter sets the standard logger formatter.
|
||||
func SetFormatter(formatter Formatter) {
|
||||
std.SetFormatter(formatter)
|
||||
}
|
||||
|
||||
// SetReportCaller sets whether the standard logger will include the calling
|
||||
// method as a field.
|
||||
func SetReportCaller(include bool) {
|
||||
std.SetReportCaller(include)
|
||||
}
|
||||
|
||||
// SetLevel sets the standard logger level.
|
||||
func SetLevel(level Level) {
|
||||
std.SetLevel(level)
|
||||
}
|
||||
|
||||
// GetLevel returns the standard logger level.
|
||||
func GetLevel() Level {
|
||||
return std.GetLevel()
|
||||
}
|
||||
|
||||
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
||||
func IsLevelEnabled(level Level) bool {
|
||||
return std.IsLevelEnabled(level)
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the standard logger hooks.
|
||||
func AddHook(hook Hook) {
|
||||
std.AddHook(hook)
|
||||
}
|
||||
|
||||
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||
func WithError(err error) *Entry {
|
||||
return std.WithField(ErrorKey, err)
|
||||
}
|
||||
|
||||
// WithContext creates an entry from the standard logger and adds a context to it.
|
||||
func WithContext(ctx context.Context) *Entry {
|
||||
return std.WithContext(ctx)
|
||||
}
|
||||
|
||||
// WithField creates an entry from the standard logger and adds a field to
|
||||
// it. If you want multiple fields, use `WithFields`.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithField(key string, value interface{}) *Entry {
|
||||
return std.WithField(key, value)
|
||||
}
|
||||
|
||||
// WithFields creates an entry from the standard logger and adds multiple
|
||||
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||
// once for each field.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithFields(fields Fields) *Entry {
|
||||
return std.WithFields(fields)
|
||||
}
|
||||
|
||||
// WithTime creates an entry from the standard logger and overrides the time of
|
||||
// logs generated with it.
|
||||
//
|
||||
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||
// or Panic on the Entry it returns.
|
||||
func WithTime(t time.Time) *Entry {
|
||||
return std.WithTime(t)
|
||||
}
|
||||
|
||||
// Trace logs a message at level Trace on the standard logger.
|
||||
func Trace(args ...interface{}) {
|
||||
std.Trace(args...)
|
||||
}
|
||||
|
||||
// Debug logs a message at level Debug on the standard logger.
|
||||
func Debug(args ...interface{}) {
|
||||
std.Debug(args...)
|
||||
}
|
||||
|
||||
// Print logs a message at level Info on the standard logger.
|
||||
func Print(args ...interface{}) {
|
||||
std.Print(args...)
|
||||
}
|
||||
|
||||
// Info logs a message at level Info on the standard logger.
|
||||
func Info(args ...interface{}) {
|
||||
std.Info(args...)
|
||||
}
|
||||
|
||||
// Warn logs a message at level Warn on the standard logger.
|
||||
func Warn(args ...interface{}) {
|
||||
std.Warn(args...)
|
||||
}
|
||||
|
||||
// Warning logs a message at level Warn on the standard logger.
|
||||
func Warning(args ...interface{}) {
|
||||
std.Warning(args...)
|
||||
}
|
||||
|
||||
// Error logs a message at level Error on the standard logger.
|
||||
func Error(args ...interface{}) {
|
||||
std.Error(args...)
|
||||
}
|
||||
|
||||
// Panic logs a message at level Panic on the standard logger.
|
||||
func Panic(args ...interface{}) {
|
||||
std.Panic(args...)
|
||||
}
|
||||
|
||||
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatal(args ...interface{}) {
|
||||
std.Fatal(args...)
|
||||
}
|
||||
|
||||
// TraceFn logs a message from a func at level Trace on the standard logger.
|
||||
func TraceFn(fn LogFunction) {
|
||||
std.TraceFn(fn)
|
||||
}
|
||||
|
||||
// DebugFn logs a message from a func at level Debug on the standard logger.
|
||||
func DebugFn(fn LogFunction) {
|
||||
std.DebugFn(fn)
|
||||
}
|
||||
|
||||
// PrintFn logs a message from a func at level Info on the standard logger.
|
||||
func PrintFn(fn LogFunction) {
|
||||
std.PrintFn(fn)
|
||||
}
|
||||
|
||||
// InfoFn logs a message from a func at level Info on the standard logger.
|
||||
func InfoFn(fn LogFunction) {
|
||||
std.InfoFn(fn)
|
||||
}
|
||||
|
||||
// WarnFn logs a message from a func at level Warn on the standard logger.
|
||||
func WarnFn(fn LogFunction) {
|
||||
std.WarnFn(fn)
|
||||
}
|
||||
|
||||
// WarningFn logs a message from a func at level Warn on the standard logger.
|
||||
func WarningFn(fn LogFunction) {
|
||||
std.WarningFn(fn)
|
||||
}
|
||||
|
||||
// ErrorFn logs a message from a func at level Error on the standard logger.
|
||||
func ErrorFn(fn LogFunction) {
|
||||
std.ErrorFn(fn)
|
||||
}
|
||||
|
||||
// PanicFn logs a message from a func at level Panic on the standard logger.
|
||||
func PanicFn(fn LogFunction) {
|
||||
std.PanicFn(fn)
|
||||
}
|
||||
|
||||
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func FatalFn(fn LogFunction) {
|
||||
std.FatalFn(fn)
|
||||
}
|
||||
|
||||
// Tracef logs a message at level Trace on the standard logger.
|
||||
func Tracef(format string, args ...interface{}) {
|
||||
std.Tracef(format, args...)
|
||||
}
|
||||
|
||||
// Debugf logs a message at level Debug on the standard logger.
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
std.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Printf logs a message at level Info on the standard logger.
|
||||
func Printf(format string, args ...interface{}) {
|
||||
std.Printf(format, args...)
|
||||
}
|
||||
|
||||
// Infof logs a message at level Info on the standard logger.
|
||||
func Infof(format string, args ...interface{}) {
|
||||
std.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Warnf logs a message at level Warn on the standard logger.
|
||||
func Warnf(format string, args ...interface{}) {
|
||||
std.Warnf(format, args...)
|
||||
}
|
||||
|
||||
// Warningf logs a message at level Warn on the standard logger.
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
std.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Errorf logs a message at level Error on the standard logger.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
std.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Panicf logs a message at level Panic on the standard logger.
|
||||
func Panicf(format string, args ...interface{}) {
|
||||
std.Panicf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
std.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// Traceln logs a message at level Trace on the standard logger.
|
||||
func Traceln(args ...interface{}) {
|
||||
std.Traceln(args...)
|
||||
}
|
||||
|
||||
// Debugln logs a message at level Debug on the standard logger.
|
||||
func Debugln(args ...interface{}) {
|
||||
std.Debugln(args...)
|
||||
}
|
||||
|
||||
// Println logs a message at level Info on the standard logger.
|
||||
func Println(args ...interface{}) {
|
||||
std.Println(args...)
|
||||
}
|
||||
|
||||
// Infoln logs a message at level Info on the standard logger.
|
||||
func Infoln(args ...interface{}) {
|
||||
std.Infoln(args...)
|
||||
}
|
||||
|
||||
// Warnln logs a message at level Warn on the standard logger.
|
||||
func Warnln(args ...interface{}) {
|
||||
std.Warnln(args...)
|
||||
}
|
||||
|
||||
// Warningln logs a message at level Warn on the standard logger.
|
||||
func Warningln(args ...interface{}) {
|
||||
std.Warningln(args...)
|
||||
}
|
||||
|
||||
// Errorln logs a message at level Error on the standard logger.
|
||||
func Errorln(args ...interface{}) {
|
||||
std.Errorln(args...)
|
||||
}
|
||||
|
||||
// Panicln logs a message at level Panic on the standard logger.
|
||||
func Panicln(args ...interface{}) {
|
||||
std.Panicln(args...)
|
||||
}
|
||||
|
||||
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||
func Fatalln(args ...interface{}) {
|
||||
std.Fatalln(args...)
|
||||
}
|
78
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
78
vendor/github.com/Sirupsen/logrus/formatter.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
package logrus
|
||||
|
||||
import "time"
|
||||
|
||||
// Default key names for the default fields
|
||||
const (
|
||||
defaultTimestampFormat = time.RFC3339
|
||||
FieldKeyMsg = "msg"
|
||||
FieldKeyLevel = "level"
|
||||
FieldKeyTime = "time"
|
||||
FieldKeyLogrusError = "logrus_error"
|
||||
FieldKeyFunc = "func"
|
||||
FieldKeyFile = "file"
|
||||
)
|
||||
|
||||
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||
// `Entry`. It exposes all the fields, including the default ones:
|
||||
//
|
||||
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||
// * `entry.Data["time"]`. The timestamp.
|
||||
// * `entry.Data["level"]. The level the entry was logged at.
|
||||
//
|
||||
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||
// logged to `logger.Out`.
|
||||
type Formatter interface {
|
||||
Format(*Entry) ([]byte, error)
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
|
||||
timeKey := fieldMap.resolve(FieldKeyTime)
|
||||
if t, ok := data[timeKey]; ok {
|
||||
data["fields."+timeKey] = t
|
||||
delete(data, timeKey)
|
||||
}
|
||||
|
||||
msgKey := fieldMap.resolve(FieldKeyMsg)
|
||||
if m, ok := data[msgKey]; ok {
|
||||
data["fields."+msgKey] = m
|
||||
delete(data, msgKey)
|
||||
}
|
||||
|
||||
levelKey := fieldMap.resolve(FieldKeyLevel)
|
||||
if l, ok := data[levelKey]; ok {
|
||||
data["fields."+levelKey] = l
|
||||
delete(data, levelKey)
|
||||
}
|
||||
|
||||
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
|
||||
if l, ok := data[logrusErrKey]; ok {
|
||||
data["fields."+logrusErrKey] = l
|
||||
delete(data, logrusErrKey)
|
||||
}
|
||||
|
||||
// If reportCaller is not set, 'func' will not conflict.
|
||||
if reportCaller {
|
||||
funcKey := fieldMap.resolve(FieldKeyFunc)
|
||||
if l, ok := data[funcKey]; ok {
|
||||
data["fields."+funcKey] = l
|
||||
}
|
||||
fileKey := fieldMap.resolve(FieldKeyFile)
|
||||
if l, ok := data[fileKey]; ok {
|
||||
data["fields."+fileKey] = l
|
||||
}
|
||||
}
|
||||
}
|
10
vendor/github.com/Sirupsen/logrus/go.mod
generated
vendored
Normal file
10
vendor/github.com/Sirupsen/logrus/go.mod
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
module github.com/sirupsen/logrus
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037
|
||||
)
|
||||
|
||||
go 1.13
|
8
vendor/github.com/Sirupsen/logrus/go.sum
generated
vendored
Normal file
8
vendor/github.com/Sirupsen/logrus/go.sum
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
34
vendor/github.com/Sirupsen/logrus/hooks.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package logrus
|
||||
|
||||
// A hook to be fired when logging on the logging levels returned from
|
||||
// `Levels()` on your implementation of the interface. Note that this is not
|
||||
// fired in a goroutine or a channel with workers, you should handle such
|
||||
// functionality yourself if your call is non-blocking and you don't wish for
|
||||
// the logging calls for levels returned from `Levels()` to block.
|
||||
type Hook interface {
|
||||
Levels() []Level
|
||||
Fire(*Entry) error
|
||||
}
|
||||
|
||||
// Internal type for storing the hooks on a logger instance.
|
||||
type LevelHooks map[Level][]Hook
|
||||
|
||||
// Add a hook to an instance of logger. This is called with
|
||||
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||
func (hooks LevelHooks) Add(hook Hook) {
|
||||
for _, level := range hook.Levels() {
|
||||
hooks[level] = append(hooks[level], hook)
|
||||
}
|
||||
}
|
||||
|
||||
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||
// appropriate hooks for a log entry.
|
||||
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||
for _, hook := range hooks[level] {
|
||||
if err := hook.Fire(entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
128
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
128
vendor/github.com/Sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type fieldKey string
|
||||
|
||||
// FieldMap allows customization of the key names for default fields.
|
||||
type FieldMap map[fieldKey]string
|
||||
|
||||
func (f FieldMap) resolve(key fieldKey) string {
|
||||
if k, ok := f[key]; ok {
|
||||
return k
|
||||
}
|
||||
|
||||
return string(key)
|
||||
}
|
||||
|
||||
// JSONFormatter formats logs into parsable json
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
// The format to use is the same than for time.Format or time.Parse from the standard
|
||||
// library.
|
||||
// The standard Library already provides a set of predefined format.
|
||||
TimestampFormat string
|
||||
|
||||
// DisableTimestamp allows disabling automatic timestamps in output
|
||||
DisableTimestamp bool
|
||||
|
||||
// DisableHTMLEscape allows disabling html escaping in output
|
||||
DisableHTMLEscape bool
|
||||
|
||||
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
||||
DataKey string
|
||||
|
||||
// FieldMap allows users to customize the names of keys for default fields.
|
||||
// As an example:
|
||||
// formatter := &JSONFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message",
|
||||
// FieldKeyFunc: "@caller",
|
||||
// },
|
||||
// }
|
||||
FieldMap FieldMap
|
||||
|
||||
// CallerPrettyfier can be set by the user to modify the content
|
||||
// of the function and file keys in the json data when ReportCaller is
|
||||
// activated. If any of the returned value is the empty string the
|
||||
// corresponding key will be removed from json fields.
|
||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||
|
||||
// PrettyPrint will indent all json logs
|
||||
PrettyPrint bool
|
||||
}
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields, len(entry.Data)+4)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
// Otherwise errors are ignored by `encoding/json`
|
||||
// https://github.com/sirupsen/logrus/issues/137
|
||||
data[k] = v.Error()
|
||||
default:
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if f.DataKey != "" {
|
||||
newData := make(Fields, 4)
|
||||
newData[f.DataKey] = data
|
||||
data = newData
|
||||
}
|
||||
|
||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
|
||||
if entry.err != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
|
||||
}
|
||||
if !f.DisableTimestamp {
|
||||
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||
}
|
||||
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||
if entry.HasCaller() {
|
||||
funcVal := entry.Caller.Function
|
||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
}
|
||||
if funcVal != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
||||
}
|
||||
if fileVal != "" {
|
||||
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
|
||||
}
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
if entry.Buffer != nil {
|
||||
b = entry.Buffer
|
||||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(b)
|
||||
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
|
||||
if f.PrettyPrint {
|
||||
encoder.SetIndent("", " ")
|
||||
}
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
|
||||
}
|
||||
|
||||
return b.Bytes(), nil
|
||||
}
|
404
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
404
vendor/github.com/Sirupsen/logrus/logger.go
generated
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LogFunction For big messages, it can be more efficient to pass a function
|
||||
// and only call it if the log level is actually enables rather than
|
||||
// generating the log message and then checking if the level is enabled
|
||||
type LogFunction func() []interface{}
|
||||
|
||||
type Logger struct {
|
||||
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||
// something more adventurous, such as logging to Kafka.
|
||||
Out io.Writer
|
||||
// Hooks for the logger instance. These allow firing events based on logging
|
||||
// levels and log entries. For example, to send errors to an error tracking
|
||||
// service, log to StatsD or dump the core on fatal errors.
|
||||
Hooks LevelHooks
|
||||
// All log entries pass through the formatter before logged to Out. The
|
||||
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||
// own that implements the `Formatter` interface, see the `README` or included
|
||||
// formatters for examples.
|
||||
Formatter Formatter
|
||||
|
||||
// Flag for whether to log caller info (off by default)
|
||||
ReportCaller bool
|
||||
|
||||
// The logging level the logger should log at. This is typically (and defaults
|
||||
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||
// logged.
|
||||
Level Level
|
||||
// Used to sync writing to the log. Locking is enabled by Default
|
||||
mu MutexWrap
|
||||
// Reusable empty entry
|
||||
entryPool sync.Pool
|
||||
// Function to exit the application, defaults to `os.Exit()`
|
||||
ExitFunc exitFunc
|
||||
}
|
||||
|
||||
type exitFunc func(int)
|
||||
|
||||
type MutexWrap struct {
|
||||
lock sync.Mutex
|
||||
disabled bool
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Lock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Unlock() {
|
||||
if !mw.disabled {
|
||||
mw.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *MutexWrap) Disable() {
|
||||
mw.disabled = true
|
||||
}
|
||||
|
||||
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||
// instantiate your own:
|
||||
//
|
||||
// var log = &logrus.Logger{
|
||||
// Out: os.Stderr,
|
||||
// Formatter: new(logrus.TextFormatter),
|
||||
// Hooks: make(logrus.LevelHooks),
|
||||
// Level: logrus.DebugLevel,
|
||||
// }
|
||||
//
|
||||
// It's recommended to make this a global instance called `log`.
|
||||
func New() *Logger {
|
||||
return &Logger{
|
||||
Out: os.Stderr,
|
||||
Formatter: new(TextFormatter),
|
||||
Hooks: make(LevelHooks),
|
||||
Level: InfoLevel,
|
||||
ExitFunc: os.Exit,
|
||||
ReportCaller: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) newEntry() *Entry {
|
||||
entry, ok := logger.entryPool.Get().(*Entry)
|
||||
if ok {
|
||||
return entry
|
||||
}
|
||||
return NewEntry(logger)
|
||||
}
|
||||
|
||||
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||
entry.Data = map[string]interface{}{}
|
||||
logger.entryPool.Put(entry)
|
||||
}
|
||||
|
||||
// WithField allocates a new entry and adds a field to it.
|
||||
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
||||
// this new returned entry.
|
||||
// If you want multiple fields, use `WithFields`.
|
||||
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithField(key, value)
|
||||
}
|
||||
|
||||
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||
// each `Field`.
|
||||
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithFields(fields)
|
||||
}
|
||||
|
||||
// Add an error as single field to the log entry. All it does is call
|
||||
// `WithError` for the given `error`.
|
||||
func (logger *Logger) WithError(err error) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithError(err)
|
||||
}
|
||||
|
||||
// Add a context to the log entry.
|
||||
func (logger *Logger) WithContext(ctx context.Context) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithContext(ctx)
|
||||
}
|
||||
|
||||
// Overrides the time of the log entry.
|
||||
func (logger *Logger) WithTime(t time.Time) *Entry {
|
||||
entry := logger.newEntry()
|
||||
defer logger.releaseEntry(entry)
|
||||
return entry.WithTime(t)
|
||||
}
|
||||
|
||||
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Logf(level, format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
||||
logger.Logf(TraceLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||
logger.Logf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||
logger.Logf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Printf(format, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||
logger.Logf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||
logger.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||
logger.Logf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||
logger.Logf(FatalLevel, format, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||
logger.Logf(PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Log(level, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Log(level, fn()...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Trace(args ...interface{}) {
|
||||
logger.Log(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debug(args ...interface{}) {
|
||||
logger.Log(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Info(args ...interface{}) {
|
||||
logger.Log(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Print(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Print(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warn(args ...interface{}) {
|
||||
logger.Log(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warning(args ...interface{}) {
|
||||
logger.Warn(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Error(args ...interface{}) {
|
||||
logger.Log(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatal(args ...interface{}) {
|
||||
logger.Log(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panic(args ...interface{}) {
|
||||
logger.Log(PanicLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) TraceFn(fn LogFunction) {
|
||||
logger.LogFn(TraceLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) DebugFn(fn LogFunction) {
|
||||
logger.LogFn(DebugLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) InfoFn(fn LogFunction) {
|
||||
logger.LogFn(InfoLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) PrintFn(fn LogFunction) {
|
||||
entry := logger.newEntry()
|
||||
entry.Print(fn()...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) WarnFn(fn LogFunction) {
|
||||
logger.LogFn(WarnLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) WarningFn(fn LogFunction) {
|
||||
logger.WarnFn(fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) ErrorFn(fn LogFunction) {
|
||||
logger.LogFn(ErrorLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) FatalFn(fn LogFunction) {
|
||||
logger.LogFn(FatalLevel, fn)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) PanicFn(fn LogFunction) {
|
||||
logger.LogFn(PanicLevel, fn)
|
||||
}
|
||||
|
||||
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||
if logger.IsLevelEnabled(level) {
|
||||
entry := logger.newEntry()
|
||||
entry.Logln(level, args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
func (logger *Logger) Traceln(args ...interface{}) {
|
||||
logger.Logln(TraceLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Debugln(args ...interface{}) {
|
||||
logger.Logln(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Infoln(args ...interface{}) {
|
||||
logger.Logln(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Println(args ...interface{}) {
|
||||
entry := logger.newEntry()
|
||||
entry.Println(args...)
|
||||
logger.releaseEntry(entry)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warnln(args ...interface{}) {
|
||||
logger.Logln(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Warningln(args ...interface{}) {
|
||||
logger.Warnln(args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Errorln(args ...interface{}) {
|
||||
logger.Logln(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||
logger.Logln(FatalLevel, args...)
|
||||
logger.Exit(1)
|
||||
}
|
||||
|
||||
func (logger *Logger) Panicln(args ...interface{}) {
|
||||
logger.Logln(PanicLevel, args...)
|
||||
}
|
||||
|
||||
func (logger *Logger) Exit(code int) {
|
||||
runHandlers()
|
||||
if logger.ExitFunc == nil {
|
||||
logger.ExitFunc = os.Exit
|
||||
}
|
||||
logger.ExitFunc(code)
|
||||
}
|
||||
|
||||
//When file is opened with appending mode, it's safe to
|
||||
//write concurrently to a file (within 4k message on Linux).
|
||||
//In these cases user can choose to disable the lock.
|
||||
func (logger *Logger) SetNoLock() {
|
||||
logger.mu.Disable()
|
||||
}
|
||||
|
||||
func (logger *Logger) level() Level {
|
||||
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||
}
|
||||
|
||||
// SetLevel sets the logger level.
|
||||
func (logger *Logger) SetLevel(level Level) {
|
||||
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||
}
|
||||
|
||||
// GetLevel returns the logger level.
|
||||
func (logger *Logger) GetLevel() Level {
|
||||
return logger.level()
|
||||
}
|
||||
|
||||
// AddHook adds a hook to the logger hooks.
|
||||
func (logger *Logger) AddHook(hook Hook) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Hooks.Add(hook)
|
||||
}
|
||||
|
||||
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
||||
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
||||
return logger.level() >= level
|
||||
}
|
||||
|
||||
// SetFormatter sets the logger formatter.
|
||||
func (logger *Logger) SetFormatter(formatter Formatter) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Formatter = formatter
|
||||
}
|
||||
|
||||
// SetOutput sets the logger output.
|
||||
func (logger *Logger) SetOutput(output io.Writer) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.Out = output
|
||||
}
|
||||
|
||||
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||
logger.mu.Lock()
|
||||
defer logger.mu.Unlock()
|
||||
logger.ReportCaller = reportCaller
|
||||
}
|
||||
|
||||
// ReplaceHooks replaces the logger hooks and returns the old ones
|
||||
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||
logger.mu.Lock()
|
||||
oldHooks := logger.Hooks
|
||||
logger.Hooks = hooks
|
||||
logger.mu.Unlock()
|
||||
return oldHooks
|
||||
}
|
186
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
186
vendor/github.com/Sirupsen/logrus/logrus.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fields type, used to pass to `WithFields`.
|
||||
type Fields map[string]interface{}
|
||||
|
||||
// Level type
|
||||
type Level uint32
|
||||
|
||||
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||
func (level Level) String() string {
|
||||
if b, err := level.MarshalText(); err == nil {
|
||||
return string(b)
|
||||
} else {
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||
func ParseLevel(lvl string) (Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "panic":
|
||||
return PanicLevel, nil
|
||||
case "fatal":
|
||||
return FatalLevel, nil
|
||||
case "error":
|
||||
return ErrorLevel, nil
|
||||
case "warn", "warning":
|
||||
return WarnLevel, nil
|
||||
case "info":
|
||||
return InfoLevel, nil
|
||||
case "debug":
|
||||
return DebugLevel, nil
|
||||
case "trace":
|
||||
return TraceLevel, nil
|
||||
}
|
||||
|
||||
var l Level
|
||||
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||
}
|
||||
|
||||
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||
func (level *Level) UnmarshalText(text []byte) error {
|
||||
l, err := ParseLevel(string(text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*level = l
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (level Level) MarshalText() ([]byte, error) {
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
return []byte("trace"), nil
|
||||
case DebugLevel:
|
||||
return []byte("debug"), nil
|
||||
case InfoLevel:
|
||||
return []byte("info"), nil
|
||||
case WarnLevel:
|
||||
return []byte("warning"), nil
|
||||
case ErrorLevel:
|
||||
return []byte("error"), nil
|
||||
case FatalLevel:
|
||||
return []byte("fatal"), nil
|
||||
case PanicLevel:
|
||||
return []byte("panic"), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||
}
|
||||
|
||||
// A constant exposing all logging levels
|
||||
var AllLevels = []Level{
|
||||
PanicLevel,
|
||||
FatalLevel,
|
||||
ErrorLevel,
|
||||
WarnLevel,
|
||||
InfoLevel,
|
||||
DebugLevel,
|
||||
TraceLevel,
|
||||
}
|
||||
|
||||
// These are the different logging levels. You can set the logging level to log
|
||||
// on your instance of logger, obtained with `logrus.New()`.
|
||||
const (
|
||||
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||
// message passed to Debug, Info, ...
|
||||
PanicLevel Level = iota
|
||||
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
||||
// logging level is set to Panic.
|
||||
FatalLevel
|
||||
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||
// Commonly used for hooks to send errors to an error tracking service.
|
||||
ErrorLevel
|
||||
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||
WarnLevel
|
||||
// InfoLevel level. General operational entries about what's going on inside the
|
||||
// application.
|
||||
InfoLevel
|
||||
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||
DebugLevel
|
||||
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
||||
TraceLevel
|
||||
)
|
||||
|
||||
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||
var (
|
||||
_ StdLogger = &log.Logger{}
|
||||
_ StdLogger = &Entry{}
|
||||
_ StdLogger = &Logger{}
|
||||
)
|
||||
|
||||
// StdLogger is what your logrus-enabled library should take, that way
|
||||
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||
// interface, this is the closest we get, unfortunately.
|
||||
type StdLogger interface {
|
||||
Print(...interface{})
|
||||
Printf(string, ...interface{})
|
||||
Println(...interface{})
|
||||
|
||||
Fatal(...interface{})
|
||||
Fatalf(string, ...interface{})
|
||||
Fatalln(...interface{})
|
||||
|
||||
Panic(...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Panicln(...interface{})
|
||||
}
|
||||
|
||||
// The FieldLogger interface generalizes the Entry and Logger types
|
||||
type FieldLogger interface {
|
||||
WithField(key string, value interface{}) *Entry
|
||||
WithFields(fields Fields) *Entry
|
||||
WithError(err error) *Entry
|
||||
|
||||
Debugf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Printf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warningf(format string, args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
|
||||
Debug(args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Print(args ...interface{})
|
||||
Warn(args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Fatal(args ...interface{})
|
||||
Panic(args ...interface{})
|
||||
|
||||
Debugln(args ...interface{})
|
||||
Infoln(args ...interface{})
|
||||
Println(args ...interface{})
|
||||
Warnln(args ...interface{})
|
||||
Warningln(args ...interface{})
|
||||
Errorln(args ...interface{})
|
||||
Fatalln(args ...interface{})
|
||||
Panicln(args ...interface{})
|
||||
|
||||
// IsDebugEnabled() bool
|
||||
// IsInfoEnabled() bool
|
||||
// IsWarnEnabled() bool
|
||||
// IsErrorEnabled() bool
|
||||
// IsFatalEnabled() bool
|
||||
// IsPanicEnabled() bool
|
||||
}
|
||||
|
||||
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
|
||||
// here for consistancy. Do not use. Use Logger or Entry instead.
|
||||
type Ext1FieldLogger interface {
|
||||
FieldLogger
|
||||
Tracef(format string, args ...interface{})
|
||||
Trace(args ...interface{})
|
||||
Traceln(args ...interface{})
|
||||
}
|
11
vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go
generated
vendored
Normal file
11
vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build appengine
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
return true
|
||||
}
|
13
vendor/github.com/Sirupsen/logrus/terminal_check_bsd.go
generated
vendored
Normal file
13
vendor/github.com/Sirupsen/logrus/terminal_check_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
||||
// +build !js
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TIOCGETA
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
return err == nil
|
||||
}
|
7
vendor/github.com/Sirupsen/logrus/terminal_check_js.go
generated
vendored
Normal file
7
vendor/github.com/Sirupsen/logrus/terminal_check_js.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
// +build js
|
||||
|
||||
package logrus
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
return false
|
||||
}
|
11
vendor/github.com/Sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
Normal file
11
vendor/github.com/Sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
// +build js nacl plan9
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
return false
|
||||
}
|
17
vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
Normal file
17
vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// +build !appengine,!js,!windows,!nacl,!plan9
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
switch v := w.(type) {
|
||||
case *os.File:
|
||||
return isTerminal(int(v.Fd()))
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
11
vendor/github.com/Sirupsen/logrus/terminal_check_solaris.go
generated
vendored
Normal file
11
vendor/github.com/Sirupsen/logrus/terminal_check_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||
return err == nil
|
||||
}
|
13
vendor/github.com/Sirupsen/logrus/terminal_check_unix.go
generated
vendored
Normal file
13
vendor/github.com/Sirupsen/logrus/terminal_check_unix.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build linux aix zos
|
||||
// +build !js
|
||||
|
||||
package logrus
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const ioctlReadTermios = unix.TCGETS
|
||||
|
||||
func isTerminal(fd int) bool {
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||
return err == nil
|
||||
}
|
27
vendor/github.com/Sirupsen/logrus/terminal_check_windows.go
generated
vendored
Normal file
27
vendor/github.com/Sirupsen/logrus/terminal_check_windows.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
// +build !appengine,!js,windows
|
||||
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func checkIfTerminal(w io.Writer) bool {
|
||||
switch v := w.(type) {
|
||||
case *os.File:
|
||||
handle := windows.Handle(v.Fd())
|
||||
var mode uint32
|
||||
if err := windows.GetConsoleMode(handle, &mode); err != nil {
|
||||
return false
|
||||
}
|
||||
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
if err := windows.SetConsoleMode(handle, mode); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
339
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
339
vendor/github.com/Sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
red = 31
|
||||
yellow = 33
|
||||
blue = 36
|
||||
gray = 37
|
||||
)
|
||||
|
||||
var baseTimestamp time.Time
|
||||
|
||||
func init() {
|
||||
baseTimestamp = time.Now()
|
||||
}
|
||||
|
||||
// TextFormatter formats logs into text
|
||||
type TextFormatter struct {
|
||||
// Set to true to bypass checking for a TTY before outputting colors.
|
||||
ForceColors bool
|
||||
|
||||
// Force disabling colors.
|
||||
DisableColors bool
|
||||
|
||||
// Force quoting of all values
|
||||
ForceQuote bool
|
||||
|
||||
// DisableQuote disables quoting for all values.
|
||||
// DisableQuote will have a lower priority than ForceQuote.
|
||||
// If both of them are set to true, quote will be forced on all values.
|
||||
DisableQuote bool
|
||||
|
||||
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
|
||||
EnvironmentOverrideColors bool
|
||||
|
||||
// Disable timestamp logging. useful when output is redirected to logging
|
||||
// system that already adds timestamps.
|
||||
DisableTimestamp bool
|
||||
|
||||
// Enable logging the full timestamp when a TTY is attached instead of just
|
||||
// the time passed since beginning of execution.
|
||||
FullTimestamp bool
|
||||
|
||||
// TimestampFormat to use for display when a full timestamp is printed.
|
||||
// The format to use is the same than for time.Format or time.Parse from the standard
|
||||
// library.
|
||||
// The standard Library already provides a set of predefined format.
|
||||
TimestampFormat string
|
||||
|
||||
// The fields are sorted by default for a consistent output. For applications
|
||||
// that log extremely frequently and don't use the JSON formatter this may not
|
||||
// be desired.
|
||||
DisableSorting bool
|
||||
|
||||
// The keys sorting function, when uninitialized it uses sort.Strings.
|
||||
SortingFunc func([]string)
|
||||
|
||||
// Disables the truncation of the level text to 4 characters.
|
||||
DisableLevelTruncation bool
|
||||
|
||||
// PadLevelText Adds padding the level text so that all the levels output at the same length
|
||||
// PadLevelText is a superset of the DisableLevelTruncation option
|
||||
PadLevelText bool
|
||||
|
||||
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||
QuoteEmptyFields bool
|
||||
|
||||
// Whether the logger's out is to a terminal
|
||||
isTerminal bool
|
||||
|
||||
// FieldMap allows users to customize the names of keys for default fields.
|
||||
// As an example:
|
||||
// formatter := &TextFormatter{
|
||||
// FieldMap: FieldMap{
|
||||
// FieldKeyTime: "@timestamp",
|
||||
// FieldKeyLevel: "@level",
|
||||
// FieldKeyMsg: "@message"}}
|
||||
FieldMap FieldMap
|
||||
|
||||
// CallerPrettyfier can be set by the user to modify the content
|
||||
// of the function and file keys in the data when ReportCaller is
|
||||
// activated. If any of the returned value is the empty string the
|
||||
// corresponding key will be removed from fields.
|
||||
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||
|
||||
terminalInitOnce sync.Once
|
||||
|
||||
// The max length of the level text, generated dynamically on init
|
||||
levelTextMaxLength int
|
||||
}
|
||||
|
||||
func (f *TextFormatter) init(entry *Entry) {
|
||||
if entry.Logger != nil {
|
||||
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
||||
}
|
||||
// Get the max length of the level text
|
||||
for _, level := range AllLevels {
|
||||
levelTextLength := utf8.RuneCount([]byte(level.String()))
|
||||
if levelTextLength > f.levelTextMaxLength {
|
||||
f.levelTextMaxLength = levelTextLength
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TextFormatter) isColored() bool {
|
||||
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
||||
|
||||
if f.EnvironmentOverrideColors {
|
||||
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
|
||||
case ok && force != "0":
|
||||
isColored = true
|
||||
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
|
||||
isColored = false
|
||||
}
|
||||
}
|
||||
|
||||
return isColored && !f.DisableColors
|
||||
}
|
||||
|
||||
// Format renders a single log entry
|
||||
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||
data := make(Fields)
|
||||
for k, v := range entry.Data {
|
||||
data[k] = v
|
||||
}
|
||||
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||
keys := make([]string, 0, len(data))
|
||||
for k := range data {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
var funcVal, fileVal string
|
||||
|
||||
fixedKeys := make([]string, 0, 4+len(data))
|
||||
if !f.DisableTimestamp {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
||||
}
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
|
||||
if entry.Message != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
|
||||
}
|
||||
if entry.err != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
||||
}
|
||||
if entry.HasCaller() {
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
} else {
|
||||
funcVal = entry.Caller.Function
|
||||
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
}
|
||||
|
||||
if funcVal != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
|
||||
}
|
||||
if fileVal != "" {
|
||||
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
|
||||
}
|
||||
}
|
||||
|
||||
if !f.DisableSorting {
|
||||
if f.SortingFunc == nil {
|
||||
sort.Strings(keys)
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
} else {
|
||||
if !f.isColored() {
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
f.SortingFunc(fixedKeys)
|
||||
} else {
|
||||
f.SortingFunc(keys)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fixedKeys = append(fixedKeys, keys...)
|
||||
}
|
||||
|
||||
var b *bytes.Buffer
|
||||
if entry.Buffer != nil {
|
||||
b = entry.Buffer
|
||||
} else {
|
||||
b = &bytes.Buffer{}
|
||||
}
|
||||
|
||||
f.terminalInitOnce.Do(func() { f.init(entry) })
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = defaultTimestampFormat
|
||||
}
|
||||
if f.isColored() {
|
||||
f.printColored(b, entry, keys, data, timestampFormat)
|
||||
} else {
|
||||
|
||||
for _, key := range fixedKeys {
|
||||
var value interface{}
|
||||
switch {
|
||||
case key == f.FieldMap.resolve(FieldKeyTime):
|
||||
value = entry.Time.Format(timestampFormat)
|
||||
case key == f.FieldMap.resolve(FieldKeyLevel):
|
||||
value = entry.Level.String()
|
||||
case key == f.FieldMap.resolve(FieldKeyMsg):
|
||||
value = entry.Message
|
||||
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
||||
value = entry.err
|
||||
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
|
||||
value = funcVal
|
||||
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
|
||||
value = fileVal
|
||||
default:
|
||||
value = data[key]
|
||||
}
|
||||
f.appendKeyValue(b, key, value)
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteByte('\n')
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
|
||||
var levelColor int
|
||||
switch entry.Level {
|
||||
case DebugLevel, TraceLevel:
|
||||
levelColor = gray
|
||||
case WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, FatalLevel, PanicLevel:
|
||||
levelColor = red
|
||||
case InfoLevel:
|
||||
levelColor = blue
|
||||
default:
|
||||
levelColor = blue
|
||||
}
|
||||
|
||||
levelText := strings.ToUpper(entry.Level.String())
|
||||
if !f.DisableLevelTruncation && !f.PadLevelText {
|
||||
levelText = levelText[0:4]
|
||||
}
|
||||
if f.PadLevelText {
|
||||
// Generates the format string used in the next line, for example "%-6s" or "%-7s".
|
||||
// Based on the max level text length.
|
||||
formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
|
||||
// Formats the level text by appending spaces up to the max length, for example:
|
||||
// - "INFO "
|
||||
// - "WARNING"
|
||||
levelText = fmt.Sprintf(formatString, levelText)
|
||||
}
|
||||
|
||||
// Remove a single newline if it already exists in the message to keep
|
||||
// the behavior of logrus text_formatter the same as the stdlib log package
|
||||
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
||||
|
||||
caller := ""
|
||||
if entry.HasCaller() {
|
||||
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
|
||||
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||
|
||||
if f.CallerPrettyfier != nil {
|
||||
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||
}
|
||||
|
||||
if fileVal == "" {
|
||||
caller = funcVal
|
||||
} else if funcVal == "" {
|
||||
caller = fileVal
|
||||
} else {
|
||||
caller = fileVal + " " + funcVal
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case f.DisableTimestamp:
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
||||
case !f.FullTimestamp:
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
||||
default:
|
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
||||
}
|
||||
for _, k := range keys {
|
||||
v := data[k]
|
||||
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
||||
f.appendValue(b, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||
if f.ForceQuote {
|
||||
return true
|
||||
}
|
||||
if f.QuoteEmptyFields && len(text) == 0 {
|
||||
return true
|
||||
}
|
||||
if f.DisableQuote {
|
||||
return false
|
||||
}
|
||||
for _, ch := range text {
|
||||
if !((ch >= 'a' && ch <= 'z') ||
|
||||
(ch >= 'A' && ch <= 'Z') ||
|
||||
(ch >= '0' && ch <= '9') ||
|
||||
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||
if b.Len() > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
b.WriteString(key)
|
||||
b.WriteByte('=')
|
||||
f.appendValue(b, value)
|
||||
}
|
||||
|
||||
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||
stringVal, ok := value.(string)
|
||||
if !ok {
|
||||
stringVal = fmt.Sprint(value)
|
||||
}
|
||||
|
||||
if !f.needsQuoting(stringVal) {
|
||||
b.WriteString(stringVal)
|
||||
} else {
|
||||
b.WriteString(fmt.Sprintf("%q", stringVal))
|
||||
}
|
||||
}
|
70
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
Normal file
70
vendor/github.com/Sirupsen/logrus/writer.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
package logrus
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Writer at INFO level. See WriterLevel for details.
|
||||
func (logger *Logger) Writer() *io.PipeWriter {
|
||||
return logger.WriterLevel(InfoLevel)
|
||||
}
|
||||
|
||||
// WriterLevel returns an io.Writer that can be used to write arbitrary text to
|
||||
// the logger at the given log level. Each line written to the writer will be
|
||||
// printed in the usual way using formatters and hooks. The writer is part of an
|
||||
// io.Pipe and it is the callers responsibility to close the writer when done.
|
||||
// This can be used to override the standard library logger easily.
|
||||
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||
return NewEntry(logger).WriterLevel(level)
|
||||
}
|
||||
|
||||
func (entry *Entry) Writer() *io.PipeWriter {
|
||||
return entry.WriterLevel(InfoLevel)
|
||||
}
|
||||
|
||||
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||
reader, writer := io.Pipe()
|
||||
|
||||
var printFunc func(args ...interface{})
|
||||
|
||||
switch level {
|
||||
case TraceLevel:
|
||||
printFunc = entry.Trace
|
||||
case DebugLevel:
|
||||
printFunc = entry.Debug
|
||||
case InfoLevel:
|
||||
printFunc = entry.Info
|
||||
case WarnLevel:
|
||||
printFunc = entry.Warn
|
||||
case ErrorLevel:
|
||||
printFunc = entry.Error
|
||||
case FatalLevel:
|
||||
printFunc = entry.Fatal
|
||||
case PanicLevel:
|
||||
printFunc = entry.Panic
|
||||
default:
|
||||
printFunc = entry.Print
|
||||
}
|
||||
|
||||
go entry.writerScanner(reader, printFunc)
|
||||
runtime.SetFinalizer(writer, writerFinalizer)
|
||||
|
||||
return writer
|
||||
}
|
||||
|
||||
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
for scanner.Scan() {
|
||||
printFunc(scanner.Text())
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
entry.Errorf("Error while reading from Writer: %s", err)
|
||||
}
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
func writerFinalizer(writer *io.PipeWriter) {
|
||||
writer.Close()
|
||||
}
|
22
vendor/github.com/armon/go-socks5/.gitignore
generated
vendored
Normal file
22
vendor/github.com/armon/go-socks5/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
4
vendor/github.com/armon/go-socks5/.travis.yml
generated
vendored
Normal file
4
vendor/github.com/armon/go-socks5/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- tip
|
20
vendor/github.com/armon/go-socks5/LICENSE
generated
vendored
Normal file
20
vendor/github.com/armon/go-socks5/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Armon Dadgar
|
||||
|
||||
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.
|
45
vendor/github.com/armon/go-socks5/README.md
generated
vendored
Normal file
45
vendor/github.com/armon/go-socks5/README.md
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
go-socks5 [](https://travis-ci.org/armon/go-socks5)
|
||||
=========
|
||||
|
||||
Provides the `socks5` package that implements a [SOCKS5 server](http://en.wikipedia.org/wiki/SOCKS).
|
||||
SOCKS (Secure Sockets) is used to route traffic between a client and server through
|
||||
an intermediate proxy layer. This can be used to bypass firewalls or NATs.
|
||||
|
||||
Feature
|
||||
=======
|
||||
|
||||
The package has the following features:
|
||||
* "No Auth" mode
|
||||
* User/Password authentication
|
||||
* Support for the CONNECT command
|
||||
* Rules to do granular filtering of commands
|
||||
* Custom DNS resolution
|
||||
* Unit tests
|
||||
|
||||
TODO
|
||||
====
|
||||
|
||||
The package still needs the following:
|
||||
* Support for the BIND command
|
||||
* Support for the ASSOCIATE command
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
Below is a simple example of usage
|
||||
|
||||
```go
|
||||
// Create a SOCKS5 server
|
||||
conf := &socks5.Config{}
|
||||
server, err := socks5.New(conf)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create SOCKS5 proxy on localhost port 8000
|
||||
if err := server.ListenAndServe("tcp", "127.0.0.1:8000"); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
```
|
||||
|
151
vendor/github.com/armon/go-socks5/auth.go
generated
vendored
Normal file
151
vendor/github.com/armon/go-socks5/auth.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
NoAuth = uint8(0)
|
||||
noAcceptable = uint8(255)
|
||||
UserPassAuth = uint8(2)
|
||||
userAuthVersion = uint8(1)
|
||||
authSuccess = uint8(0)
|
||||
authFailure = uint8(1)
|
||||
)
|
||||
|
||||
var (
|
||||
UserAuthFailed = fmt.Errorf("User authentication failed")
|
||||
NoSupportedAuth = fmt.Errorf("No supported authentication mechanism")
|
||||
)
|
||||
|
||||
// A Request encapsulates authentication state provided
|
||||
// during negotiation
|
||||
type AuthContext struct {
|
||||
// Provided auth method
|
||||
Method uint8
|
||||
// Payload provided during negotiation.
|
||||
// Keys depend on the used auth method.
|
||||
// For UserPassauth contains Username
|
||||
Payload map[string]string
|
||||
}
|
||||
|
||||
type Authenticator interface {
|
||||
Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error)
|
||||
GetCode() uint8
|
||||
}
|
||||
|
||||
// NoAuthAuthenticator is used to handle the "No Authentication" mode
|
||||
type NoAuthAuthenticator struct{}
|
||||
|
||||
func (a NoAuthAuthenticator) GetCode() uint8 {
|
||||
return NoAuth
|
||||
}
|
||||
|
||||
func (a NoAuthAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
|
||||
_, err := writer.Write([]byte{socks5Version, NoAuth})
|
||||
return &AuthContext{NoAuth, nil}, err
|
||||
}
|
||||
|
||||
// UserPassAuthenticator is used to handle username/password based
|
||||
// authentication
|
||||
type UserPassAuthenticator struct {
|
||||
Credentials CredentialStore
|
||||
}
|
||||
|
||||
func (a UserPassAuthenticator) GetCode() uint8 {
|
||||
return UserPassAuth
|
||||
}
|
||||
|
||||
func (a UserPassAuthenticator) Authenticate(reader io.Reader, writer io.Writer) (*AuthContext, error) {
|
||||
// Tell the client to use user/pass auth
|
||||
if _, err := writer.Write([]byte{socks5Version, UserPassAuth}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the version and username length
|
||||
header := []byte{0, 0}
|
||||
if _, err := io.ReadAtLeast(reader, header, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Ensure we are compatible
|
||||
if header[0] != userAuthVersion {
|
||||
return nil, fmt.Errorf("Unsupported auth version: %v", header[0])
|
||||
}
|
||||
|
||||
// Get the user name
|
||||
userLen := int(header[1])
|
||||
user := make([]byte, userLen)
|
||||
if _, err := io.ReadAtLeast(reader, user, userLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the password length
|
||||
if _, err := reader.Read(header[:1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the password
|
||||
passLen := int(header[0])
|
||||
pass := make([]byte, passLen)
|
||||
if _, err := io.ReadAtLeast(reader, pass, passLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Verify the password
|
||||
if a.Credentials.Valid(string(user), string(pass)) {
|
||||
if _, err := writer.Write([]byte{userAuthVersion, authSuccess}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if _, err := writer.Write([]byte{userAuthVersion, authFailure}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, UserAuthFailed
|
||||
}
|
||||
|
||||
// Done
|
||||
return &AuthContext{UserPassAuth, map[string]string{"Username": string(user)}}, nil
|
||||
}
|
||||
|
||||
// authenticate is used to handle connection authentication
|
||||
func (s *Server) authenticate(conn io.Writer, bufConn io.Reader) (*AuthContext, error) {
|
||||
// Get the methods
|
||||
methods, err := readMethods(bufConn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get auth methods: %v", err)
|
||||
}
|
||||
|
||||
// Select a usable method
|
||||
for _, method := range methods {
|
||||
cator, found := s.authMethods[method]
|
||||
if found {
|
||||
return cator.Authenticate(bufConn, conn)
|
||||
}
|
||||
}
|
||||
|
||||
// No usable method found
|
||||
return nil, noAcceptableAuth(conn)
|
||||
}
|
||||
|
||||
// noAcceptableAuth is used to handle when we have no eligible
|
||||
// authentication mechanism
|
||||
func noAcceptableAuth(conn io.Writer) error {
|
||||
conn.Write([]byte{socks5Version, noAcceptable})
|
||||
return NoSupportedAuth
|
||||
}
|
||||
|
||||
// readMethods is used to read the number of methods
|
||||
// and proceeding auth methods
|
||||
func readMethods(r io.Reader) ([]byte, error) {
|
||||
header := []byte{0}
|
||||
if _, err := r.Read(header); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numMethods := int(header[0])
|
||||
methods := make([]byte, numMethods)
|
||||
_, err := io.ReadAtLeast(r, methods, numMethods)
|
||||
return methods, err
|
||||
}
|
17
vendor/github.com/armon/go-socks5/credentials.go
generated
vendored
Normal file
17
vendor/github.com/armon/go-socks5/credentials.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
package socks5
|
||||
|
||||
// CredentialStore is used to support user/pass authentication
|
||||
type CredentialStore interface {
|
||||
Valid(user, password string) bool
|
||||
}
|
||||
|
||||
// StaticCredentials enables using a map directly as a credential store
|
||||
type StaticCredentials map[string]string
|
||||
|
||||
func (s StaticCredentials) Valid(user, password string) bool {
|
||||
pass, ok := s[user]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return password == pass
|
||||
}
|
364
vendor/github.com/armon/go-socks5/request.go
generated
vendored
Normal file
364
vendor/github.com/armon/go-socks5/request.go
generated
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
ConnectCommand = uint8(1)
|
||||
BindCommand = uint8(2)
|
||||
AssociateCommand = uint8(3)
|
||||
ipv4Address = uint8(1)
|
||||
fqdnAddress = uint8(3)
|
||||
ipv6Address = uint8(4)
|
||||
)
|
||||
|
||||
const (
|
||||
successReply uint8 = iota
|
||||
serverFailure
|
||||
ruleFailure
|
||||
networkUnreachable
|
||||
hostUnreachable
|
||||
connectionRefused
|
||||
ttlExpired
|
||||
commandNotSupported
|
||||
addrTypeNotSupported
|
||||
)
|
||||
|
||||
var (
|
||||
unrecognizedAddrType = fmt.Errorf("Unrecognized address type")
|
||||
)
|
||||
|
||||
// AddressRewriter is used to rewrite a destination transparently
|
||||
type AddressRewriter interface {
|
||||
Rewrite(ctx context.Context, request *Request) (context.Context, *AddrSpec)
|
||||
}
|
||||
|
||||
// AddrSpec is used to return the target AddrSpec
|
||||
// which may be specified as IPv4, IPv6, or a FQDN
|
||||
type AddrSpec struct {
|
||||
FQDN string
|
||||
IP net.IP
|
||||
Port int
|
||||
}
|
||||
|
||||
func (a *AddrSpec) String() string {
|
||||
if a.FQDN != "" {
|
||||
return fmt.Sprintf("%s (%s):%d", a.FQDN, a.IP, a.Port)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", a.IP, a.Port)
|
||||
}
|
||||
|
||||
// Address returns a string suitable to dial; prefer returning IP-based
|
||||
// address, fallback to FQDN
|
||||
func (a AddrSpec) Address() string {
|
||||
if 0 != len(a.IP) {
|
||||
return net.JoinHostPort(a.IP.String(), strconv.Itoa(a.Port))
|
||||
}
|
||||
return net.JoinHostPort(a.FQDN, strconv.Itoa(a.Port))
|
||||
}
|
||||
|
||||
// A Request represents request received by a server
|
||||
type Request struct {
|
||||
// Protocol version
|
||||
Version uint8
|
||||
// Requested command
|
||||
Command uint8
|
||||
// AuthContext provided during negotiation
|
||||
AuthContext *AuthContext
|
||||
// AddrSpec of the the network that sent the request
|
||||
RemoteAddr *AddrSpec
|
||||
// AddrSpec of the desired destination
|
||||
DestAddr *AddrSpec
|
||||
// AddrSpec of the actual destination (might be affected by rewrite)
|
||||
realDestAddr *AddrSpec
|
||||
bufConn io.Reader
|
||||
}
|
||||
|
||||
type conn interface {
|
||||
Write([]byte) (int, error)
|
||||
RemoteAddr() net.Addr
|
||||
}
|
||||
|
||||
// NewRequest creates a new Request from the tcp connection
|
||||
func NewRequest(bufConn io.Reader) (*Request, error) {
|
||||
// Read the version byte
|
||||
header := []byte{0, 0, 0}
|
||||
if _, err := io.ReadAtLeast(bufConn, header, 3); err != nil {
|
||||
return nil, fmt.Errorf("Failed to get command version: %v", err)
|
||||
}
|
||||
|
||||
// Ensure we are compatible
|
||||
if header[0] != socks5Version {
|
||||
return nil, fmt.Errorf("Unsupported command version: %v", header[0])
|
||||
}
|
||||
|
||||
// Read in the destination address
|
||||
dest, err := readAddrSpec(bufConn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
request := &Request{
|
||||
Version: socks5Version,
|
||||
Command: header[1],
|
||||
DestAddr: dest,
|
||||
bufConn: bufConn,
|
||||
}
|
||||
|
||||
return request, nil
|
||||
}
|
||||
|
||||
// handleRequest is used for request processing after authentication
|
||||
func (s *Server) handleRequest(req *Request, conn conn) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// Resolve the address if we have a FQDN
|
||||
dest := req.DestAddr
|
||||
if dest.FQDN != "" {
|
||||
ctx_, addr, err := s.config.Resolver.Resolve(ctx, dest.FQDN)
|
||||
if err != nil {
|
||||
if err := sendReply(conn, hostUnreachable, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Failed to resolve destination '%v': %v", dest.FQDN, err)
|
||||
}
|
||||
ctx = ctx_
|
||||
dest.IP = addr
|
||||
}
|
||||
|
||||
// Apply any address rewrites
|
||||
req.realDestAddr = req.DestAddr
|
||||
if s.config.Rewriter != nil {
|
||||
ctx, req.realDestAddr = s.config.Rewriter.Rewrite(ctx, req)
|
||||
}
|
||||
|
||||
// Switch on the command
|
||||
switch req.Command {
|
||||
case ConnectCommand:
|
||||
return s.handleConnect(ctx, conn, req)
|
||||
case BindCommand:
|
||||
return s.handleBind(ctx, conn, req)
|
||||
case AssociateCommand:
|
||||
return s.handleAssociate(ctx, conn, req)
|
||||
default:
|
||||
if err := sendReply(conn, commandNotSupported, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Unsupported command: %v", req.Command)
|
||||
}
|
||||
}
|
||||
|
||||
// handleConnect is used to handle a connect command
|
||||
func (s *Server) handleConnect(ctx context.Context, conn conn, req *Request) error {
|
||||
// Check if this is allowed
|
||||
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
|
||||
if err := sendReply(conn, ruleFailure, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Connect to %v blocked by rules", req.DestAddr)
|
||||
} else {
|
||||
ctx = ctx_
|
||||
}
|
||||
|
||||
// Attempt to connect
|
||||
dial := s.config.Dial
|
||||
if dial == nil {
|
||||
dial = func(ctx context.Context, net_, addr string) (net.Conn, error) {
|
||||
return net.Dial(net_, addr)
|
||||
}
|
||||
}
|
||||
target, err := dial(ctx, "tcp", req.realDestAddr.Address())
|
||||
if err != nil {
|
||||
msg := err.Error()
|
||||
resp := hostUnreachable
|
||||
if strings.Contains(msg, "refused") {
|
||||
resp = connectionRefused
|
||||
} else if strings.Contains(msg, "network is unreachable") {
|
||||
resp = networkUnreachable
|
||||
}
|
||||
if err := sendReply(conn, resp, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Connect to %v failed: %v", req.DestAddr, err)
|
||||
}
|
||||
defer target.Close()
|
||||
|
||||
// Send success
|
||||
local := target.LocalAddr().(*net.TCPAddr)
|
||||
bind := AddrSpec{IP: local.IP, Port: local.Port}
|
||||
if err := sendReply(conn, successReply, &bind); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
|
||||
// Start proxying
|
||||
errCh := make(chan error, 2)
|
||||
go proxy(target, req.bufConn, errCh)
|
||||
go proxy(conn, target, errCh)
|
||||
|
||||
// Wait
|
||||
for i := 0; i < 2; i++ {
|
||||
e := <-errCh
|
||||
if e != nil {
|
||||
// return from this function closes target (and conn).
|
||||
return e
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleBind is used to handle a connect command
|
||||
func (s *Server) handleBind(ctx context.Context, conn conn, req *Request) error {
|
||||
// Check if this is allowed
|
||||
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
|
||||
if err := sendReply(conn, ruleFailure, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Bind to %v blocked by rules", req.DestAddr)
|
||||
} else {
|
||||
ctx = ctx_
|
||||
}
|
||||
|
||||
// TODO: Support bind
|
||||
if err := sendReply(conn, commandNotSupported, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleAssociate is used to handle a connect command
|
||||
func (s *Server) handleAssociate(ctx context.Context, conn conn, req *Request) error {
|
||||
// Check if this is allowed
|
||||
if ctx_, ok := s.config.Rules.Allow(ctx, req); !ok {
|
||||
if err := sendReply(conn, ruleFailure, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return fmt.Errorf("Associate to %v blocked by rules", req.DestAddr)
|
||||
} else {
|
||||
ctx = ctx_
|
||||
}
|
||||
|
||||
// TODO: Support associate
|
||||
if err := sendReply(conn, commandNotSupported, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readAddrSpec is used to read AddrSpec.
|
||||
// Expects an address type byte, follwed by the address and port
|
||||
func readAddrSpec(r io.Reader) (*AddrSpec, error) {
|
||||
d := &AddrSpec{}
|
||||
|
||||
// Get the address type
|
||||
addrType := []byte{0}
|
||||
if _, err := r.Read(addrType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle on a per type basis
|
||||
switch addrType[0] {
|
||||
case ipv4Address:
|
||||
addr := make([]byte, 4)
|
||||
if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.IP = net.IP(addr)
|
||||
|
||||
case ipv6Address:
|
||||
addr := make([]byte, 16)
|
||||
if _, err := io.ReadAtLeast(r, addr, len(addr)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.IP = net.IP(addr)
|
||||
|
||||
case fqdnAddress:
|
||||
if _, err := r.Read(addrType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrLen := int(addrType[0])
|
||||
fqdn := make([]byte, addrLen)
|
||||
if _, err := io.ReadAtLeast(r, fqdn, addrLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.FQDN = string(fqdn)
|
||||
|
||||
default:
|
||||
return nil, unrecognizedAddrType
|
||||
}
|
||||
|
||||
// Read the port
|
||||
port := []byte{0, 0}
|
||||
if _, err := io.ReadAtLeast(r, port, 2); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.Port = (int(port[0]) << 8) | int(port[1])
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
// sendReply is used to send a reply message
|
||||
func sendReply(w io.Writer, resp uint8, addr *AddrSpec) error {
|
||||
// Format the address
|
||||
var addrType uint8
|
||||
var addrBody []byte
|
||||
var addrPort uint16
|
||||
switch {
|
||||
case addr == nil:
|
||||
addrType = ipv4Address
|
||||
addrBody = []byte{0, 0, 0, 0}
|
||||
addrPort = 0
|
||||
|
||||
case addr.FQDN != "":
|
||||
addrType = fqdnAddress
|
||||
addrBody = append([]byte{byte(len(addr.FQDN))}, addr.FQDN...)
|
||||
addrPort = uint16(addr.Port)
|
||||
|
||||
case addr.IP.To4() != nil:
|
||||
addrType = ipv4Address
|
||||
addrBody = []byte(addr.IP.To4())
|
||||
addrPort = uint16(addr.Port)
|
||||
|
||||
case addr.IP.To16() != nil:
|
||||
addrType = ipv6Address
|
||||
addrBody = []byte(addr.IP.To16())
|
||||
addrPort = uint16(addr.Port)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Failed to format address: %v", addr)
|
||||
}
|
||||
|
||||
// Format the message
|
||||
msg := make([]byte, 6+len(addrBody))
|
||||
msg[0] = socks5Version
|
||||
msg[1] = resp
|
||||
msg[2] = 0 // Reserved
|
||||
msg[3] = addrType
|
||||
copy(msg[4:], addrBody)
|
||||
msg[4+len(addrBody)] = byte(addrPort >> 8)
|
||||
msg[4+len(addrBody)+1] = byte(addrPort & 0xff)
|
||||
|
||||
// Send the message
|
||||
_, err := w.Write(msg)
|
||||
return err
|
||||
}
|
||||
|
||||
type closeWriter interface {
|
||||
CloseWrite() error
|
||||
}
|
||||
|
||||
// proxy is used to suffle data from src to destination, and sends errors
|
||||
// down a dedicated channel
|
||||
func proxy(dst io.Writer, src io.Reader, errCh chan error) {
|
||||
_, err := io.Copy(dst, src)
|
||||
if tcpConn, ok := dst.(closeWriter); ok {
|
||||
tcpConn.CloseWrite()
|
||||
}
|
||||
errCh <- err
|
||||
}
|
23
vendor/github.com/armon/go-socks5/resolver.go
generated
vendored
Normal file
23
vendor/github.com/armon/go-socks5/resolver.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NameResolver is used to implement custom name resolution
|
||||
type NameResolver interface {
|
||||
Resolve(ctx context.Context, name string) (context.Context, net.IP, error)
|
||||
}
|
||||
|
||||
// DNSResolver uses the system DNS to resolve host names
|
||||
type DNSResolver struct{}
|
||||
|
||||
func (d DNSResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) {
|
||||
addr, err := net.ResolveIPAddr("ip", name)
|
||||
if err != nil {
|
||||
return ctx, nil, err
|
||||
}
|
||||
return ctx, addr.IP, err
|
||||
}
|
41
vendor/github.com/armon/go-socks5/ruleset.go
generated
vendored
Normal file
41
vendor/github.com/armon/go-socks5/ruleset.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// RuleSet is used to provide custom rules to allow or prohibit actions
|
||||
type RuleSet interface {
|
||||
Allow(ctx context.Context, req *Request) (context.Context, bool)
|
||||
}
|
||||
|
||||
// PermitAll returns a RuleSet which allows all types of connections
|
||||
func PermitAll() RuleSet {
|
||||
return &PermitCommand{true, true, true}
|
||||
}
|
||||
|
||||
// PermitNone returns a RuleSet which disallows all types of connections
|
||||
func PermitNone() RuleSet {
|
||||
return &PermitCommand{false, false, false}
|
||||
}
|
||||
|
||||
// PermitCommand is an implementation of the RuleSet which
|
||||
// enables filtering supported commands
|
||||
type PermitCommand struct {
|
||||
EnableConnect bool
|
||||
EnableBind bool
|
||||
EnableAssociate bool
|
||||
}
|
||||
|
||||
func (p *PermitCommand) Allow(ctx context.Context, req *Request) (context.Context, bool) {
|
||||
switch req.Command {
|
||||
case ConnectCommand:
|
||||
return ctx, p.EnableConnect
|
||||
case BindCommand:
|
||||
return ctx, p.EnableBind
|
||||
case AssociateCommand:
|
||||
return ctx, p.EnableAssociate
|
||||
}
|
||||
|
||||
return ctx, false
|
||||
}
|
169
vendor/github.com/armon/go-socks5/socks5.go
generated
vendored
Normal file
169
vendor/github.com/armon/go-socks5/socks5.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
package socks5
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
socks5Version = uint8(5)
|
||||
)
|
||||
|
||||
// Config is used to setup and configure a Server
|
||||
type Config struct {
|
||||
// AuthMethods can be provided to implement custom authentication
|
||||
// By default, "auth-less" mode is enabled.
|
||||
// For password-based auth use UserPassAuthenticator.
|
||||
AuthMethods []Authenticator
|
||||
|
||||
// If provided, username/password authentication is enabled,
|
||||
// by appending a UserPassAuthenticator to AuthMethods. If not provided,
|
||||
// and AUthMethods is nil, then "auth-less" mode is enabled.
|
||||
Credentials CredentialStore
|
||||
|
||||
// Resolver can be provided to do custom name resolution.
|
||||
// Defaults to DNSResolver if not provided.
|
||||
Resolver NameResolver
|
||||
|
||||
// Rules is provided to enable custom logic around permitting
|
||||
// various commands. If not provided, PermitAll is used.
|
||||
Rules RuleSet
|
||||
|
||||
// Rewriter can be used to transparently rewrite addresses.
|
||||
// This is invoked before the RuleSet is invoked.
|
||||
// Defaults to NoRewrite.
|
||||
Rewriter AddressRewriter
|
||||
|
||||
// BindIP is used for bind or udp associate
|
||||
BindIP net.IP
|
||||
|
||||
// Logger can be used to provide a custom log target.
|
||||
// Defaults to stdout.
|
||||
Logger *log.Logger
|
||||
|
||||
// Optional function for dialing out
|
||||
Dial func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
// Server is reponsible for accepting connections and handling
|
||||
// the details of the SOCKS5 protocol
|
||||
type Server struct {
|
||||
config *Config
|
||||
authMethods map[uint8]Authenticator
|
||||
}
|
||||
|
||||
// New creates a new Server and potentially returns an error
|
||||
func New(conf *Config) (*Server, error) {
|
||||
// Ensure we have at least one authentication method enabled
|
||||
if len(conf.AuthMethods) == 0 {
|
||||
if conf.Credentials != nil {
|
||||
conf.AuthMethods = []Authenticator{&UserPassAuthenticator{conf.Credentials}}
|
||||
} else {
|
||||
conf.AuthMethods = []Authenticator{&NoAuthAuthenticator{}}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have a DNS resolver
|
||||
if conf.Resolver == nil {
|
||||
conf.Resolver = DNSResolver{}
|
||||
}
|
||||
|
||||
// Ensure we have a rule set
|
||||
if conf.Rules == nil {
|
||||
conf.Rules = PermitAll()
|
||||
}
|
||||
|
||||
// Ensure we have a log target
|
||||
if conf.Logger == nil {
|
||||
conf.Logger = log.New(os.Stdout, "", log.LstdFlags)
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
config: conf,
|
||||
}
|
||||
|
||||
server.authMethods = make(map[uint8]Authenticator)
|
||||
|
||||
for _, a := range conf.AuthMethods {
|
||||
server.authMethods[a.GetCode()] = a
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// ListenAndServe is used to create a listener and serve on it
|
||||
func (s *Server) ListenAndServe(network, addr string) error {
|
||||
l, err := net.Listen(network, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Serve(l)
|
||||
}
|
||||
|
||||
// Serve is used to serve connections from a listener
|
||||
func (s *Server) Serve(l net.Listener) error {
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go s.ServeConn(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeConn is used to serve a single connection.
|
||||
func (s *Server) ServeConn(conn net.Conn) error {
|
||||
defer conn.Close()
|
||||
bufConn := bufio.NewReader(conn)
|
||||
|
||||
// Read the version byte
|
||||
version := []byte{0}
|
||||
if _, err := bufConn.Read(version); err != nil {
|
||||
s.config.Logger.Printf("[ERR] socks: Failed to get version byte: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure we are compatible
|
||||
if version[0] != socks5Version {
|
||||
err := fmt.Errorf("Unsupported SOCKS version: %v", version)
|
||||
s.config.Logger.Printf("[ERR] socks: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Authenticate the connection
|
||||
authContext, err := s.authenticate(conn, bufConn)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Failed to authenticate: %v", err)
|
||||
s.config.Logger.Printf("[ERR] socks: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
request, err := NewRequest(bufConn)
|
||||
if err != nil {
|
||||
if err == unrecognizedAddrType {
|
||||
if err := sendReply(conn, addrTypeNotSupported, nil); err != nil {
|
||||
return fmt.Errorf("Failed to send reply: %v", err)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("Failed to read destination address: %v", err)
|
||||
}
|
||||
request.AuthContext = authContext
|
||||
if client, ok := conn.RemoteAddr().(*net.TCPAddr); ok {
|
||||
request.RemoteAddr = &AddrSpec{IP: client.IP, Port: client.Port}
|
||||
}
|
||||
|
||||
// Process the client request
|
||||
if err := s.handleRequest(request, conn); err != nil {
|
||||
err = fmt.Errorf("Failed to handle request: %v", err)
|
||||
s.config.Logger.Printf("[ERR] socks: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
20
vendor/github.com/beorn7/perks/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
Copyright (C) 2013 Blake Mizerany
|
||||
|
||||
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.
|
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
2388
vendor/github.com/beorn7/perks/quantile/exampledata.txt
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
316
vendor/github.com/beorn7/perks/quantile/stream.go
generated
vendored
Normal file
@@ -0,0 +1,316 @@
|
||||
// Package quantile computes approximate quantiles over an unbounded data
|
||||
// stream within low memory and CPU bounds.
|
||||
//
|
||||
// A small amount of accuracy is traded to achieve the above properties.
|
||||
//
|
||||
// Multiple streams can be merged before calling Query to generate a single set
|
||||
// of results. This is meaningful when the streams represent the same type of
|
||||
// data. See Merge and Samples.
|
||||
//
|
||||
// For more detailed information about the algorithm used, see:
|
||||
//
|
||||
// Effective Computation of Biased Quantiles over Data Streams
|
||||
//
|
||||
// http://www.cs.rutgers.edu/~muthu/bquant.pdf
|
||||
package quantile
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Sample holds an observed value and meta information for compression. JSON
|
||||
// tags have been added for convenience.
|
||||
type Sample struct {
|
||||
Value float64 `json:",string"`
|
||||
Width float64 `json:",string"`
|
||||
Delta float64 `json:",string"`
|
||||
}
|
||||
|
||||
// Samples represents a slice of samples. It implements sort.Interface.
|
||||
type Samples []Sample
|
||||
|
||||
func (a Samples) Len() int { return len(a) }
|
||||
func (a Samples) Less(i, j int) bool { return a[i].Value < a[j].Value }
|
||||
func (a Samples) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
type invariant func(s *stream, r float64) float64
|
||||
|
||||
// NewLowBiased returns an initialized Stream for low-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the lower ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within (1±Epsilon)*Quantile.
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewLowBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * r
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewHighBiased returns an initialized Stream for high-biased quantiles
|
||||
// (e.g. 0.01, 0.1, 0.5) where the needed quantiles are not known a priori, but
|
||||
// error guarantees can still be given even for the higher ranks of the data
|
||||
// distribution.
|
||||
//
|
||||
// The provided epsilon is a relative error, i.e. the true quantile of a value
|
||||
// returned by a query is guaranteed to be within 1-(1±Epsilon)*(1-Quantile).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error
|
||||
// properties.
|
||||
func NewHighBiased(epsilon float64) *Stream {
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
return 2 * epsilon * (s.n - r)
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
// NewTargeted returns an initialized Stream concerned with a particular set of
|
||||
// quantile values that are supplied a priori. Knowing these a priori reduces
|
||||
// space and computation time. The targets map maps the desired quantiles to
|
||||
// their absolute errors, i.e. the true quantile of a value returned by a query
|
||||
// is guaranteed to be within (Quantile±Epsilon).
|
||||
//
|
||||
// See http://www.cs.rutgers.edu/~muthu/bquant.pdf for time, space, and error properties.
|
||||
func NewTargeted(targetMap map[float64]float64) *Stream {
|
||||
// Convert map to slice to avoid slow iterations on a map.
|
||||
// ƒ is called on the hot path, so converting the map to a slice
|
||||
// beforehand results in significant CPU savings.
|
||||
targets := targetMapToSlice(targetMap)
|
||||
|
||||
ƒ := func(s *stream, r float64) float64 {
|
||||
var m = math.MaxFloat64
|
||||
var f float64
|
||||
for _, t := range targets {
|
||||
if t.quantile*s.n <= r {
|
||||
f = (2 * t.epsilon * r) / t.quantile
|
||||
} else {
|
||||
f = (2 * t.epsilon * (s.n - r)) / (1 - t.quantile)
|
||||
}
|
||||
if f < m {
|
||||
m = f
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
return newStream(ƒ)
|
||||
}
|
||||
|
||||
type target struct {
|
||||
quantile float64
|
||||
epsilon float64
|
||||
}
|
||||
|
||||
func targetMapToSlice(targetMap map[float64]float64) []target {
|
||||
targets := make([]target, 0, len(targetMap))
|
||||
|
||||
for quantile, epsilon := range targetMap {
|
||||
t := target{
|
||||
quantile: quantile,
|
||||
epsilon: epsilon,
|
||||
}
|
||||
targets = append(targets, t)
|
||||
}
|
||||
|
||||
return targets
|
||||
}
|
||||
|
||||
// Stream computes quantiles for a stream of float64s. It is not thread-safe by
|
||||
// design. Take care when using across multiple goroutines.
|
||||
type Stream struct {
|
||||
*stream
|
||||
b Samples
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newStream(ƒ invariant) *Stream {
|
||||
x := &stream{ƒ: ƒ}
|
||||
return &Stream{x, make(Samples, 0, 500), true}
|
||||
}
|
||||
|
||||
// Insert inserts v into the stream.
|
||||
func (s *Stream) Insert(v float64) {
|
||||
s.insert(Sample{Value: v, Width: 1})
|
||||
}
|
||||
|
||||
func (s *Stream) insert(sample Sample) {
|
||||
s.b = append(s.b, sample)
|
||||
s.sorted = false
|
||||
if len(s.b) == cap(s.b) {
|
||||
s.flush()
|
||||
}
|
||||
}
|
||||
|
||||
// Query returns the computed qth percentiles value. If s was created with
|
||||
// NewTargeted, and q is not in the set of quantiles provided a priori, Query
|
||||
// will return an unspecified result.
|
||||
func (s *Stream) Query(q float64) float64 {
|
||||
if !s.flushed() {
|
||||
// Fast path when there hasn't been enough data for a flush;
|
||||
// this also yields better accuracy for small sets of data.
|
||||
l := len(s.b)
|
||||
if l == 0 {
|
||||
return 0
|
||||
}
|
||||
i := int(math.Ceil(float64(l) * q))
|
||||
if i > 0 {
|
||||
i -= 1
|
||||
}
|
||||
s.maybeSort()
|
||||
return s.b[i].Value
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.query(q)
|
||||
}
|
||||
|
||||
// Merge merges samples into the underlying streams samples. This is handy when
|
||||
// merging multiple streams from separate threads, database shards, etc.
|
||||
//
|
||||
// ATTENTION: This method is broken and does not yield correct results. The
|
||||
// underlying algorithm is not capable of merging streams correctly.
|
||||
func (s *Stream) Merge(samples Samples) {
|
||||
sort.Sort(samples)
|
||||
s.stream.merge(samples)
|
||||
}
|
||||
|
||||
// Reset reinitializes and clears the list reusing the samples buffer memory.
|
||||
func (s *Stream) Reset() {
|
||||
s.stream.reset()
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
// Samples returns stream samples held by s.
|
||||
func (s *Stream) Samples() Samples {
|
||||
if !s.flushed() {
|
||||
return s.b
|
||||
}
|
||||
s.flush()
|
||||
return s.stream.samples()
|
||||
}
|
||||
|
||||
// Count returns the total number of samples observed in the stream
|
||||
// since initialization.
|
||||
func (s *Stream) Count() int {
|
||||
return len(s.b) + s.stream.count()
|
||||
}
|
||||
|
||||
func (s *Stream) flush() {
|
||||
s.maybeSort()
|
||||
s.stream.merge(s.b)
|
||||
s.b = s.b[:0]
|
||||
}
|
||||
|
||||
func (s *Stream) maybeSort() {
|
||||
if !s.sorted {
|
||||
s.sorted = true
|
||||
sort.Sort(s.b)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Stream) flushed() bool {
|
||||
return len(s.stream.l) > 0
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
n float64
|
||||
l []Sample
|
||||
ƒ invariant
|
||||
}
|
||||
|
||||
func (s *stream) reset() {
|
||||
s.l = s.l[:0]
|
||||
s.n = 0
|
||||
}
|
||||
|
||||
func (s *stream) insert(v float64) {
|
||||
s.merge(Samples{{v, 1, 0}})
|
||||
}
|
||||
|
||||
func (s *stream) merge(samples Samples) {
|
||||
// TODO(beorn7): This tries to merge not only individual samples, but
|
||||
// whole summaries. The paper doesn't mention merging summaries at
|
||||
// all. Unittests show that the merging is inaccurate. Find out how to
|
||||
// do merges properly.
|
||||
var r float64
|
||||
i := 0
|
||||
for _, sample := range samples {
|
||||
for ; i < len(s.l); i++ {
|
||||
c := s.l[i]
|
||||
if c.Value > sample.Value {
|
||||
// Insert at position i.
|
||||
s.l = append(s.l, Sample{})
|
||||
copy(s.l[i+1:], s.l[i:])
|
||||
s.l[i] = Sample{
|
||||
sample.Value,
|
||||
sample.Width,
|
||||
math.Max(sample.Delta, math.Floor(s.ƒ(s, r))-1),
|
||||
// TODO(beorn7): How to calculate delta correctly?
|
||||
}
|
||||
i++
|
||||
goto inserted
|
||||
}
|
||||
r += c.Width
|
||||
}
|
||||
s.l = append(s.l, Sample{sample.Value, sample.Width, 0})
|
||||
i++
|
||||
inserted:
|
||||
s.n += sample.Width
|
||||
r += sample.Width
|
||||
}
|
||||
s.compress()
|
||||
}
|
||||
|
||||
func (s *stream) count() int {
|
||||
return int(s.n)
|
||||
}
|
||||
|
||||
func (s *stream) query(q float64) float64 {
|
||||
t := math.Ceil(q * s.n)
|
||||
t += math.Ceil(s.ƒ(s, t) / 2)
|
||||
p := s.l[0]
|
||||
var r float64
|
||||
for _, c := range s.l[1:] {
|
||||
r += p.Width
|
||||
if r+c.Width+c.Delta > t {
|
||||
return p.Value
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.Value
|
||||
}
|
||||
|
||||
func (s *stream) compress() {
|
||||
if len(s.l) < 2 {
|
||||
return
|
||||
}
|
||||
x := s.l[len(s.l)-1]
|
||||
xi := len(s.l) - 1
|
||||
r := s.n - 1 - x.Width
|
||||
|
||||
for i := len(s.l) - 2; i >= 0; i-- {
|
||||
c := s.l[i]
|
||||
if c.Width+x.Width+x.Delta <= s.ƒ(s, r) {
|
||||
x.Width += c.Width
|
||||
s.l[xi] = x
|
||||
// Remove element at i.
|
||||
copy(s.l[i:], s.l[i+1:])
|
||||
s.l = s.l[:len(s.l)-1]
|
||||
xi -= 1
|
||||
} else {
|
||||
x = c
|
||||
xi = i
|
||||
}
|
||||
r -= c.Width
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stream) samples() Samples {
|
||||
samples := make(Samples, len(s.l))
|
||||
copy(samples, s.l)
|
||||
return samples
|
||||
}
|
25
vendor/github.com/cenkalti/backoff/v4/.gitignore
generated
vendored
Normal file
25
vendor/github.com/cenkalti/backoff/v4/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
|
||||
# IDEs
|
||||
.idea/
|
10
vendor/github.com/cenkalti/backoff/v4/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/cenkalti/backoff/v4/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.13
|
||||
- 1.x
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
20
vendor/github.com/cenkalti/backoff/v4/LICENSE
generated
vendored
Normal file
20
vendor/github.com/cenkalti/backoff/v4/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Cenk Altı
|
||||
|
||||
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.
|
32
vendor/github.com/cenkalti/backoff/v4/README.md
generated
vendored
Normal file
32
vendor/github.com/cenkalti/backoff/v4/README.md
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
|
||||
|
||||
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
||||
|
||||
[Exponential backoff][exponential backoff wiki]
|
||||
is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
|
||||
in order to gradually find an acceptable rate.
|
||||
The retries exponentially increase and stop increasing when a certain threshold is met.
|
||||
|
||||
## Usage
|
||||
|
||||
Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end.
|
||||
|
||||
Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
|
||||
|
||||
## Contributing
|
||||
|
||||
* I would like to keep this library as small as possible.
|
||||
* Please don't send a PR without opening an issue and discussing it first.
|
||||
* If proposed change is not a common use case, I will probably not accept it.
|
||||
|
||||
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
|
||||
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
||||
[travis]: https://travis-ci.org/cenkalti/backoff
|
||||
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
|
||||
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
|
||||
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
|
||||
|
||||
[google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java
|
||||
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
|
||||
|
||||
[advanced example]: https://pkg.go.dev/github.com/cenkalti/backoff/v4?tab=doc#pkg-examples
|
66
vendor/github.com/cenkalti/backoff/v4/backoff.go
generated
vendored
Normal file
66
vendor/github.com/cenkalti/backoff/v4/backoff.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Package backoff implements backoff algorithms for retrying operations.
|
||||
//
|
||||
// Use Retry function for retrying operations that may fail.
|
||||
// If Retry does not meet your needs,
|
||||
// copy/paste the function into your project and modify as you wish.
|
||||
//
|
||||
// There is also Ticker type similar to time.Ticker.
|
||||
// You can use it if you need to work with channels.
|
||||
//
|
||||
// See Examples section below for usage examples.
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
// BackOff is a backoff policy for retrying an operation.
|
||||
type BackOff interface {
|
||||
// NextBackOff returns the duration to wait before retrying the operation,
|
||||
// or backoff. Stop to indicate that no more retries should be made.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// duration := backoff.NextBackOff();
|
||||
// if (duration == backoff.Stop) {
|
||||
// // Do not retry operation.
|
||||
// } else {
|
||||
// // Sleep for duration and retry operation.
|
||||
// }
|
||||
//
|
||||
NextBackOff() time.Duration
|
||||
|
||||
// Reset to initial state.
|
||||
Reset()
|
||||
}
|
||||
|
||||
// Stop indicates that no more retries should be made for use in NextBackOff().
|
||||
const Stop time.Duration = -1
|
||||
|
||||
// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
|
||||
// meaning that the operation is retried immediately without waiting, indefinitely.
|
||||
type ZeroBackOff struct{}
|
||||
|
||||
func (b *ZeroBackOff) Reset() {}
|
||||
|
||||
func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
|
||||
|
||||
// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
|
||||
// NextBackOff(), meaning that the operation should never be retried.
|
||||
type StopBackOff struct{}
|
||||
|
||||
func (b *StopBackOff) Reset() {}
|
||||
|
||||
func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
|
||||
|
||||
// ConstantBackOff is a backoff policy that always returns the same backoff delay.
|
||||
// This is in contrast to an exponential backoff policy,
|
||||
// which returns a delay that grows longer as you call NextBackOff() over and over again.
|
||||
type ConstantBackOff struct {
|
||||
Interval time.Duration
|
||||
}
|
||||
|
||||
func (b *ConstantBackOff) Reset() {}
|
||||
func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
|
||||
|
||||
func NewConstantBackOff(d time.Duration) *ConstantBackOff {
|
||||
return &ConstantBackOff{Interval: d}
|
||||
}
|
62
vendor/github.com/cenkalti/backoff/v4/context.go
generated
vendored
Normal file
62
vendor/github.com/cenkalti/backoff/v4/context.go
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BackOffContext is a backoff policy that stops retrying after the context
|
||||
// is canceled.
|
||||
type BackOffContext interface { // nolint: golint
|
||||
BackOff
|
||||
Context() context.Context
|
||||
}
|
||||
|
||||
type backOffContext struct {
|
||||
BackOff
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// WithContext returns a BackOffContext with context ctx
|
||||
//
|
||||
// ctx must not be nil
|
||||
func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint
|
||||
if ctx == nil {
|
||||
panic("nil context")
|
||||
}
|
||||
|
||||
if b, ok := b.(*backOffContext); ok {
|
||||
return &backOffContext{
|
||||
BackOff: b.BackOff,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
return &backOffContext{
|
||||
BackOff: b,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func getContext(b BackOff) context.Context {
|
||||
if cb, ok := b.(BackOffContext); ok {
|
||||
return cb.Context()
|
||||
}
|
||||
if tb, ok := b.(*backOffTries); ok {
|
||||
return getContext(tb.delegate)
|
||||
}
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
func (b *backOffContext) Context() context.Context {
|
||||
return b.ctx
|
||||
}
|
||||
|
||||
func (b *backOffContext) NextBackOff() time.Duration {
|
||||
select {
|
||||
case <-b.ctx.Done():
|
||||
return Stop
|
||||
default:
|
||||
return b.BackOff.NextBackOff()
|
||||
}
|
||||
}
|
158
vendor/github.com/cenkalti/backoff/v4/exponential.go
generated
vendored
Normal file
158
vendor/github.com/cenkalti/backoff/v4/exponential.go
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
ExponentialBackOff is a backoff implementation that increases the backoff
|
||||
period for each retry attempt using a randomization function that grows exponentially.
|
||||
|
||||
NextBackOff() is calculated using the following formula:
|
||||
|
||||
randomized interval =
|
||||
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
|
||||
|
||||
In other words NextBackOff() will range between the randomization factor
|
||||
percentage below and above the retry interval.
|
||||
|
||||
For example, given the following parameters:
|
||||
|
||||
RetryInterval = 2
|
||||
RandomizationFactor = 0.5
|
||||
Multiplier = 2
|
||||
|
||||
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
|
||||
multiplied by the exponential, that is, between 2 and 6 seconds.
|
||||
|
||||
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
||||
|
||||
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
||||
|
||||
The elapsed time can be reset by calling Reset().
|
||||
|
||||
Example: Given the following default arguments, for 10 tries the sequence will be,
|
||||
and assuming we go over the MaxElapsedTime on the 10th try:
|
||||
|
||||
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
||||
|
||||
1 0.5 [0.25, 0.75]
|
||||
2 0.75 [0.375, 1.125]
|
||||
3 1.125 [0.562, 1.687]
|
||||
4 1.687 [0.8435, 2.53]
|
||||
5 2.53 [1.265, 3.795]
|
||||
6 3.795 [1.897, 5.692]
|
||||
7 5.692 [2.846, 8.538]
|
||||
8 8.538 [4.269, 12.807]
|
||||
9 12.807 [6.403, 19.210]
|
||||
10 19.210 backoff.Stop
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
type ExponentialBackOff struct {
|
||||
InitialInterval time.Duration
|
||||
RandomizationFactor float64
|
||||
Multiplier float64
|
||||
MaxInterval time.Duration
|
||||
// After MaxElapsedTime the ExponentialBackOff returns Stop.
|
||||
// It never stops if MaxElapsedTime == 0.
|
||||
MaxElapsedTime time.Duration
|
||||
Stop time.Duration
|
||||
Clock Clock
|
||||
|
||||
currentInterval time.Duration
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// Clock is an interface that returns current time for BackOff.
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
// Default values for ExponentialBackOff.
|
||||
const (
|
||||
DefaultInitialInterval = 500 * time.Millisecond
|
||||
DefaultRandomizationFactor = 0.5
|
||||
DefaultMultiplier = 1.5
|
||||
DefaultMaxInterval = 60 * time.Second
|
||||
DefaultMaxElapsedTime = 15 * time.Minute
|
||||
)
|
||||
|
||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||
func NewExponentialBackOff() *ExponentialBackOff {
|
||||
b := &ExponentialBackOff{
|
||||
InitialInterval: DefaultInitialInterval,
|
||||
RandomizationFactor: DefaultRandomizationFactor,
|
||||
Multiplier: DefaultMultiplier,
|
||||
MaxInterval: DefaultMaxInterval,
|
||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||
Stop: Stop,
|
||||
Clock: SystemClock,
|
||||
}
|
||||
b.Reset()
|
||||
return b
|
||||
}
|
||||
|
||||
type systemClock struct{}
|
||||
|
||||
func (t systemClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// SystemClock implements Clock interface that uses time.Now().
|
||||
var SystemClock = systemClock{}
|
||||
|
||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||
// Reset must be called before using b.
|
||||
func (b *ExponentialBackOff) Reset() {
|
||||
b.currentInterval = b.InitialInterval
|
||||
b.startTime = b.Clock.Now()
|
||||
}
|
||||
|
||||
// NextBackOff calculates the next backoff interval using the formula:
|
||||
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
|
||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||
// Make sure we have not gone over the maximum elapsed time.
|
||||
elapsed := b.GetElapsedTime()
|
||||
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||
b.incrementCurrentInterval()
|
||||
if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
|
||||
return b.Stop
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||
// is created and is reset when Reset() is called.
|
||||
//
|
||||
// The elapsed time is computed using time.Now().UnixNano(). It is
|
||||
// safe to call even while the backoff policy is used by a running
|
||||
// ticker.
|
||||
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
||||
return b.Clock.Now().Sub(b.startTime)
|
||||
}
|
||||
|
||||
// Increments the current interval by multiplying it with the multiplier.
|
||||
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||
if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
|
||||
b.currentInterval = b.MaxInterval
|
||||
} else {
|
||||
b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a random value from the following interval:
|
||||
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||
var delta = randomizationFactor * float64(currentInterval)
|
||||
var minInterval = float64(currentInterval) - delta
|
||||
var maxInterval = float64(currentInterval) + delta
|
||||
|
||||
// Get a random value from the range [minInterval, maxInterval].
|
||||
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
|
||||
// we want a 33% chance for selecting either 1, 2 or 3.
|
||||
return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
|
||||
}
|
3
vendor/github.com/cenkalti/backoff/v4/go.mod
generated
vendored
Normal file
3
vendor/github.com/cenkalti/backoff/v4/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/cenkalti/backoff/v4
|
||||
|
||||
go 1.13
|
112
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
Normal file
112
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// An Operation is executing by Retry() or RetryNotify().
|
||||
// The operation will be retried using a backoff policy if it returns an error.
|
||||
type Operation func() error
|
||||
|
||||
// Notify is a notify-on-error function. It receives an operation error and
|
||||
// backoff delay if the operation failed (with an error).
|
||||
//
|
||||
// NOTE that if the backoff policy stated to stop retrying,
|
||||
// the notify function isn't called.
|
||||
type Notify func(error, time.Duration)
|
||||
|
||||
// Retry the operation o until it does not return error or BackOff stops.
|
||||
// o is guaranteed to be run at least once.
|
||||
//
|
||||
// If o returns a *PermanentError, the operation is not retried, and the
|
||||
// wrapped error is returned.
|
||||
//
|
||||
// Retry sleeps the goroutine for the duration returned by BackOff after a
|
||||
// failed operation returns.
|
||||
func Retry(o Operation, b BackOff) error {
|
||||
return RetryNotify(o, b, nil)
|
||||
}
|
||||
|
||||
// RetryNotify calls notify function with the error and wait duration
|
||||
// for each failed attempt before sleep.
|
||||
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||
return RetryNotifyWithTimer(operation, b, notify, nil)
|
||||
}
|
||||
|
||||
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
|
||||
// for each failed attempt before sleep.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
|
||||
var err error
|
||||
var next time.Duration
|
||||
if t == nil {
|
||||
t = &defaultTimer{}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
t.Stop()
|
||||
}()
|
||||
|
||||
ctx := getContext(b)
|
||||
|
||||
b.Reset()
|
||||
for {
|
||||
if err = operation(); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var permanent *PermanentError
|
||||
if errors.As(err, &permanent) {
|
||||
return permanent.Err
|
||||
}
|
||||
|
||||
if next = b.NextBackOff(); next == Stop {
|
||||
if cerr := ctx.Err(); cerr != nil {
|
||||
return cerr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if notify != nil {
|
||||
notify(err, next)
|
||||
}
|
||||
|
||||
t.Start(next)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-t.C():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PermanentError signals that the operation should not be retried.
|
||||
type PermanentError struct {
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *PermanentError) Error() string {
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
func (e *PermanentError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
func (e *PermanentError) Is(target error) bool {
|
||||
_, ok := target.(*PermanentError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// Permanent wraps the given err in a *PermanentError.
|
||||
func Permanent(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &PermanentError{
|
||||
Err: err,
|
||||
}
|
||||
}
|
97
vendor/github.com/cenkalti/backoff/v4/ticker.go
generated
vendored
Normal file
97
vendor/github.com/cenkalti/backoff/v4/ticker.go
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
package backoff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
|
||||
//
|
||||
// Ticks will continue to arrive when the previous operation is still running,
|
||||
// so operations that take a while to fail could run in quick succession.
|
||||
type Ticker struct {
|
||||
C <-chan time.Time
|
||||
c chan time.Time
|
||||
b BackOff
|
||||
ctx context.Context
|
||||
timer Timer
|
||||
stop chan struct{}
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
// NewTicker returns a new Ticker containing a channel that will send
|
||||
// the time at times specified by the BackOff argument. Ticker is
|
||||
// guaranteed to tick at least once. The channel is closed when Stop
|
||||
// method is called or BackOff stops. It is not safe to manipulate the
|
||||
// provided backoff policy (notably calling NextBackOff or Reset)
|
||||
// while the ticker is running.
|
||||
func NewTicker(b BackOff) *Ticker {
|
||||
return NewTickerWithTimer(b, &defaultTimer{})
|
||||
}
|
||||
|
||||
// NewTickerWithTimer returns a new Ticker with a custom timer.
|
||||
// A default timer that uses system timer is used when nil is passed.
|
||||
func NewTickerWithTimer(b BackOff, timer Timer) *Ticker {
|
||||
if timer == nil {
|
||||
timer = &defaultTimer{}
|
||||
}
|
||||
c := make(chan time.Time)
|
||||
t := &Ticker{
|
||||
C: c,
|
||||
c: c,
|
||||
b: b,
|
||||
ctx: getContext(b),
|
||||
timer: timer,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
t.b.Reset()
|
||||
go t.run()
|
||||
return t
|
||||
}
|
||||
|
||||
// Stop turns off a ticker. After Stop, no more ticks will be sent.
|
||||
func (t *Ticker) Stop() {
|
||||
t.stopOnce.Do(func() { close(t.stop) })
|
||||
}
|
||||
|
||||
func (t *Ticker) run() {
|
||||
c := t.c
|
||||
defer close(c)
|
||||
|
||||
// Ticker is guaranteed to tick at least once.
|
||||
afterC := t.send(time.Now())
|
||||
|
||||
for {
|
||||
if afterC == nil {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case tick := <-afterC:
|
||||
afterC = t.send(tick)
|
||||
case <-t.stop:
|
||||
t.c = nil // Prevent future ticks from being sent to the channel.
|
||||
return
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Ticker) send(tick time.Time) <-chan time.Time {
|
||||
select {
|
||||
case t.c <- tick:
|
||||
case <-t.stop:
|
||||
return nil
|
||||
}
|
||||
|
||||
next := t.b.NextBackOff()
|
||||
if next == Stop {
|
||||
t.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
t.timer.Start(next)
|
||||
return t.timer.C()
|
||||
}
|
35
vendor/github.com/cenkalti/backoff/v4/timer.go
generated
vendored
Normal file
35
vendor/github.com/cenkalti/backoff/v4/timer.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
type Timer interface {
|
||||
Start(duration time.Duration)
|
||||
Stop()
|
||||
C() <-chan time.Time
|
||||
}
|
||||
|
||||
// defaultTimer implements Timer interface using time.Timer
|
||||
type defaultTimer struct {
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// C returns the timers channel which receives the current time when the timer fires.
|
||||
func (t *defaultTimer) C() <-chan time.Time {
|
||||
return t.timer.C
|
||||
}
|
||||
|
||||
// Start starts the timer to fire after the given duration
|
||||
func (t *defaultTimer) Start(duration time.Duration) {
|
||||
if t.timer == nil {
|
||||
t.timer = time.NewTimer(duration)
|
||||
} else {
|
||||
t.timer.Reset(duration)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop is called when the timer is not used anymore and resources may be freed.
|
||||
func (t *defaultTimer) Stop() {
|
||||
if t.timer != nil {
|
||||
t.timer.Stop()
|
||||
}
|
||||
}
|
38
vendor/github.com/cenkalti/backoff/v4/tries.go
generated
vendored
Normal file
38
vendor/github.com/cenkalti/backoff/v4/tries.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package backoff
|
||||
|
||||
import "time"
|
||||
|
||||
/*
|
||||
WithMaxRetries creates a wrapper around another BackOff, which will
|
||||
return Stop if NextBackOff() has been called too many times since
|
||||
the last time Reset() was called
|
||||
|
||||
Note: Implementation is not thread-safe.
|
||||
*/
|
||||
func WithMaxRetries(b BackOff, max uint64) BackOff {
|
||||
return &backOffTries{delegate: b, maxTries: max}
|
||||
}
|
||||
|
||||
type backOffTries struct {
|
||||
delegate BackOff
|
||||
maxTries uint64
|
||||
numTries uint64
|
||||
}
|
||||
|
||||
func (b *backOffTries) NextBackOff() time.Duration {
|
||||
if b.maxTries == 0 {
|
||||
return Stop
|
||||
}
|
||||
if b.maxTries > 0 {
|
||||
if b.maxTries <= b.numTries {
|
||||
return Stop
|
||||
}
|
||||
b.numTries++
|
||||
}
|
||||
return b.delegate.NextBackOff()
|
||||
}
|
||||
|
||||
func (b *backOffTries) Reset() {
|
||||
b.numTries = 0
|
||||
b.delegate.Reset()
|
||||
}
|
22
vendor/github.com/cenkalti/hub/.gitignore
generated
vendored
Normal file
22
vendor/github.com/cenkalti/hub/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
3
vendor/github.com/cenkalti/hub/.travis.yml
generated
vendored
Normal file
3
vendor/github.com/cenkalti/hub/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
language: go
|
||||
go: 1.2
|
||||
|
20
vendor/github.com/cenkalti/hub/LICENSE
generated
vendored
Normal file
20
vendor/github.com/cenkalti/hub/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Cenk Altı
|
||||
|
||||
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.
|
5
vendor/github.com/cenkalti/hub/README.md
generated
vendored
Normal file
5
vendor/github.com/cenkalti/hub/README.md
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
hub
|
||||
===
|
||||
|
||||
[](https://godoc.org/github.com/cenkalti/hub)
|
||||
[](https://travis-ci.org/cenkalti/hub)
|
82
vendor/github.com/cenkalti/hub/hub.go
generated
vendored
Normal file
82
vendor/github.com/cenkalti/hub/hub.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Package hub provides a simple event dispatcher for publish/subscribe pattern.
|
||||
package hub
|
||||
|
||||
import "sync"
|
||||
|
||||
type Kind int
|
||||
|
||||
// Event is an interface for published events.
|
||||
type Event interface {
|
||||
Kind() Kind
|
||||
}
|
||||
|
||||
// Hub is an event dispatcher, publishes events to the subscribers
|
||||
// which are subscribed for a specific event type.
|
||||
// Optimized for publish calls.
|
||||
// The handlers may be called in order different than they are registered.
|
||||
type Hub struct {
|
||||
subscribers map[Kind][]handler
|
||||
m sync.RWMutex
|
||||
seq uint64
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
f func(Event)
|
||||
id uint64
|
||||
}
|
||||
|
||||
// Subscribe registers f for the event of a specific kind.
|
||||
func (h *Hub) Subscribe(kind Kind, f func(Event)) (cancel func()) {
|
||||
var cancelled bool
|
||||
h.m.Lock()
|
||||
h.seq++
|
||||
id := h.seq
|
||||
if h.subscribers == nil {
|
||||
h.subscribers = make(map[Kind][]handler)
|
||||
}
|
||||
h.subscribers[kind] = append(h.subscribers[kind], handler{id: id, f: f})
|
||||
h.m.Unlock()
|
||||
return func() {
|
||||
h.m.Lock()
|
||||
if cancelled {
|
||||
h.m.Unlock()
|
||||
return
|
||||
}
|
||||
cancelled = true
|
||||
a := h.subscribers[kind]
|
||||
for i, f := range a {
|
||||
if f.id == id {
|
||||
a[i], h.subscribers[kind] = a[len(a)-1], a[:len(a)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(a) == 0 {
|
||||
delete(h.subscribers, kind)
|
||||
}
|
||||
h.m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Publish an event to the subscribers.
|
||||
func (h *Hub) Publish(e Event) {
|
||||
h.m.RLock()
|
||||
if handlers, ok := h.subscribers[e.Kind()]; ok {
|
||||
for _, h := range handlers {
|
||||
h.f(e)
|
||||
}
|
||||
}
|
||||
h.m.RUnlock()
|
||||
}
|
||||
|
||||
// DefaultHub is the default Hub used by Publish and Subscribe.
|
||||
var DefaultHub Hub
|
||||
|
||||
// Subscribe registers f for the event of a specific kind in the DefaultHub.
|
||||
func Subscribe(kind Kind, f func(Event)) (cancel func()) {
|
||||
return DefaultHub.Subscribe(kind, f)
|
||||
}
|
||||
|
||||
// Publish an event to the subscribers in DefaultHub.
|
||||
func Publish(e Event) {
|
||||
DefaultHub.Publish(e)
|
||||
}
|
23
vendor/github.com/cenkalti/rpc2/.gitignore
generated
vendored
Normal file
23
vendor/github.com/cenkalti/rpc2/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
9
vendor/github.com/cenkalti/rpc2/.travis.yml
generated
vendored
Normal file
9
vendor/github.com/cenkalti/rpc2/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.15
|
||||
- tip
|
||||
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
21
vendor/github.com/cenkalti/rpc2/LICENSE
generated
vendored
Normal file
21
vendor/github.com/cenkalti/rpc2/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Cenk Altı
|
||||
|
||||
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.
|
82
vendor/github.com/cenkalti/rpc2/README.md
generated
vendored
Normal file
82
vendor/github.com/cenkalti/rpc2/README.md
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
rpc2
|
||||
====
|
||||
|
||||
[](https://godoc.org/github.com/cenkalti/rpc2)
|
||||
[](https://travis-ci.org/cenkalti/rpc2)
|
||||
|
||||
rpc2 is a fork of net/rpc package in the standard library.
|
||||
The main goal is to add bi-directional support to calls.
|
||||
That means server can call the methods of client.
|
||||
This is not possible with net/rpc package.
|
||||
In order to do this it adds a `*Client` argument to method signatures.
|
||||
|
||||
Install
|
||||
--------
|
||||
|
||||
go get github.com/cenkalti/rpc2
|
||||
|
||||
Example server
|
||||
---------------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/cenkalti/rpc2"
|
||||
)
|
||||
|
||||
type Args struct{ A, B int }
|
||||
type Reply int
|
||||
|
||||
func main() {
|
||||
srv := rpc2.NewServer()
|
||||
srv.Handle("add", func(client *rpc2.Client, args *Args, reply *Reply) error {
|
||||
|
||||
// Reversed call (server to client)
|
||||
var rep Reply
|
||||
client.Call("mult", Args{2, 3}, &rep)
|
||||
fmt.Println("mult result:", rep)
|
||||
|
||||
*reply = Reply(args.A + args.B)
|
||||
return nil
|
||||
})
|
||||
|
||||
lis, _ := net.Listen("tcp", "127.0.0.1:5000")
|
||||
srv.Accept(lis)
|
||||
}
|
||||
```
|
||||
|
||||
Example Client
|
||||
---------------
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/cenkalti/rpc2"
|
||||
)
|
||||
|
||||
type Args struct{ A, B int }
|
||||
type Reply int
|
||||
|
||||
func main() {
|
||||
conn, _ := net.Dial("tcp", "127.0.0.1:5000")
|
||||
|
||||
clt := rpc2.NewClient(conn)
|
||||
clt.Handle("mult", func(client *rpc2.Client, args *Args, reply *Reply) error {
|
||||
*reply = Reply(args.A * args.B)
|
||||
return nil
|
||||
})
|
||||
go clt.Run()
|
||||
|
||||
var rep Reply
|
||||
clt.Call("add", Args{1, 2}, &rep)
|
||||
fmt.Println("add result:", rep)
|
||||
}
|
||||
```
|
364
vendor/github.com/cenkalti/rpc2/client.go
generated
vendored
Normal file
364
vendor/github.com/cenkalti/rpc2/client.go
generated
vendored
Normal file
@@ -0,0 +1,364 @@
|
||||
// Package rpc2 provides bi-directional RPC client and server similar to net/rpc.
|
||||
package rpc2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Client represents an RPC Client.
|
||||
// There may be multiple outstanding Calls associated
|
||||
// with a single Client, and a Client may be used by
|
||||
// multiple goroutines simultaneously.
|
||||
type Client struct {
|
||||
mutex sync.Mutex // protects pending, seq, request
|
||||
sending sync.Mutex
|
||||
request Request // temp area used in send()
|
||||
seq uint64
|
||||
pending map[uint64]*Call
|
||||
closing bool
|
||||
shutdown bool
|
||||
server bool
|
||||
codec Codec
|
||||
handlers map[string]*handler
|
||||
disconnect chan struct{}
|
||||
State *State // additional information to associate with client
|
||||
blocking bool // whether to block request handling
|
||||
}
|
||||
|
||||
// NewClient returns a new Client to handle requests to the
|
||||
// set of services at the other end of the connection.
|
||||
// It adds a buffer to the write side of the connection so
|
||||
// the header and payload are sent as a unit.
|
||||
func NewClient(conn io.ReadWriteCloser) *Client {
|
||||
return NewClientWithCodec(NewGobCodec(conn))
|
||||
}
|
||||
|
||||
// NewClientWithCodec is like NewClient but uses the specified
|
||||
// codec to encode requests and decode responses.
|
||||
func NewClientWithCodec(codec Codec) *Client {
|
||||
return &Client{
|
||||
codec: codec,
|
||||
pending: make(map[uint64]*Call),
|
||||
handlers: make(map[string]*handler),
|
||||
disconnect: make(chan struct{}),
|
||||
seq: 1, // 0 means notification.
|
||||
}
|
||||
}
|
||||
|
||||
// SetBlocking puts the client in blocking mode.
|
||||
// In blocking mode, received requests are processes synchronously.
|
||||
// If you have methods that may take a long time, other subsequent requests may time out.
|
||||
func (c *Client) SetBlocking(blocking bool) {
|
||||
c.blocking = blocking
|
||||
}
|
||||
|
||||
// Run the client's read loop.
|
||||
// You must run this method before calling any methods on the server.
|
||||
func (c *Client) Run() {
|
||||
c.readLoop()
|
||||
}
|
||||
|
||||
// DisconnectNotify returns a channel that is closed
|
||||
// when the client connection has gone away.
|
||||
func (c *Client) DisconnectNotify() chan struct{} {
|
||||
return c.disconnect
|
||||
}
|
||||
|
||||
// Handle registers the handler function for the given method. If a handler already exists for method, Handle panics.
|
||||
func (c *Client) Handle(method string, handlerFunc interface{}) {
|
||||
addHandler(c.handlers, method, handlerFunc)
|
||||
}
|
||||
|
||||
// readLoop reads messages from codec.
|
||||
// It reads a reqeust or a response to the previous request.
|
||||
// If the message is request, calls the handler function.
|
||||
// If the message is response, sends the reply to the associated call.
|
||||
func (c *Client) readLoop() {
|
||||
var err error
|
||||
var req Request
|
||||
var resp Response
|
||||
for err == nil {
|
||||
req = Request{}
|
||||
resp = Response{}
|
||||
if err = c.codec.ReadHeader(&req, &resp); err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
if req.Method != "" {
|
||||
// request comes to server
|
||||
if err = c.readRequest(&req); err != nil {
|
||||
debugln("rpc2: error reading request:", err.Error())
|
||||
}
|
||||
} else {
|
||||
// response comes to client
|
||||
if err = c.readResponse(&resp); err != nil {
|
||||
debugln("rpc2: error reading response:", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
// Terminate pending calls.
|
||||
c.sending.Lock()
|
||||
c.mutex.Lock()
|
||||
c.shutdown = true
|
||||
closing := c.closing
|
||||
if err == io.EOF {
|
||||
if closing {
|
||||
err = ErrShutdown
|
||||
} else {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
for _, call := range c.pending {
|
||||
call.Error = err
|
||||
call.done()
|
||||
}
|
||||
c.mutex.Unlock()
|
||||
c.sending.Unlock()
|
||||
if err != io.EOF && !closing && !c.server {
|
||||
debugln("rpc2: client protocol error:", err)
|
||||
}
|
||||
close(c.disconnect)
|
||||
if !closing {
|
||||
c.codec.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) handleRequest(req Request, method *handler, argv reflect.Value) {
|
||||
// Invoke the method, providing a new value for the reply.
|
||||
replyv := reflect.New(method.replyType.Elem())
|
||||
|
||||
returnValues := method.fn.Call([]reflect.Value{reflect.ValueOf(c), argv, replyv})
|
||||
|
||||
// Do not send response if request is a notification.
|
||||
if req.Seq == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// The return value for the method is an error.
|
||||
errInter := returnValues[0].Interface()
|
||||
errmsg := ""
|
||||
if errInter != nil {
|
||||
errmsg = errInter.(error).Error()
|
||||
}
|
||||
resp := &Response{
|
||||
Seq: req.Seq,
|
||||
Error: errmsg,
|
||||
}
|
||||
if err := c.codec.WriteResponse(resp, replyv.Interface()); err != nil {
|
||||
debugln("rpc2: error writing response:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) readRequest(req *Request) error {
|
||||
method, ok := c.handlers[req.Method]
|
||||
if !ok {
|
||||
resp := &Response{
|
||||
Seq: req.Seq,
|
||||
Error: "rpc2: can't find method " + req.Method,
|
||||
}
|
||||
return c.codec.WriteResponse(resp, resp)
|
||||
}
|
||||
|
||||
// Decode the argument value.
|
||||
var argv reflect.Value
|
||||
argIsValue := false // if true, need to indirect before calling.
|
||||
if method.argType.Kind() == reflect.Ptr {
|
||||
argv = reflect.New(method.argType.Elem())
|
||||
} else {
|
||||
argv = reflect.New(method.argType)
|
||||
argIsValue = true
|
||||
}
|
||||
// argv guaranteed to be a pointer now.
|
||||
if err := c.codec.ReadRequestBody(argv.Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
if argIsValue {
|
||||
argv = argv.Elem()
|
||||
}
|
||||
|
||||
if c.blocking {
|
||||
c.handleRequest(*req, method, argv)
|
||||
} else {
|
||||
go c.handleRequest(*req, method, argv)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) readResponse(resp *Response) error {
|
||||
seq := resp.Seq
|
||||
c.mutex.Lock()
|
||||
call := c.pending[seq]
|
||||
delete(c.pending, seq)
|
||||
c.mutex.Unlock()
|
||||
|
||||
var err error
|
||||
switch {
|
||||
case call == nil:
|
||||
// We've got no pending call. That usually means that
|
||||
// WriteRequest partially failed, and call was already
|
||||
// removed; response is a server telling us about an
|
||||
// error reading request body. We should still attempt
|
||||
// to read error body, but there's no one to give it to.
|
||||
err = c.codec.ReadResponseBody(nil)
|
||||
if err != nil {
|
||||
err = errors.New("reading error body: " + err.Error())
|
||||
}
|
||||
case resp.Error != "":
|
||||
// We've got an error response. Give this to the request;
|
||||
// any subsequent requests will get the ReadResponseBody
|
||||
// error if there is one.
|
||||
call.Error = ServerError(resp.Error)
|
||||
err = c.codec.ReadResponseBody(nil)
|
||||
if err != nil {
|
||||
err = errors.New("reading error body: " + err.Error())
|
||||
}
|
||||
call.done()
|
||||
default:
|
||||
err = c.codec.ReadResponseBody(call.Reply)
|
||||
if err != nil {
|
||||
call.Error = errors.New("reading body " + err.Error())
|
||||
}
|
||||
call.done()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Close waits for active calls to finish and closes the codec.
|
||||
func (c *Client) Close() error {
|
||||
c.mutex.Lock()
|
||||
if c.shutdown || c.closing {
|
||||
c.mutex.Unlock()
|
||||
return ErrShutdown
|
||||
}
|
||||
c.closing = true
|
||||
c.mutex.Unlock()
|
||||
return c.codec.Close()
|
||||
}
|
||||
|
||||
// Go invokes the function asynchronously. It returns the Call structure representing
|
||||
// the invocation. The done channel will signal when the call is complete by returning
|
||||
// the same Call object. If done is nil, Go will allocate a new channel.
|
||||
// If non-nil, done must be buffered or Go will deliberately crash.
|
||||
func (c *Client) Go(method string, args interface{}, reply interface{}, done chan *Call) *Call {
|
||||
call := new(Call)
|
||||
call.Method = method
|
||||
call.Args = args
|
||||
call.Reply = reply
|
||||
if done == nil {
|
||||
done = make(chan *Call, 10) // buffered.
|
||||
} else {
|
||||
// If caller passes done != nil, it must arrange that
|
||||
// done has enough buffer for the number of simultaneous
|
||||
// RPCs that will be using that channel. If the channel
|
||||
// is totally unbuffered, it's best not to run at all.
|
||||
if cap(done) == 0 {
|
||||
log.Panic("rpc2: done channel is unbuffered")
|
||||
}
|
||||
}
|
||||
call.Done = done
|
||||
c.send(call)
|
||||
return call
|
||||
}
|
||||
|
||||
// CallWithContext invokes the named function, waits for it to complete, and
|
||||
// returns its error status, or an error from Context timeout.
|
||||
func (c *Client) CallWithContext(ctx context.Context, method string, args interface{}, reply interface{}) error {
|
||||
call := c.Go(method, args, reply, make(chan *Call, 1))
|
||||
select {
|
||||
case <-call.Done:
|
||||
return call.Error
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Call invokes the named function, waits for it to complete, and returns its error status.
|
||||
func (c *Client) Call(method string, args interface{}, reply interface{}) error {
|
||||
return c.CallWithContext(context.Background(), method, args, reply)
|
||||
}
|
||||
|
||||
func (call *Call) done() {
|
||||
select {
|
||||
case call.Done <- call:
|
||||
// ok
|
||||
default:
|
||||
// We don't want to block here. It is the caller's responsibility to make
|
||||
// sure the channel has enough buffer space. See comment in Go().
|
||||
debugln("rpc2: discarding Call reply due to insufficient Done chan capacity")
|
||||
}
|
||||
}
|
||||
|
||||
// ServerError represents an error that has been returned from
|
||||
// the remote side of the RPC connection.
|
||||
type ServerError string
|
||||
|
||||
func (e ServerError) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// ErrShutdown is returned when the connection is closing or closed.
|
||||
var ErrShutdown = errors.New("connection is shut down")
|
||||
|
||||
// Call represents an active RPC.
|
||||
type Call struct {
|
||||
Method string // The name of the service and method to call.
|
||||
Args interface{} // The argument to the function (*struct).
|
||||
Reply interface{} // The reply from the function (*struct).
|
||||
Error error // After completion, the error status.
|
||||
Done chan *Call // Strobes when call is complete.
|
||||
}
|
||||
|
||||
func (c *Client) send(call *Call) {
|
||||
c.sending.Lock()
|
||||
defer c.sending.Unlock()
|
||||
|
||||
// Register this call.
|
||||
c.mutex.Lock()
|
||||
if c.shutdown || c.closing {
|
||||
call.Error = ErrShutdown
|
||||
c.mutex.Unlock()
|
||||
call.done()
|
||||
return
|
||||
}
|
||||
seq := c.seq
|
||||
c.seq++
|
||||
c.pending[seq] = call
|
||||
c.mutex.Unlock()
|
||||
|
||||
// Encode and send the request.
|
||||
c.request.Seq = seq
|
||||
c.request.Method = call.Method
|
||||
err := c.codec.WriteRequest(&c.request, call.Args)
|
||||
if err != nil {
|
||||
c.mutex.Lock()
|
||||
call = c.pending[seq]
|
||||
delete(c.pending, seq)
|
||||
c.mutex.Unlock()
|
||||
if call != nil {
|
||||
call.Error = err
|
||||
call.done()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify sends a request to the receiver but does not wait for a return value.
|
||||
func (c *Client) Notify(method string, args interface{}) error {
|
||||
c.sending.Lock()
|
||||
defer c.sending.Unlock()
|
||||
|
||||
if c.shutdown || c.closing {
|
||||
return ErrShutdown
|
||||
}
|
||||
|
||||
c.request.Seq = 0
|
||||
c.request.Method = method
|
||||
return c.codec.WriteRequest(&c.request, args)
|
||||
}
|
125
vendor/github.com/cenkalti/rpc2/codec.go
generated
vendored
Normal file
125
vendor/github.com/cenkalti/rpc2/codec.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package rpc2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/gob"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// A Codec implements reading and writing of RPC requests and responses.
|
||||
// The client calls ReadHeader to read a message header.
|
||||
// The implementation must populate either Request or Response argument.
|
||||
// Depending on which argument is populated, ReadRequestBody or
|
||||
// ReadResponseBody is called right after ReadHeader.
|
||||
// ReadRequestBody and ReadResponseBody may be called with a nil
|
||||
// argument to force the body to be read and then discarded.
|
||||
type Codec interface {
|
||||
// ReadHeader must read a message and populate either the request
|
||||
// or the response by inspecting the incoming message.
|
||||
ReadHeader(*Request, *Response) error
|
||||
|
||||
// ReadRequestBody into args argument of handler function.
|
||||
ReadRequestBody(interface{}) error
|
||||
|
||||
// ReadResponseBody into reply argument of handler function.
|
||||
ReadResponseBody(interface{}) error
|
||||
|
||||
// WriteRequest must be safe for concurrent use by multiple goroutines.
|
||||
WriteRequest(*Request, interface{}) error
|
||||
|
||||
// WriteResponse must be safe for concurrent use by multiple goroutines.
|
||||
WriteResponse(*Response, interface{}) error
|
||||
|
||||
// Close is called when client/server finished with the connection.
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Request is a header written before every RPC call.
|
||||
type Request struct {
|
||||
Seq uint64 // sequence number chosen by client
|
||||
Method string
|
||||
}
|
||||
|
||||
// Response is a header written before every RPC return.
|
||||
type Response struct {
|
||||
Seq uint64 // echoes that of the request
|
||||
Error string // error, if any.
|
||||
}
|
||||
|
||||
type gobCodec struct {
|
||||
rwc io.ReadWriteCloser
|
||||
dec *gob.Decoder
|
||||
enc *gob.Encoder
|
||||
encBuf *bufio.Writer
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
type message struct {
|
||||
Seq uint64
|
||||
Method string
|
||||
Error string
|
||||
}
|
||||
|
||||
// NewGobCodec returns a new rpc2.Codec using gob encoding/decoding on conn.
|
||||
func NewGobCodec(conn io.ReadWriteCloser) Codec {
|
||||
buf := bufio.NewWriter(conn)
|
||||
return &gobCodec{
|
||||
rwc: conn,
|
||||
dec: gob.NewDecoder(conn),
|
||||
enc: gob.NewEncoder(buf),
|
||||
encBuf: buf,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *gobCodec) ReadHeader(req *Request, resp *Response) error {
|
||||
var msg message
|
||||
if err := c.dec.Decode(&msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if msg.Method != "" {
|
||||
req.Seq = msg.Seq
|
||||
req.Method = msg.Method
|
||||
} else {
|
||||
resp.Seq = msg.Seq
|
||||
resp.Error = msg.Error
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *gobCodec) ReadRequestBody(body interface{}) error {
|
||||
return c.dec.Decode(body)
|
||||
}
|
||||
|
||||
func (c *gobCodec) ReadResponseBody(body interface{}) error {
|
||||
return c.dec.Decode(body)
|
||||
}
|
||||
|
||||
func (c *gobCodec) WriteRequest(r *Request, body interface{}) (err error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
if err = c.enc.Encode(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = c.enc.Encode(body); err != nil {
|
||||
return
|
||||
}
|
||||
return c.encBuf.Flush()
|
||||
}
|
||||
|
||||
func (c *gobCodec) WriteResponse(r *Response, body interface{}) (err error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
if err = c.enc.Encode(r); err != nil {
|
||||
return
|
||||
}
|
||||
if err = c.enc.Encode(body); err != nil {
|
||||
return
|
||||
}
|
||||
return c.encBuf.Flush()
|
||||
}
|
||||
|
||||
func (c *gobCodec) Close() error {
|
||||
return c.rwc.Close()
|
||||
}
|
12
vendor/github.com/cenkalti/rpc2/debug.go
generated
vendored
Normal file
12
vendor/github.com/cenkalti/rpc2/debug.go
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
package rpc2
|
||||
|
||||
import "log"
|
||||
|
||||
// DebugLog controls the printing of internal and I/O errors.
|
||||
var DebugLog = false
|
||||
|
||||
func debugln(v ...interface{}) {
|
||||
if DebugLog {
|
||||
log.Println(v...)
|
||||
}
|
||||
}
|
226
vendor/github.com/cenkalti/rpc2/jsonrpc/jsonrpc.go
generated
vendored
Normal file
226
vendor/github.com/cenkalti/rpc2/jsonrpc/jsonrpc.go
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
// Package jsonrpc implements a JSON-RPC ClientCodec and ServerCodec for the rpc2 package.
|
||||
//
|
||||
// Beside struct types, JSONCodec allows using positional arguments.
|
||||
// Use []interface{} as the type of argument when sending and receiving methods.
|
||||
//
|
||||
// Positional arguments example:
|
||||
// server.Handle("add", func(client *rpc2.Client, args []interface{}, result *float64) error {
|
||||
// *result = args[0].(float64) + args[1].(float64)
|
||||
// return nil
|
||||
// })
|
||||
//
|
||||
// var result float64
|
||||
// client.Call("add", []interface{}{1, 2}, &result)
|
||||
//
|
||||
package jsonrpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/cenkalti/rpc2"
|
||||
)
|
||||
|
||||
type jsonCodec struct {
|
||||
dec *json.Decoder // for reading JSON values
|
||||
enc *json.Encoder // for writing JSON values
|
||||
c io.Closer
|
||||
|
||||
// temporary work space
|
||||
msg message
|
||||
serverRequest serverRequest
|
||||
clientResponse clientResponse
|
||||
|
||||
// JSON-RPC clients can use arbitrary json values as request IDs.
|
||||
// Package rpc expects uint64 request IDs.
|
||||
// We assign uint64 sequence numbers to incoming requests
|
||||
// but save the original request ID in the pending map.
|
||||
// When rpc responds, we use the sequence number in
|
||||
// the response to find the original request ID.
|
||||
mutex sync.Mutex // protects seq, pending
|
||||
pending map[uint64]*json.RawMessage
|
||||
seq uint64
|
||||
}
|
||||
|
||||
// NewJSONCodec returns a new rpc2.Codec using JSON-RPC on conn.
|
||||
func NewJSONCodec(conn io.ReadWriteCloser) rpc2.Codec {
|
||||
return &jsonCodec{
|
||||
dec: json.NewDecoder(conn),
|
||||
enc: json.NewEncoder(conn),
|
||||
c: conn,
|
||||
pending: make(map[uint64]*json.RawMessage),
|
||||
}
|
||||
}
|
||||
|
||||
// serverRequest and clientResponse combined
|
||||
type message struct {
|
||||
Method string `json:"method"`
|
||||
Params *json.RawMessage `json:"params"`
|
||||
Id *json.RawMessage `json:"id"`
|
||||
Result *json.RawMessage `json:"result"`
|
||||
Error interface{} `json:"error"`
|
||||
}
|
||||
|
||||
// Unmarshal to
|
||||
type serverRequest struct {
|
||||
Method string `json:"method"`
|
||||
Params *json.RawMessage `json:"params"`
|
||||
Id *json.RawMessage `json:"id"`
|
||||
}
|
||||
type clientResponse struct {
|
||||
Id uint64 `json:"id"`
|
||||
Result *json.RawMessage `json:"result"`
|
||||
Error interface{} `json:"error"`
|
||||
}
|
||||
|
||||
// to Marshal
|
||||
type serverResponse struct {
|
||||
Id *json.RawMessage `json:"id"`
|
||||
Result interface{} `json:"result"`
|
||||
Error interface{} `json:"error"`
|
||||
}
|
||||
type clientRequest struct {
|
||||
Method string `json:"method"`
|
||||
Params interface{} `json:"params"`
|
||||
Id *uint64 `json:"id"`
|
||||
}
|
||||
|
||||
func (c *jsonCodec) ReadHeader(req *rpc2.Request, resp *rpc2.Response) error {
|
||||
c.msg = message{}
|
||||
if err := c.dec.Decode(&c.msg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.msg.Method != "" {
|
||||
// request comes to server
|
||||
c.serverRequest.Id = c.msg.Id
|
||||
c.serverRequest.Method = c.msg.Method
|
||||
c.serverRequest.Params = c.msg.Params
|
||||
|
||||
req.Method = c.serverRequest.Method
|
||||
|
||||
// JSON request id can be any JSON value;
|
||||
// RPC package expects uint64. Translate to
|
||||
// internal uint64 and save JSON on the side.
|
||||
if c.serverRequest.Id == nil {
|
||||
// Notification
|
||||
} else {
|
||||
c.mutex.Lock()
|
||||
c.seq++
|
||||
c.pending[c.seq] = c.serverRequest.Id
|
||||
c.serverRequest.Id = nil
|
||||
req.Seq = c.seq
|
||||
c.mutex.Unlock()
|
||||
}
|
||||
} else {
|
||||
// response comes to client
|
||||
err := json.Unmarshal(*c.msg.Id, &c.clientResponse.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.clientResponse.Result = c.msg.Result
|
||||
c.clientResponse.Error = c.msg.Error
|
||||
|
||||
resp.Error = ""
|
||||
resp.Seq = c.clientResponse.Id
|
||||
if c.clientResponse.Error != nil || c.clientResponse.Result == nil {
|
||||
x, ok := c.clientResponse.Error.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid error %v", c.clientResponse.Error)
|
||||
}
|
||||
if x == "" {
|
||||
x = "unspecified error"
|
||||
}
|
||||
resp.Error = x
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var errMissingParams = errors.New("jsonrpc: request body missing params")
|
||||
|
||||
func (c *jsonCodec) ReadRequestBody(x interface{}) error {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
if c.serverRequest.Params == nil {
|
||||
return errMissingParams
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
// Check if x points to a slice of any kind
|
||||
rt := reflect.TypeOf(x)
|
||||
if rt.Kind() == reflect.Ptr && rt.Elem().Kind() == reflect.Slice {
|
||||
// If it's a slice, unmarshal as is
|
||||
err = json.Unmarshal(*c.serverRequest.Params, x)
|
||||
} else {
|
||||
// Anything else unmarshal into a slice containing x
|
||||
params := &[]interface{}{x}
|
||||
err = json.Unmarshal(*c.serverRequest.Params, params)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *jsonCodec) ReadResponseBody(x interface{}) error {
|
||||
if x == nil {
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(*c.clientResponse.Result, x)
|
||||
}
|
||||
|
||||
func (c *jsonCodec) WriteRequest(r *rpc2.Request, param interface{}) error {
|
||||
req := &clientRequest{Method: r.Method}
|
||||
|
||||
// Check if param is a slice of any kind
|
||||
if param != nil && reflect.TypeOf(param).Kind() == reflect.Slice {
|
||||
// If it's a slice, leave as is
|
||||
req.Params = param
|
||||
} else {
|
||||
// Put anything else into a slice
|
||||
req.Params = []interface{}{param}
|
||||
}
|
||||
|
||||
if r.Seq == 0 {
|
||||
// Notification
|
||||
req.Id = nil
|
||||
} else {
|
||||
seq := r.Seq
|
||||
req.Id = &seq
|
||||
}
|
||||
return c.enc.Encode(req)
|
||||
}
|
||||
|
||||
var null = json.RawMessage([]byte("null"))
|
||||
|
||||
func (c *jsonCodec) WriteResponse(r *rpc2.Response, x interface{}) error {
|
||||
c.mutex.Lock()
|
||||
b, ok := c.pending[r.Seq]
|
||||
if !ok {
|
||||
c.mutex.Unlock()
|
||||
return errors.New("invalid sequence number in response")
|
||||
}
|
||||
delete(c.pending, r.Seq)
|
||||
c.mutex.Unlock()
|
||||
|
||||
if b == nil {
|
||||
// Invalid request so no id. Use JSON null.
|
||||
b = &null
|
||||
}
|
||||
resp := serverResponse{Id: b}
|
||||
if r.Error == "" {
|
||||
resp.Result = x
|
||||
} else {
|
||||
resp.Error = r.Error
|
||||
}
|
||||
return c.enc.Encode(resp)
|
||||
}
|
||||
|
||||
func (c *jsonCodec) Close() error {
|
||||
return c.c.Close()
|
||||
}
|
181
vendor/github.com/cenkalti/rpc2/server.go
generated
vendored
Normal file
181
vendor/github.com/cenkalti/rpc2/server.go
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
||||
package rpc2
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/cenkalti/hub"
|
||||
)
|
||||
|
||||
// Precompute the reflect type for error. Can't use error directly
|
||||
// because Typeof takes an empty interface value. This is annoying.
|
||||
var typeOfError = reflect.TypeOf((*error)(nil)).Elem()
|
||||
var typeOfClient = reflect.TypeOf((*Client)(nil))
|
||||
|
||||
const (
|
||||
clientConnected hub.Kind = iota
|
||||
clientDisconnected
|
||||
)
|
||||
|
||||
// Server responds to RPC requests made by Client.
|
||||
type Server struct {
|
||||
handlers map[string]*handler
|
||||
eventHub *hub.Hub
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
fn reflect.Value
|
||||
argType reflect.Type
|
||||
replyType reflect.Type
|
||||
}
|
||||
|
||||
type connectionEvent struct {
|
||||
Client *Client
|
||||
}
|
||||
|
||||
type disconnectionEvent struct {
|
||||
Client *Client
|
||||
}
|
||||
|
||||
func (connectionEvent) Kind() hub.Kind { return clientConnected }
|
||||
func (disconnectionEvent) Kind() hub.Kind { return clientDisconnected }
|
||||
|
||||
// NewServer returns a new Server.
|
||||
func NewServer() *Server {
|
||||
return &Server{
|
||||
handlers: make(map[string]*handler),
|
||||
eventHub: &hub.Hub{},
|
||||
}
|
||||
}
|
||||
|
||||
// Handle registers the handler function for the given method. If a handler already exists for method, Handle panics.
|
||||
func (s *Server) Handle(method string, handlerFunc interface{}) {
|
||||
addHandler(s.handlers, method, handlerFunc)
|
||||
}
|
||||
|
||||
func addHandler(handlers map[string]*handler, mname string, handlerFunc interface{}) {
|
||||
if _, ok := handlers[mname]; ok {
|
||||
panic("rpc2: multiple registrations for " + mname)
|
||||
}
|
||||
|
||||
method := reflect.ValueOf(handlerFunc)
|
||||
mtype := method.Type()
|
||||
// Method needs three ins: *client, *args, *reply.
|
||||
if mtype.NumIn() != 3 {
|
||||
log.Panicln("method", mname, "has wrong number of ins:", mtype.NumIn())
|
||||
}
|
||||
// First arg must be a pointer to rpc2.Client.
|
||||
clientType := mtype.In(0)
|
||||
if clientType.Kind() != reflect.Ptr {
|
||||
log.Panicln("method", mname, "client type not a pointer:", clientType)
|
||||
}
|
||||
if clientType != typeOfClient {
|
||||
log.Panicln("method", mname, "first argument", clientType.String(), "not *rpc2.Client")
|
||||
}
|
||||
// Second arg need not be a pointer.
|
||||
argType := mtype.In(1)
|
||||
if !isExportedOrBuiltinType(argType) {
|
||||
log.Panicln(mname, "argument type not exported:", argType)
|
||||
}
|
||||
// Third arg must be a pointer.
|
||||
replyType := mtype.In(2)
|
||||
if replyType.Kind() != reflect.Ptr {
|
||||
log.Panicln("method", mname, "reply type not a pointer:", replyType)
|
||||
}
|
||||
// Reply type must be exported.
|
||||
if !isExportedOrBuiltinType(replyType) {
|
||||
log.Panicln("method", mname, "reply type not exported:", replyType)
|
||||
}
|
||||
// Method needs one out.
|
||||
if mtype.NumOut() != 1 {
|
||||
log.Panicln("method", mname, "has wrong number of outs:", mtype.NumOut())
|
||||
}
|
||||
// The return type of the method must be error.
|
||||
if returnType := mtype.Out(0); returnType != typeOfError {
|
||||
log.Panicln("method", mname, "returns", returnType.String(), "not error")
|
||||
}
|
||||
handlers[mname] = &handler{
|
||||
fn: method,
|
||||
argType: argType,
|
||||
replyType: replyType,
|
||||
}
|
||||
}
|
||||
|
||||
// Is this type exported or a builtin?
|
||||
func isExportedOrBuiltinType(t reflect.Type) bool {
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
// PkgPath will be non-empty even for an exported type,
|
||||
// so we need to check the type name as well.
|
||||
return isExported(t.Name()) || t.PkgPath() == ""
|
||||
}
|
||||
|
||||
// Is this an exported - upper case - name?
|
||||
func isExported(name string) bool {
|
||||
rune, _ := utf8.DecodeRuneInString(name)
|
||||
return unicode.IsUpper(rune)
|
||||
}
|
||||
|
||||
// OnConnect registers a function to run when a client connects.
|
||||
func (s *Server) OnConnect(f func(*Client)) {
|
||||
s.eventHub.Subscribe(clientConnected, func(e hub.Event) {
|
||||
go f(e.(connectionEvent).Client)
|
||||
})
|
||||
}
|
||||
|
||||
// OnDisconnect registers a function to run when a client disconnects.
|
||||
func (s *Server) OnDisconnect(f func(*Client)) {
|
||||
s.eventHub.Subscribe(clientDisconnected, func(e hub.Event) {
|
||||
go f(e.(disconnectionEvent).Client)
|
||||
})
|
||||
}
|
||||
|
||||
// Accept accepts connections on the listener and serves requests
|
||||
// for each incoming connection. Accept blocks; the caller typically
|
||||
// invokes it in a go statement.
|
||||
func (s *Server) Accept(lis net.Listener) {
|
||||
for {
|
||||
conn, err := lis.Accept()
|
||||
if err != nil {
|
||||
log.Print("rpc.Serve: accept:", err.Error())
|
||||
return
|
||||
}
|
||||
go s.ServeConn(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeConn runs the server on a single connection.
|
||||
// ServeConn blocks, serving the connection until the client hangs up.
|
||||
// The caller typically invokes ServeConn in a go statement.
|
||||
// ServeConn uses the gob wire format (see package gob) on the
|
||||
// connection. To use an alternate codec, use ServeCodec.
|
||||
func (s *Server) ServeConn(conn io.ReadWriteCloser) {
|
||||
s.ServeCodec(NewGobCodec(conn))
|
||||
}
|
||||
|
||||
// ServeCodec is like ServeConn but uses the specified codec to
|
||||
// decode requests and encode responses.
|
||||
func (s *Server) ServeCodec(codec Codec) {
|
||||
s.ServeCodecWithState(codec, NewState())
|
||||
}
|
||||
|
||||
// ServeCodecWithState is like ServeCodec but also gives the ability to
|
||||
// associate a state variable with the client that persists across RPC calls.
|
||||
func (s *Server) ServeCodecWithState(codec Codec, state *State) {
|
||||
defer codec.Close()
|
||||
|
||||
// Client also handles the incoming connections.
|
||||
c := NewClientWithCodec(codec)
|
||||
c.server = true
|
||||
c.handlers = s.handlers
|
||||
c.State = state
|
||||
|
||||
s.eventHub.Publish(connectionEvent{c})
|
||||
c.Run()
|
||||
s.eventHub.Publish(disconnectionEvent{c})
|
||||
}
|
25
vendor/github.com/cenkalti/rpc2/state.go
generated
vendored
Normal file
25
vendor/github.com/cenkalti/rpc2/state.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package rpc2
|
||||
|
||||
import "sync"
|
||||
|
||||
type State struct {
|
||||
store map[string]interface{}
|
||||
m sync.RWMutex
|
||||
}
|
||||
|
||||
func NewState() *State {
|
||||
return &State{store: make(map[string]interface{})}
|
||||
}
|
||||
|
||||
func (s *State) Get(key string) (value interface{}, ok bool) {
|
||||
s.m.RLock()
|
||||
value, ok = s.store[key]
|
||||
s.m.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *State) Set(key string, value interface{}) {
|
||||
s.m.Lock()
|
||||
s.store[key] = value
|
||||
s.m.Unlock()
|
||||
}
|
8
vendor/github.com/cespare/xxhash/v2/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/cespare/xxhash/v2/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- "1.x"
|
||||
- master
|
||||
env:
|
||||
- TAGS=""
|
||||
- TAGS="-tags purego"
|
||||
script: go test $TAGS -v ./...
|
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
Normal file
22
vendor/github.com/cespare/xxhash/v2/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
Copyright (c) 2016 Caleb Spare
|
||||
|
||||
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.
|
67
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
Normal file
67
vendor/github.com/cespare/xxhash/v2/README.md
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# xxhash
|
||||
|
||||
[](https://godoc.org/github.com/cespare/xxhash)
|
||||
[](https://travis-ci.org/cespare/xxhash)
|
||||
|
||||
xxhash is a Go implementation of the 64-bit
|
||||
[xxHash](http://cyan4973.github.io/xxHash/) algorithm, XXH64. This is a
|
||||
high-quality hashing algorithm that is much faster than anything in the Go
|
||||
standard library.
|
||||
|
||||
This package provides a straightforward API:
|
||||
|
||||
```
|
||||
func Sum64(b []byte) uint64
|
||||
func Sum64String(s string) uint64
|
||||
type Digest struct{ ... }
|
||||
func New() *Digest
|
||||
```
|
||||
|
||||
The `Digest` type implements hash.Hash64. Its key methods are:
|
||||
|
||||
```
|
||||
func (*Digest) Write([]byte) (int, error)
|
||||
func (*Digest) WriteString(string) (int, error)
|
||||
func (*Digest) Sum64() uint64
|
||||
```
|
||||
|
||||
This implementation provides a fast pure-Go implementation and an even faster
|
||||
assembly implementation for amd64.
|
||||
|
||||
## Compatibility
|
||||
|
||||
This package is in a module and the latest code is in version 2 of the module.
|
||||
You need a version of Go with at least "minimal module compatibility" to use
|
||||
github.com/cespare/xxhash/v2:
|
||||
|
||||
* 1.9.7+ for Go 1.9
|
||||
* 1.10.3+ for Go 1.10
|
||||
* Go 1.11 or later
|
||||
|
||||
I recommend using the latest release of Go.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
Here are some quick benchmarks comparing the pure-Go and assembly
|
||||
implementations of Sum64.
|
||||
|
||||
| input size | purego | asm |
|
||||
| --- | --- | --- |
|
||||
| 5 B | 979.66 MB/s | 1291.17 MB/s |
|
||||
| 100 B | 7475.26 MB/s | 7973.40 MB/s |
|
||||
| 4 KB | 17573.46 MB/s | 17602.65 MB/s |
|
||||
| 10 MB | 17131.46 MB/s | 17142.16 MB/s |
|
||||
|
||||
These numbers were generated on Ubuntu 18.04 with an Intel i7-8700K CPU using
|
||||
the following commands under Go 1.11.2:
|
||||
|
||||
```
|
||||
$ go test -tags purego -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||
$ go test -benchtime 10s -bench '/xxhash,direct,bytes'
|
||||
```
|
||||
|
||||
## Projects using this package
|
||||
|
||||
- [InfluxDB](https://github.com/influxdata/influxdb)
|
||||
- [Prometheus](https://github.com/prometheus/prometheus)
|
||||
- [FreeCache](https://github.com/coocood/freecache)
|
3
vendor/github.com/cespare/xxhash/v2/go.mod
generated
vendored
Normal file
3
vendor/github.com/cespare/xxhash/v2/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/cespare/xxhash/v2
|
||||
|
||||
go 1.11
|
0
vendor/github.com/cespare/xxhash/v2/go.sum
generated
vendored
Normal file
0
vendor/github.com/cespare/xxhash/v2/go.sum
generated
vendored
Normal file
236
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
Normal file
236
vendor/github.com/cespare/xxhash/v2/xxhash.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
// Package xxhash implements the 64-bit variant of xxHash (XXH64) as described
|
||||
// at http://cyan4973.github.io/xxHash/.
|
||||
package xxhash
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math/bits"
|
||||
)
|
||||
|
||||
const (
|
||||
prime1 uint64 = 11400714785074694791
|
||||
prime2 uint64 = 14029467366897019727
|
||||
prime3 uint64 = 1609587929392839161
|
||||
prime4 uint64 = 9650029242287828579
|
||||
prime5 uint64 = 2870177450012600261
|
||||
)
|
||||
|
||||
// NOTE(caleb): I'm using both consts and vars of the primes. Using consts where
|
||||
// possible in the Go code is worth a small (but measurable) performance boost
|
||||
// by avoiding some MOVQs. Vars are needed for the asm and also are useful for
|
||||
// convenience in the Go code in a few places where we need to intentionally
|
||||
// avoid constant arithmetic (e.g., v1 := prime1 + prime2 fails because the
|
||||
// result overflows a uint64).
|
||||
var (
|
||||
prime1v = prime1
|
||||
prime2v = prime2
|
||||
prime3v = prime3
|
||||
prime4v = prime4
|
||||
prime5v = prime5
|
||||
)
|
||||
|
||||
// Digest implements hash.Hash64.
|
||||
type Digest struct {
|
||||
v1 uint64
|
||||
v2 uint64
|
||||
v3 uint64
|
||||
v4 uint64
|
||||
total uint64
|
||||
mem [32]byte
|
||||
n int // how much of mem is used
|
||||
}
|
||||
|
||||
// New creates a new Digest that computes the 64-bit xxHash algorithm.
|
||||
func New() *Digest {
|
||||
var d Digest
|
||||
d.Reset()
|
||||
return &d
|
||||
}
|
||||
|
||||
// Reset clears the Digest's state so that it can be reused.
|
||||
func (d *Digest) Reset() {
|
||||
d.v1 = prime1v + prime2
|
||||
d.v2 = prime2
|
||||
d.v3 = 0
|
||||
d.v4 = -prime1v
|
||||
d.total = 0
|
||||
d.n = 0
|
||||
}
|
||||
|
||||
// Size always returns 8 bytes.
|
||||
func (d *Digest) Size() int { return 8 }
|
||||
|
||||
// BlockSize always returns 32 bytes.
|
||||
func (d *Digest) BlockSize() int { return 32 }
|
||||
|
||||
// Write adds more data to d. It always returns len(b), nil.
|
||||
func (d *Digest) Write(b []byte) (n int, err error) {
|
||||
n = len(b)
|
||||
d.total += uint64(n)
|
||||
|
||||
if d.n+n < 32 {
|
||||
// This new data doesn't even fill the current block.
|
||||
copy(d.mem[d.n:], b)
|
||||
d.n += n
|
||||
return
|
||||
}
|
||||
|
||||
if d.n > 0 {
|
||||
// Finish off the partial block.
|
||||
copy(d.mem[d.n:], b)
|
||||
d.v1 = round(d.v1, u64(d.mem[0:8]))
|
||||
d.v2 = round(d.v2, u64(d.mem[8:16]))
|
||||
d.v3 = round(d.v3, u64(d.mem[16:24]))
|
||||
d.v4 = round(d.v4, u64(d.mem[24:32]))
|
||||
b = b[32-d.n:]
|
||||
d.n = 0
|
||||
}
|
||||
|
||||
if len(b) >= 32 {
|
||||
// One or more full blocks left.
|
||||
nw := writeBlocks(d, b)
|
||||
b = b[nw:]
|
||||
}
|
||||
|
||||
// Store any remaining partial block.
|
||||
copy(d.mem[:], b)
|
||||
d.n = len(b)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sum appends the current hash to b and returns the resulting slice.
|
||||
func (d *Digest) Sum(b []byte) []byte {
|
||||
s := d.Sum64()
|
||||
return append(
|
||||
b,
|
||||
byte(s>>56),
|
||||
byte(s>>48),
|
||||
byte(s>>40),
|
||||
byte(s>>32),
|
||||
byte(s>>24),
|
||||
byte(s>>16),
|
||||
byte(s>>8),
|
||||
byte(s),
|
||||
)
|
||||
}
|
||||
|
||||
// Sum64 returns the current hash.
|
||||
func (d *Digest) Sum64() uint64 {
|
||||
var h uint64
|
||||
|
||||
if d.total >= 32 {
|
||||
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||
h = mergeRound(h, v1)
|
||||
h = mergeRound(h, v2)
|
||||
h = mergeRound(h, v3)
|
||||
h = mergeRound(h, v4)
|
||||
} else {
|
||||
h = d.v3 + prime5
|
||||
}
|
||||
|
||||
h += d.total
|
||||
|
||||
i, end := 0, d.n
|
||||
for ; i+8 <= end; i += 8 {
|
||||
k1 := round(0, u64(d.mem[i:i+8]))
|
||||
h ^= k1
|
||||
h = rol27(h)*prime1 + prime4
|
||||
}
|
||||
if i+4 <= end {
|
||||
h ^= uint64(u32(d.mem[i:i+4])) * prime1
|
||||
h = rol23(h)*prime2 + prime3
|
||||
i += 4
|
||||
}
|
||||
for i < end {
|
||||
h ^= uint64(d.mem[i]) * prime5
|
||||
h = rol11(h) * prime1
|
||||
i++
|
||||
}
|
||||
|
||||
h ^= h >> 33
|
||||
h *= prime2
|
||||
h ^= h >> 29
|
||||
h *= prime3
|
||||
h ^= h >> 32
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
const (
|
||||
magic = "xxh\x06"
|
||||
marshaledSize = len(magic) + 8*5 + 32
|
||||
)
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||
func (d *Digest) MarshalBinary() ([]byte, error) {
|
||||
b := make([]byte, 0, marshaledSize)
|
||||
b = append(b, magic...)
|
||||
b = appendUint64(b, d.v1)
|
||||
b = appendUint64(b, d.v2)
|
||||
b = appendUint64(b, d.v3)
|
||||
b = appendUint64(b, d.v4)
|
||||
b = appendUint64(b, d.total)
|
||||
b = append(b, d.mem[:d.n]...)
|
||||
b = b[:len(b)+len(d.mem)-d.n]
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
||||
func (d *Digest) UnmarshalBinary(b []byte) error {
|
||||
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
|
||||
return errors.New("xxhash: invalid hash state identifier")
|
||||
}
|
||||
if len(b) != marshaledSize {
|
||||
return errors.New("xxhash: invalid hash state size")
|
||||
}
|
||||
b = b[len(magic):]
|
||||
b, d.v1 = consumeUint64(b)
|
||||
b, d.v2 = consumeUint64(b)
|
||||
b, d.v3 = consumeUint64(b)
|
||||
b, d.v4 = consumeUint64(b)
|
||||
b, d.total = consumeUint64(b)
|
||||
copy(d.mem[:], b)
|
||||
b = b[len(d.mem):]
|
||||
d.n = int(d.total % uint64(len(d.mem)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendUint64(b []byte, x uint64) []byte {
|
||||
var a [8]byte
|
||||
binary.LittleEndian.PutUint64(a[:], x)
|
||||
return append(b, a[:]...)
|
||||
}
|
||||
|
||||
func consumeUint64(b []byte) ([]byte, uint64) {
|
||||
x := u64(b)
|
||||
return b[8:], x
|
||||
}
|
||||
|
||||
func u64(b []byte) uint64 { return binary.LittleEndian.Uint64(b) }
|
||||
func u32(b []byte) uint32 { return binary.LittleEndian.Uint32(b) }
|
||||
|
||||
func round(acc, input uint64) uint64 {
|
||||
acc += input * prime2
|
||||
acc = rol31(acc)
|
||||
acc *= prime1
|
||||
return acc
|
||||
}
|
||||
|
||||
func mergeRound(acc, val uint64) uint64 {
|
||||
val = round(0, val)
|
||||
acc ^= val
|
||||
acc = acc*prime1 + prime4
|
||||
return acc
|
||||
}
|
||||
|
||||
func rol1(x uint64) uint64 { return bits.RotateLeft64(x, 1) }
|
||||
func rol7(x uint64) uint64 { return bits.RotateLeft64(x, 7) }
|
||||
func rol11(x uint64) uint64 { return bits.RotateLeft64(x, 11) }
|
||||
func rol12(x uint64) uint64 { return bits.RotateLeft64(x, 12) }
|
||||
func rol18(x uint64) uint64 { return bits.RotateLeft64(x, 18) }
|
||||
func rol23(x uint64) uint64 { return bits.RotateLeft64(x, 23) }
|
||||
func rol27(x uint64) uint64 { return bits.RotateLeft64(x, 27) }
|
||||
func rol31(x uint64) uint64 { return bits.RotateLeft64(x, 31) }
|
13
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go
generated
vendored
Normal file
13
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !purego
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
//
|
||||
//go:noescape
|
||||
func Sum64(b []byte) uint64
|
||||
|
||||
//go:noescape
|
||||
func writeBlocks(d *Digest, b []byte) int
|
215
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
Normal file
215
vendor/github.com/cespare/xxhash/v2/xxhash_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
// +build !appengine
|
||||
// +build gc
|
||||
// +build !purego
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// Register allocation:
|
||||
// AX h
|
||||
// CX pointer to advance through b
|
||||
// DX n
|
||||
// BX loop end
|
||||
// R8 v1, k1
|
||||
// R9 v2
|
||||
// R10 v3
|
||||
// R11 v4
|
||||
// R12 tmp
|
||||
// R13 prime1v
|
||||
// R14 prime2v
|
||||
// R15 prime4v
|
||||
|
||||
// round reads from and advances the buffer pointer in CX.
|
||||
// It assumes that R13 has prime1v and R14 has prime2v.
|
||||
#define round(r) \
|
||||
MOVQ (CX), R12 \
|
||||
ADDQ $8, CX \
|
||||
IMULQ R14, R12 \
|
||||
ADDQ R12, r \
|
||||
ROLQ $31, r \
|
||||
IMULQ R13, r
|
||||
|
||||
// mergeRound applies a merge round on the two registers acc and val.
|
||||
// It assumes that R13 has prime1v, R14 has prime2v, and R15 has prime4v.
|
||||
#define mergeRound(acc, val) \
|
||||
IMULQ R14, val \
|
||||
ROLQ $31, val \
|
||||
IMULQ R13, val \
|
||||
XORQ val, acc \
|
||||
IMULQ R13, acc \
|
||||
ADDQ R15, acc
|
||||
|
||||
// func Sum64(b []byte) uint64
|
||||
TEXT ·Sum64(SB), NOSPLIT, $0-32
|
||||
// Load fixed primes.
|
||||
MOVQ ·prime1v(SB), R13
|
||||
MOVQ ·prime2v(SB), R14
|
||||
MOVQ ·prime4v(SB), R15
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+0(FP), CX
|
||||
MOVQ b_len+8(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
|
||||
// The first loop limit will be len(b)-32.
|
||||
SUBQ $32, BX
|
||||
|
||||
// Check whether we have at least one block.
|
||||
CMPQ DX, $32
|
||||
JLT noBlocks
|
||||
|
||||
// Set up initial state (v1, v2, v3, v4).
|
||||
MOVQ R13, R8
|
||||
ADDQ R14, R8
|
||||
MOVQ R14, R9
|
||||
XORQ R10, R10
|
||||
XORQ R11, R11
|
||||
SUBQ R13, R11
|
||||
|
||||
// Loop until CX > BX.
|
||||
blockLoop:
|
||||
round(R8)
|
||||
round(R9)
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE blockLoop
|
||||
|
||||
MOVQ R8, AX
|
||||
ROLQ $1, AX
|
||||
MOVQ R9, R12
|
||||
ROLQ $7, R12
|
||||
ADDQ R12, AX
|
||||
MOVQ R10, R12
|
||||
ROLQ $12, R12
|
||||
ADDQ R12, AX
|
||||
MOVQ R11, R12
|
||||
ROLQ $18, R12
|
||||
ADDQ R12, AX
|
||||
|
||||
mergeRound(AX, R8)
|
||||
mergeRound(AX, R9)
|
||||
mergeRound(AX, R10)
|
||||
mergeRound(AX, R11)
|
||||
|
||||
JMP afterBlocks
|
||||
|
||||
noBlocks:
|
||||
MOVQ ·prime5v(SB), AX
|
||||
|
||||
afterBlocks:
|
||||
ADDQ DX, AX
|
||||
|
||||
// Right now BX has len(b)-32, and we want to loop until CX > len(b)-8.
|
||||
ADDQ $24, BX
|
||||
|
||||
CMPQ CX, BX
|
||||
JG fourByte
|
||||
|
||||
wordLoop:
|
||||
// Calculate k1.
|
||||
MOVQ (CX), R8
|
||||
ADDQ $8, CX
|
||||
IMULQ R14, R8
|
||||
ROLQ $31, R8
|
||||
IMULQ R13, R8
|
||||
|
||||
XORQ R8, AX
|
||||
ROLQ $27, AX
|
||||
IMULQ R13, AX
|
||||
ADDQ R15, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE wordLoop
|
||||
|
||||
fourByte:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
JG singles
|
||||
|
||||
MOVL (CX), R8
|
||||
ADDQ $4, CX
|
||||
IMULQ R13, R8
|
||||
XORQ R8, AX
|
||||
|
||||
ROLQ $23, AX
|
||||
IMULQ R14, AX
|
||||
ADDQ ·prime3v(SB), AX
|
||||
|
||||
singles:
|
||||
ADDQ $4, BX
|
||||
CMPQ CX, BX
|
||||
JGE finalize
|
||||
|
||||
singlesLoop:
|
||||
MOVBQZX (CX), R12
|
||||
ADDQ $1, CX
|
||||
IMULQ ·prime5v(SB), R12
|
||||
XORQ R12, AX
|
||||
|
||||
ROLQ $11, AX
|
||||
IMULQ R13, AX
|
||||
|
||||
CMPQ CX, BX
|
||||
JL singlesLoop
|
||||
|
||||
finalize:
|
||||
MOVQ AX, R12
|
||||
SHRQ $33, R12
|
||||
XORQ R12, AX
|
||||
IMULQ R14, AX
|
||||
MOVQ AX, R12
|
||||
SHRQ $29, R12
|
||||
XORQ R12, AX
|
||||
IMULQ ·prime3v(SB), AX
|
||||
MOVQ AX, R12
|
||||
SHRQ $32, R12
|
||||
XORQ R12, AX
|
||||
|
||||
MOVQ AX, ret+24(FP)
|
||||
RET
|
||||
|
||||
// writeBlocks uses the same registers as above except that it uses AX to store
|
||||
// the d pointer.
|
||||
|
||||
// func writeBlocks(d *Digest, b []byte) int
|
||||
TEXT ·writeBlocks(SB), NOSPLIT, $0-40
|
||||
// Load fixed primes needed for round.
|
||||
MOVQ ·prime1v(SB), R13
|
||||
MOVQ ·prime2v(SB), R14
|
||||
|
||||
// Load slice.
|
||||
MOVQ b_base+8(FP), CX
|
||||
MOVQ b_len+16(FP), DX
|
||||
LEAQ (CX)(DX*1), BX
|
||||
SUBQ $32, BX
|
||||
|
||||
// Load vN from d.
|
||||
MOVQ d+0(FP), AX
|
||||
MOVQ 0(AX), R8 // v1
|
||||
MOVQ 8(AX), R9 // v2
|
||||
MOVQ 16(AX), R10 // v3
|
||||
MOVQ 24(AX), R11 // v4
|
||||
|
||||
// We don't need to check the loop condition here; this function is
|
||||
// always called with at least one block of data to process.
|
||||
blockLoop:
|
||||
round(R8)
|
||||
round(R9)
|
||||
round(R10)
|
||||
round(R11)
|
||||
|
||||
CMPQ CX, BX
|
||||
JLE blockLoop
|
||||
|
||||
// Copy vN back to d.
|
||||
MOVQ R8, 0(AX)
|
||||
MOVQ R9, 8(AX)
|
||||
MOVQ R10, 16(AX)
|
||||
MOVQ R11, 24(AX)
|
||||
|
||||
// The number of bytes written is CX minus the old base pointer.
|
||||
SUBQ b_base+8(FP), CX
|
||||
MOVQ CX, ret+32(FP)
|
||||
|
||||
RET
|
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
Normal file
76
vendor/github.com/cespare/xxhash/v2/xxhash_other.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// +build !amd64 appengine !gc purego
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64 computes the 64-bit xxHash digest of b.
|
||||
func Sum64(b []byte) uint64 {
|
||||
// A simpler version would be
|
||||
// d := New()
|
||||
// d.Write(b)
|
||||
// return d.Sum64()
|
||||
// but this is faster, particularly for small inputs.
|
||||
|
||||
n := len(b)
|
||||
var h uint64
|
||||
|
||||
if n >= 32 {
|
||||
v1 := prime1v + prime2
|
||||
v2 := prime2
|
||||
v3 := uint64(0)
|
||||
v4 := -prime1v
|
||||
for len(b) >= 32 {
|
||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||
b = b[32:len(b):len(b)]
|
||||
}
|
||||
h = rol1(v1) + rol7(v2) + rol12(v3) + rol18(v4)
|
||||
h = mergeRound(h, v1)
|
||||
h = mergeRound(h, v2)
|
||||
h = mergeRound(h, v3)
|
||||
h = mergeRound(h, v4)
|
||||
} else {
|
||||
h = prime5
|
||||
}
|
||||
|
||||
h += uint64(n)
|
||||
|
||||
i, end := 0, len(b)
|
||||
for ; i+8 <= end; i += 8 {
|
||||
k1 := round(0, u64(b[i:i+8:len(b)]))
|
||||
h ^= k1
|
||||
h = rol27(h)*prime1 + prime4
|
||||
}
|
||||
if i+4 <= end {
|
||||
h ^= uint64(u32(b[i:i+4:len(b)])) * prime1
|
||||
h = rol23(h)*prime2 + prime3
|
||||
i += 4
|
||||
}
|
||||
for ; i < end; i++ {
|
||||
h ^= uint64(b[i]) * prime5
|
||||
h = rol11(h) * prime1
|
||||
}
|
||||
|
||||
h ^= h >> 33
|
||||
h *= prime2
|
||||
h ^= h >> 29
|
||||
h *= prime3
|
||||
h ^= h >> 32
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func writeBlocks(d *Digest, b []byte) int {
|
||||
v1, v2, v3, v4 := d.v1, d.v2, d.v3, d.v4
|
||||
n := len(b)
|
||||
for len(b) >= 32 {
|
||||
v1 = round(v1, u64(b[0:8:len(b)]))
|
||||
v2 = round(v2, u64(b[8:16:len(b)]))
|
||||
v3 = round(v3, u64(b[16:24:len(b)]))
|
||||
v4 = round(v4, u64(b[24:32:len(b)]))
|
||||
b = b[32:len(b):len(b)]
|
||||
}
|
||||
d.v1, d.v2, d.v3, d.v4 = v1, v2, v3, v4
|
||||
return n - len(b)
|
||||
}
|
15
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
Normal file
15
vendor/github.com/cespare/xxhash/v2/xxhash_safe.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build appengine
|
||||
|
||||
// This file contains the safe implementations of otherwise unsafe-using code.
|
||||
|
||||
package xxhash
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
func Sum64String(s string) uint64 {
|
||||
return Sum64([]byte(s))
|
||||
}
|
||||
|
||||
// WriteString adds more data to d. It always returns len(s), nil.
|
||||
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||
return d.Write([]byte(s))
|
||||
}
|
46
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
Normal file
46
vendor/github.com/cespare/xxhash/v2/xxhash_unsafe.go
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// +build !appengine
|
||||
|
||||
// This file encapsulates usage of unsafe.
|
||||
// xxhash_safe.go contains the safe implementations.
|
||||
|
||||
package xxhash
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Notes:
|
||||
//
|
||||
// See https://groups.google.com/d/msg/golang-nuts/dcjzJy-bSpw/tcZYBzQqAQAJ
|
||||
// for some discussion about these unsafe conversions.
|
||||
//
|
||||
// In the future it's possible that compiler optimizations will make these
|
||||
// unsafe operations unnecessary: https://golang.org/issue/2205.
|
||||
//
|
||||
// Both of these wrapper functions still incur function call overhead since they
|
||||
// will not be inlined. We could write Go/asm copies of Sum64 and Digest.Write
|
||||
// for strings to squeeze out a bit more speed. Mid-stack inlining should
|
||||
// eventually fix this.
|
||||
|
||||
// Sum64String computes the 64-bit xxHash digest of s.
|
||||
// It may be faster than Sum64([]byte(s)) by avoiding a copy.
|
||||
func Sum64String(s string) uint64 {
|
||||
var b []byte
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||
bh.Len = len(s)
|
||||
bh.Cap = len(s)
|
||||
return Sum64(b)
|
||||
}
|
||||
|
||||
// WriteString adds more data to d. It always returns len(s), nil.
|
||||
// It may be faster than Write([]byte(s)) by avoiding a copy.
|
||||
func (d *Digest) WriteString(s string) (n int, err error) {
|
||||
var b []byte
|
||||
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
|
||||
bh.Data = (*reflect.StringHeader)(unsafe.Pointer(&s)).Data
|
||||
bh.Len = len(s)
|
||||
bh.Cap = len(s)
|
||||
return d.Write(b)
|
||||
}
|
1
vendor/github.com/chzyer/readline/.gitignore
generated
vendored
Normal file
1
vendor/github.com/chzyer/readline/.gitignore
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.vscode/*
|
8
vendor/github.com/chzyer/readline/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/chzyer/readline/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.x
|
||||
script:
|
||||
- GOOS=windows go install github.com/chzyer/readline/example/...
|
||||
- GOOS=linux go install github.com/chzyer/readline/example/...
|
||||
- GOOS=darwin go install github.com/chzyer/readline/example/...
|
||||
- go test -race -v
|
58
vendor/github.com/chzyer/readline/CHANGELOG.md
generated
vendored
Normal file
58
vendor/github.com/chzyer/readline/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
# ChangeLog
|
||||
|
||||
### 1.4 - 2016-07-25
|
||||
|
||||
* [#60][60] Support dynamic autocompletion
|
||||
* Fix ANSI parser on Windows
|
||||
* Fix wrong column width in complete mode on Windows
|
||||
* Remove dependent package "golang.org/x/crypto/ssh/terminal"
|
||||
|
||||
### 1.3 - 2016-05-09
|
||||
|
||||
* [#38][38] add SetChildren for prefix completer interface
|
||||
* [#42][42] improve multiple lines compatibility
|
||||
* [#43][43] remove sub-package(runes) for gopkg compatibility
|
||||
* [#46][46] Auto complete with space prefixed line
|
||||
* [#48][48] support suspend process (ctrl+Z)
|
||||
* [#49][49] fix bug that check equals with previous command
|
||||
* [#53][53] Fix bug which causes integer divide by zero panicking when input buffer is empty
|
||||
|
||||
### 1.2 - 2016-03-05
|
||||
|
||||
* Add a demo for checking password strength [example/readline-pass-strength](https://github.com/chzyer/readline/blob/master/example/readline-pass-strength/readline-pass-strength.go), , written by [@sahib](https://github.com/sahib)
|
||||
* [#23][23], support stdin remapping
|
||||
* [#27][27], add a `UniqueEditLine` to `Config`, which will erase the editing line after user submited it, usually use in IM.
|
||||
* Add a demo for multiline [example/readline-multiline](https://github.com/chzyer/readline/blob/master/example/readline-multiline/readline-multiline.go) which can submit one SQL by multiple lines.
|
||||
* Supports performs even stdin/stdout is not a tty.
|
||||
* Add a new simple apis for single instance, check by [here](https://github.com/chzyer/readline/blob/master/std.go). It need to save history manually if using this api.
|
||||
* [#28][28], fixes the history is not working as expected.
|
||||
* [#33][33], vim mode now support `c`, `d`, `x (delete character)`, `r (replace character)`
|
||||
|
||||
### 1.1 - 2015-11-20
|
||||
|
||||
* [#12][12] Add support for key `<Delete>`/`<Home>`/`<End>`
|
||||
* Only enter raw mode as needed (calling `Readline()`), program will receive signal(e.g. Ctrl+C) if not interact with `readline`.
|
||||
* Bugs fixed for `PrefixCompleter`
|
||||
* Press `Ctrl+D` in empty line will cause `io.EOF` in error, Press `Ctrl+C` in anytime will cause `ErrInterrupt` instead of `io.EOF`, this will privodes a shell-like user experience.
|
||||
* Customable Interrupt/EOF prompt in `Config`
|
||||
* [#17][17] Change atomic package to use 32bit function to let it runnable on arm 32bit devices
|
||||
* Provides a new password user experience(`readline.ReadPasswordEx()`).
|
||||
|
||||
### 1.0 - 2015-10-14
|
||||
|
||||
* Initial public release.
|
||||
|
||||
[12]: https://github.com/chzyer/readline/pull/12
|
||||
[17]: https://github.com/chzyer/readline/pull/17
|
||||
[23]: https://github.com/chzyer/readline/pull/23
|
||||
[27]: https://github.com/chzyer/readline/pull/27
|
||||
[28]: https://github.com/chzyer/readline/pull/28
|
||||
[33]: https://github.com/chzyer/readline/pull/33
|
||||
[38]: https://github.com/chzyer/readline/pull/38
|
||||
[42]: https://github.com/chzyer/readline/pull/42
|
||||
[43]: https://github.com/chzyer/readline/pull/43
|
||||
[46]: https://github.com/chzyer/readline/pull/46
|
||||
[48]: https://github.com/chzyer/readline/pull/48
|
||||
[49]: https://github.com/chzyer/readline/pull/49
|
||||
[53]: https://github.com/chzyer/readline/pull/53
|
||||
[60]: https://github.com/chzyer/readline/pull/60
|
22
vendor/github.com/chzyer/readline/LICENSE
generated
vendored
Normal file
22
vendor/github.com/chzyer/readline/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Chzyer
|
||||
|
||||
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.
|
||||
|
114
vendor/github.com/chzyer/readline/README.md
generated
vendored
Normal file
114
vendor/github.com/chzyer/readline/README.md
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
[](https://travis-ci.org/chzyer/readline)
|
||||
[](LICENSE.md)
|
||||
[](https://github.com/chzyer/readline/releases)
|
||||
[](https://godoc.org/github.com/chzyer/readline)
|
||||
[](#backers)
|
||||
[](#sponsors)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo.png" />
|
||||
<a href="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m" target="_blank"><img src="https://asciinema.org/a/32oseof9mkilg7t7d4780qt4m.png" width="654"/></a>
|
||||
<img src="https://raw.githubusercontent.com/chzyer/readline/assets/logo_f.png" />
|
||||
</p>
|
||||
|
||||
A powerful readline library in `Linux` `macOS` `Windows` `Solaris`
|
||||
|
||||
## Guide
|
||||
|
||||
* [Demo](example/readline-demo/readline-demo.go)
|
||||
* [Shortcut](doc/shortcut.md)
|
||||
|
||||
## Repos using readline
|
||||
|
||||
[](https://github.com/cockroachdb/cockroach)
|
||||
[](https://github.com/robertkrimen/otto)
|
||||
[](https://github.com/remind101/empire)
|
||||
[](https://github.com/mehrdadrad/mylg)
|
||||
[](https://github.com/knq/usql)
|
||||
[](https://github.com/youtube/doorman)
|
||||
[](https://github.com/bom-d-van/harp)
|
||||
[](https://github.com/abiosoft/ishell)
|
||||
[](https://github.com/Netflix/hal-9001)
|
||||
[](https://github.com/docker/go-p9p)
|
||||
|
||||
|
||||
## Feedback
|
||||
|
||||
If you have any questions, please submit a github issue and any pull requests is welcomed :)
|
||||
|
||||
* [https://twitter.com/chzyer](https://twitter.com/chzyer)
|
||||
* [http://weibo.com/2145262190](http://weibo.com/2145262190)
|
||||
|
||||
|
||||
## Backers
|
||||
|
||||
Love Readline? Help me keep it alive by donating funds to cover project expenses!<br />
|
||||
[[Become a backer](https://opencollective.com/readline#backer)]
|
||||
|
||||
<a href="https://opencollective.com/readline/backer/0/website" target="_blank"><img src="https://opencollective.com/readline/backer/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/1/website" target="_blank"><img src="https://opencollective.com/readline/backer/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/2/website" target="_blank"><img src="https://opencollective.com/readline/backer/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/3/website" target="_blank"><img src="https://opencollective.com/readline/backer/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/4/website" target="_blank"><img src="https://opencollective.com/readline/backer/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/5/website" target="_blank"><img src="https://opencollective.com/readline/backer/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/6/website" target="_blank"><img src="https://opencollective.com/readline/backer/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/7/website" target="_blank"><img src="https://opencollective.com/readline/backer/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/8/website" target="_blank"><img src="https://opencollective.com/readline/backer/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/9/website" target="_blank"><img src="https://opencollective.com/readline/backer/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/10/website" target="_blank"><img src="https://opencollective.com/readline/backer/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/11/website" target="_blank"><img src="https://opencollective.com/readline/backer/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/12/website" target="_blank"><img src="https://opencollective.com/readline/backer/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/13/website" target="_blank"><img src="https://opencollective.com/readline/backer/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/14/website" target="_blank"><img src="https://opencollective.com/readline/backer/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/15/website" target="_blank"><img src="https://opencollective.com/readline/backer/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/16/website" target="_blank"><img src="https://opencollective.com/readline/backer/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/17/website" target="_blank"><img src="https://opencollective.com/readline/backer/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/18/website" target="_blank"><img src="https://opencollective.com/readline/backer/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/19/website" target="_blank"><img src="https://opencollective.com/readline/backer/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/20/website" target="_blank"><img src="https://opencollective.com/readline/backer/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/21/website" target="_blank"><img src="https://opencollective.com/readline/backer/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/22/website" target="_blank"><img src="https://opencollective.com/readline/backer/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/23/website" target="_blank"><img src="https://opencollective.com/readline/backer/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/24/website" target="_blank"><img src="https://opencollective.com/readline/backer/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/25/website" target="_blank"><img src="https://opencollective.com/readline/backer/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/26/website" target="_blank"><img src="https://opencollective.com/readline/backer/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/27/website" target="_blank"><img src="https://opencollective.com/readline/backer/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/28/website" target="_blank"><img src="https://opencollective.com/readline/backer/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/backer/29/website" target="_blank"><img src="https://opencollective.com/readline/backer/29/avatar.svg"></a>
|
||||
|
||||
|
||||
## Sponsors
|
||||
|
||||
Become a sponsor and get your logo here on our Github page. [[Become a sponsor](https://opencollective.com/readline#sponsor)]
|
||||
|
||||
<a href="https://opencollective.com/readline/sponsor/0/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/1/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/2/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/3/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/4/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/5/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/6/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/7/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/8/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/9/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/9/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/10/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/10/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/11/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/11/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/12/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/12/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/13/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/13/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/14/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/14/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/15/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/15/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/16/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/16/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/17/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/17/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/18/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/18/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/19/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/19/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/20/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/20/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/21/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/21/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/22/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/22/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/23/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/23/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/24/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/24/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/25/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/25/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/26/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/26/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/27/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/27/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/28/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/28/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/readline/sponsor/29/website" target="_blank"><img src="https://opencollective.com/readline/sponsor/29/avatar.svg"></a>
|
||||
|
249
vendor/github.com/chzyer/readline/ansi_windows.go
generated
vendored
Normal file
249
vendor/github.com/chzyer/readline/ansi_windows.go
generated
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
_ = uint16(0)
|
||||
COLOR_FBLUE = 0x0001
|
||||
COLOR_FGREEN = 0x0002
|
||||
COLOR_FRED = 0x0004
|
||||
COLOR_FINTENSITY = 0x0008
|
||||
|
||||
COLOR_BBLUE = 0x0010
|
||||
COLOR_BGREEN = 0x0020
|
||||
COLOR_BRED = 0x0040
|
||||
COLOR_BINTENSITY = 0x0080
|
||||
|
||||
COMMON_LVB_UNDERSCORE = 0x8000
|
||||
COMMON_LVB_BOLD = 0x0007
|
||||
)
|
||||
|
||||
var ColorTableFg = []word{
|
||||
0, // 30: Black
|
||||
COLOR_FRED, // 31: Red
|
||||
COLOR_FGREEN, // 32: Green
|
||||
COLOR_FRED | COLOR_FGREEN, // 33: Yellow
|
||||
COLOR_FBLUE, // 34: Blue
|
||||
COLOR_FRED | COLOR_FBLUE, // 35: Magenta
|
||||
COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan
|
||||
COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White
|
||||
}
|
||||
|
||||
var ColorTableBg = []word{
|
||||
0, // 40: Black
|
||||
COLOR_BRED, // 41: Red
|
||||
COLOR_BGREEN, // 42: Green
|
||||
COLOR_BRED | COLOR_BGREEN, // 43: Yellow
|
||||
COLOR_BBLUE, // 44: Blue
|
||||
COLOR_BRED | COLOR_BBLUE, // 45: Magenta
|
||||
COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan
|
||||
COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White
|
||||
}
|
||||
|
||||
type ANSIWriter struct {
|
||||
target io.Writer
|
||||
wg sync.WaitGroup
|
||||
ctx *ANSIWriterCtx
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewANSIWriter(w io.Writer) *ANSIWriter {
|
||||
a := &ANSIWriter{
|
||||
target: w,
|
||||
ctx: NewANSIWriterCtx(w),
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) Close() error {
|
||||
a.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
type ANSIWriterCtx struct {
|
||||
isEsc bool
|
||||
isEscSeq bool
|
||||
arg []string
|
||||
target *bufio.Writer
|
||||
wantFlush bool
|
||||
}
|
||||
|
||||
func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx {
|
||||
return &ANSIWriterCtx{
|
||||
target: bufio.NewWriter(target),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *ANSIWriterCtx) Flush() {
|
||||
a.target.Flush()
|
||||
}
|
||||
|
||||
func (a *ANSIWriterCtx) process(r rune) bool {
|
||||
if a.wantFlush {
|
||||
if r == 0 || r == CharEsc {
|
||||
a.wantFlush = false
|
||||
a.target.Flush()
|
||||
}
|
||||
}
|
||||
if a.isEscSeq {
|
||||
a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg)
|
||||
return true
|
||||
}
|
||||
|
||||
switch r {
|
||||
case CharEsc:
|
||||
a.isEsc = true
|
||||
case '[':
|
||||
if a.isEsc {
|
||||
a.arg = nil
|
||||
a.isEscSeq = true
|
||||
a.isEsc = false
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
a.target.WriteRune(r)
|
||||
a.wantFlush = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
|
||||
arg := *argptr
|
||||
var err error
|
||||
|
||||
if r >= 'A' && r <= 'D' {
|
||||
count := short(GetInt(arg, 1))
|
||||
info, err := GetConsoleScreenBufferInfo()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
switch r {
|
||||
case 'A': // up
|
||||
info.dwCursorPosition.y -= count
|
||||
case 'B': // down
|
||||
info.dwCursorPosition.y += count
|
||||
case 'C': // right
|
||||
info.dwCursorPosition.x += count
|
||||
case 'D': // left
|
||||
info.dwCursorPosition.x -= count
|
||||
}
|
||||
SetConsoleCursorPosition(&info.dwCursorPosition)
|
||||
return false
|
||||
}
|
||||
|
||||
switch r {
|
||||
case 'J':
|
||||
killLines()
|
||||
case 'K':
|
||||
eraseLine()
|
||||
case 'm':
|
||||
color := word(0)
|
||||
for _, item := range arg {
|
||||
var c int
|
||||
c, err = strconv.Atoi(item)
|
||||
if err != nil {
|
||||
w.WriteString("[" + strings.Join(arg, ";") + "m")
|
||||
break
|
||||
}
|
||||
if c >= 30 && c < 40 {
|
||||
color ^= COLOR_FINTENSITY
|
||||
color |= ColorTableFg[c-30]
|
||||
} else if c >= 40 && c < 50 {
|
||||
color ^= COLOR_BINTENSITY
|
||||
color |= ColorTableBg[c-40]
|
||||
} else if c == 4 {
|
||||
color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
|
||||
} else if c == 1 {
|
||||
color |= COMMON_LVB_BOLD | COLOR_FINTENSITY
|
||||
} else { // unknown code treat as reset
|
||||
color = ColorTableFg[7]
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
kernel.SetConsoleTextAttribute(stdout, uintptr(color))
|
||||
case '\007': // set title
|
||||
case ';':
|
||||
if len(arg) == 0 || arg[len(arg)-1] != "" {
|
||||
arg = append(arg, "")
|
||||
*argptr = arg
|
||||
}
|
||||
return true
|
||||
default:
|
||||
if len(arg) == 0 {
|
||||
arg = append(arg, "")
|
||||
}
|
||||
arg[len(arg)-1] += string(r)
|
||||
*argptr = arg
|
||||
return true
|
||||
}
|
||||
*argptr = nil
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) Write(b []byte) (int, error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
|
||||
off := 0
|
||||
for len(b) > off {
|
||||
r, size := utf8.DecodeRune(b[off:])
|
||||
if size == 0 {
|
||||
return off, io.ErrShortWrite
|
||||
}
|
||||
off += size
|
||||
a.ctx.process(r)
|
||||
}
|
||||
a.ctx.Flush()
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func killLines() error {
|
||||
sbi, err := GetConsoleScreenBufferInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x
|
||||
size += sbi.dwCursorPosition.x
|
||||
|
||||
var written int
|
||||
kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]),
|
||||
uintptr(size),
|
||||
sbi.dwCursorPosition.ptr(),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
|
||||
uintptr(size),
|
||||
sbi.dwCursorPosition.ptr(),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
}
|
||||
|
||||
func eraseLine() error {
|
||||
sbi, err := GetConsoleScreenBufferInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
size := sbi.dwSize.x
|
||||
sbi.dwCursorPosition.x = 0
|
||||
var written int
|
||||
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
|
||||
uintptr(size),
|
||||
sbi.dwCursorPosition.ptr(),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
}
|
285
vendor/github.com/chzyer/readline/complete.go
generated
vendored
Normal file
285
vendor/github.com/chzyer/readline/complete.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type AutoCompleter interface {
|
||||
// Readline will pass the whole line and current offset to it
|
||||
// Completer need to pass all the candidates, and how long they shared the same characters in line
|
||||
// Example:
|
||||
// [go, git, git-shell, grep]
|
||||
// Do("g", 1) => ["o", "it", "it-shell", "rep"], 1
|
||||
// Do("gi", 2) => ["t", "t-shell"], 2
|
||||
// Do("git", 3) => ["", "-shell"], 3
|
||||
Do(line []rune, pos int) (newLine [][]rune, length int)
|
||||
}
|
||||
|
||||
type TabCompleter struct{}
|
||||
|
||||
func (t *TabCompleter) Do([]rune, int) ([][]rune, int) {
|
||||
return [][]rune{[]rune("\t")}, 0
|
||||
}
|
||||
|
||||
type opCompleter struct {
|
||||
w io.Writer
|
||||
op *Operation
|
||||
width int
|
||||
|
||||
inCompleteMode bool
|
||||
inSelectMode bool
|
||||
candidate [][]rune
|
||||
candidateSource []rune
|
||||
candidateOff int
|
||||
candidateChoise int
|
||||
candidateColNum int
|
||||
}
|
||||
|
||||
func newOpCompleter(w io.Writer, op *Operation, width int) *opCompleter {
|
||||
return &opCompleter{
|
||||
w: w,
|
||||
op: op,
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opCompleter) doSelect() {
|
||||
if len(o.candidate) == 1 {
|
||||
o.op.buf.WriteRunes(o.candidate[0])
|
||||
o.ExitCompleteMode(false)
|
||||
return
|
||||
}
|
||||
o.nextCandidate(1)
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
func (o *opCompleter) nextCandidate(i int) {
|
||||
o.candidateChoise += i
|
||||
o.candidateChoise = o.candidateChoise % len(o.candidate)
|
||||
if o.candidateChoise < 0 {
|
||||
o.candidateChoise = len(o.candidate) + o.candidateChoise
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opCompleter) OnComplete() bool {
|
||||
if o.width == 0 {
|
||||
return false
|
||||
}
|
||||
if o.IsInCompleteSelectMode() {
|
||||
o.doSelect()
|
||||
return true
|
||||
}
|
||||
|
||||
buf := o.op.buf
|
||||
rs := buf.Runes()
|
||||
|
||||
if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
|
||||
o.EnterCompleteSelectMode()
|
||||
o.doSelect()
|
||||
return true
|
||||
}
|
||||
|
||||
o.ExitCompleteSelectMode()
|
||||
o.candidateSource = rs
|
||||
newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)
|
||||
if len(newLines) == 0 {
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
|
||||
// only Aggregate candidates in non-complete mode
|
||||
if !o.IsInCompleteMode() {
|
||||
if len(newLines) == 1 {
|
||||
buf.WriteRunes(newLines[0])
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
|
||||
same, size := runes.Aggregate(newLines)
|
||||
if size > 0 {
|
||||
buf.WriteRunes(same)
|
||||
o.ExitCompleteMode(false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
o.EnterCompleteMode(offset, newLines)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opCompleter) IsInCompleteSelectMode() bool {
|
||||
return o.inSelectMode
|
||||
}
|
||||
|
||||
func (o *opCompleter) IsInCompleteMode() bool {
|
||||
return o.inCompleteMode
|
||||
}
|
||||
|
||||
func (o *opCompleter) HandleCompleteSelect(r rune) bool {
|
||||
next := true
|
||||
switch r {
|
||||
case CharEnter, CharCtrlJ:
|
||||
next = false
|
||||
o.op.buf.WriteRunes(o.op.candidate[o.op.candidateChoise])
|
||||
o.ExitCompleteMode(false)
|
||||
case CharLineStart:
|
||||
num := o.candidateChoise % o.candidateColNum
|
||||
o.nextCandidate(-num)
|
||||
case CharLineEnd:
|
||||
num := o.candidateColNum - o.candidateChoise%o.candidateColNum - 1
|
||||
o.candidateChoise += num
|
||||
if o.candidateChoise >= len(o.candidate) {
|
||||
o.candidateChoise = len(o.candidate) - 1
|
||||
}
|
||||
case CharBackspace:
|
||||
o.ExitCompleteSelectMode()
|
||||
next = false
|
||||
case CharTab, CharForward:
|
||||
o.doSelect()
|
||||
case CharBell, CharInterrupt:
|
||||
o.ExitCompleteMode(true)
|
||||
next = false
|
||||
case CharNext:
|
||||
tmpChoise := o.candidateChoise + o.candidateColNum
|
||||
if tmpChoise >= o.getMatrixSize() {
|
||||
tmpChoise -= o.getMatrixSize()
|
||||
} else if tmpChoise >= len(o.candidate) {
|
||||
tmpChoise += o.candidateColNum
|
||||
tmpChoise -= o.getMatrixSize()
|
||||
}
|
||||
o.candidateChoise = tmpChoise
|
||||
case CharBackward:
|
||||
o.nextCandidate(-1)
|
||||
case CharPrev:
|
||||
tmpChoise := o.candidateChoise - o.candidateColNum
|
||||
if tmpChoise < 0 {
|
||||
tmpChoise += o.getMatrixSize()
|
||||
if tmpChoise >= len(o.candidate) {
|
||||
tmpChoise -= o.candidateColNum
|
||||
}
|
||||
}
|
||||
o.candidateChoise = tmpChoise
|
||||
default:
|
||||
next = false
|
||||
o.ExitCompleteSelectMode()
|
||||
}
|
||||
if next {
|
||||
o.CompleteRefresh()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o *opCompleter) getMatrixSize() int {
|
||||
line := len(o.candidate) / o.candidateColNum
|
||||
if len(o.candidate)%o.candidateColNum != 0 {
|
||||
line++
|
||||
}
|
||||
return line * o.candidateColNum
|
||||
}
|
||||
|
||||
func (o *opCompleter) OnWidthChange(newWidth int) {
|
||||
o.width = newWidth
|
||||
}
|
||||
|
||||
func (o *opCompleter) CompleteRefresh() {
|
||||
if !o.inCompleteMode {
|
||||
return
|
||||
}
|
||||
lineCnt := o.op.buf.CursorLineCount()
|
||||
colWidth := 0
|
||||
for _, c := range o.candidate {
|
||||
w := runes.WidthAll(c)
|
||||
if w > colWidth {
|
||||
colWidth = w
|
||||
}
|
||||
}
|
||||
colWidth += o.candidateOff + 1
|
||||
same := o.op.buf.RuneSlice(-o.candidateOff)
|
||||
|
||||
// -1 to avoid reach the end of line
|
||||
width := o.width - 1
|
||||
colNum := width / colWidth
|
||||
if colNum != 0 {
|
||||
colWidth += (width - (colWidth * colNum)) / colNum
|
||||
}
|
||||
|
||||
o.candidateColNum = colNum
|
||||
buf := bufio.NewWriter(o.w)
|
||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||
|
||||
colIdx := 0
|
||||
lines := 1
|
||||
buf.WriteString("\033[J")
|
||||
for idx, c := range o.candidate {
|
||||
inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
|
||||
if inSelect {
|
||||
buf.WriteString("\033[30;47m")
|
||||
}
|
||||
buf.WriteString(string(same))
|
||||
buf.WriteString(string(c))
|
||||
buf.Write(bytes.Repeat([]byte(" "), colWidth-runes.WidthAll(c)-runes.WidthAll(same)))
|
||||
|
||||
if inSelect {
|
||||
buf.WriteString("\033[0m")
|
||||
}
|
||||
|
||||
colIdx++
|
||||
if colIdx == colNum {
|
||||
buf.WriteString("\n")
|
||||
lines++
|
||||
colIdx = 0
|
||||
}
|
||||
}
|
||||
|
||||
// move back
|
||||
fmt.Fprintf(buf, "\033[%dA\r", lineCnt-1+lines)
|
||||
fmt.Fprintf(buf, "\033[%dC", o.op.buf.idx+o.op.buf.PromptLen())
|
||||
buf.Flush()
|
||||
}
|
||||
|
||||
func (o *opCompleter) aggCandidate(candidate [][]rune) int {
|
||||
offset := 0
|
||||
for i := 0; i < len(candidate[0]); i++ {
|
||||
for j := 0; j < len(candidate)-1; j++ {
|
||||
if i > len(candidate[j]) {
|
||||
goto aggregate
|
||||
}
|
||||
if candidate[j][i] != candidate[j+1][i] {
|
||||
goto aggregate
|
||||
}
|
||||
}
|
||||
offset = i
|
||||
}
|
||||
aggregate:
|
||||
return offset
|
||||
}
|
||||
|
||||
func (o *opCompleter) EnterCompleteSelectMode() {
|
||||
o.inSelectMode = true
|
||||
o.candidateChoise = -1
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
func (o *opCompleter) EnterCompleteMode(offset int, candidate [][]rune) {
|
||||
o.inCompleteMode = true
|
||||
o.candidate = candidate
|
||||
o.candidateOff = offset
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
|
||||
func (o *opCompleter) ExitCompleteSelectMode() {
|
||||
o.inSelectMode = false
|
||||
o.candidate = nil
|
||||
o.candidateChoise = -1
|
||||
o.candidateOff = -1
|
||||
o.candidateSource = nil
|
||||
}
|
||||
|
||||
func (o *opCompleter) ExitCompleteMode(revent bool) {
|
||||
o.inCompleteMode = false
|
||||
o.ExitCompleteSelectMode()
|
||||
}
|
165
vendor/github.com/chzyer/readline/complete_helper.go
generated
vendored
Normal file
165
vendor/github.com/chzyer/readline/complete_helper.go
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Caller type for dynamic completion
|
||||
type DynamicCompleteFunc func(string) []string
|
||||
|
||||
type PrefixCompleterInterface interface {
|
||||
Print(prefix string, level int, buf *bytes.Buffer)
|
||||
Do(line []rune, pos int) (newLine [][]rune, length int)
|
||||
GetName() []rune
|
||||
GetChildren() []PrefixCompleterInterface
|
||||
SetChildren(children []PrefixCompleterInterface)
|
||||
}
|
||||
|
||||
type DynamicPrefixCompleterInterface interface {
|
||||
PrefixCompleterInterface
|
||||
IsDynamic() bool
|
||||
GetDynamicNames(line []rune) [][]rune
|
||||
}
|
||||
|
||||
type PrefixCompleter struct {
|
||||
Name []rune
|
||||
Dynamic bool
|
||||
Callback DynamicCompleteFunc
|
||||
Children []PrefixCompleterInterface
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) Tree(prefix string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
p.Print(prefix, 0, buf)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func Print(p PrefixCompleterInterface, prefix string, level int, buf *bytes.Buffer) {
|
||||
if strings.TrimSpace(string(p.GetName())) != "" {
|
||||
buf.WriteString(prefix)
|
||||
if level > 0 {
|
||||
buf.WriteString("├")
|
||||
buf.WriteString(strings.Repeat("─", (level*4)-2))
|
||||
buf.WriteString(" ")
|
||||
}
|
||||
buf.WriteString(string(p.GetName()) + "\n")
|
||||
level++
|
||||
}
|
||||
for _, ch := range p.GetChildren() {
|
||||
ch.Print(prefix, level, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) Print(prefix string, level int, buf *bytes.Buffer) {
|
||||
Print(p, prefix, level, buf)
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) IsDynamic() bool {
|
||||
return p.Dynamic
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) GetName() []rune {
|
||||
return p.Name
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) GetDynamicNames(line []rune) [][]rune {
|
||||
var names = [][]rune{}
|
||||
for _, name := range p.Callback(string(line)) {
|
||||
names = append(names, []rune(name+" "))
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) GetChildren() []PrefixCompleterInterface {
|
||||
return p.Children
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) SetChildren(children []PrefixCompleterInterface) {
|
||||
p.Children = children
|
||||
}
|
||||
|
||||
func NewPrefixCompleter(pc ...PrefixCompleterInterface) *PrefixCompleter {
|
||||
return PcItem("", pc...)
|
||||
}
|
||||
|
||||
func PcItem(name string, pc ...PrefixCompleterInterface) *PrefixCompleter {
|
||||
name += " "
|
||||
return &PrefixCompleter{
|
||||
Name: []rune(name),
|
||||
Dynamic: false,
|
||||
Children: pc,
|
||||
}
|
||||
}
|
||||
|
||||
func PcItemDynamic(callback DynamicCompleteFunc, pc ...PrefixCompleterInterface) *PrefixCompleter {
|
||||
return &PrefixCompleter{
|
||||
Callback: callback,
|
||||
Dynamic: true,
|
||||
Children: pc,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrefixCompleter) Do(line []rune, pos int) (newLine [][]rune, offset int) {
|
||||
return doInternal(p, line, pos, line)
|
||||
}
|
||||
|
||||
func Do(p PrefixCompleterInterface, line []rune, pos int) (newLine [][]rune, offset int) {
|
||||
return doInternal(p, line, pos, line)
|
||||
}
|
||||
|
||||
func doInternal(p PrefixCompleterInterface, line []rune, pos int, origLine []rune) (newLine [][]rune, offset int) {
|
||||
line = runes.TrimSpaceLeft(line[:pos])
|
||||
goNext := false
|
||||
var lineCompleter PrefixCompleterInterface
|
||||
for _, child := range p.GetChildren() {
|
||||
childNames := make([][]rune, 1)
|
||||
|
||||
childDynamic, ok := child.(DynamicPrefixCompleterInterface)
|
||||
if ok && childDynamic.IsDynamic() {
|
||||
childNames = childDynamic.GetDynamicNames(origLine)
|
||||
} else {
|
||||
childNames[0] = child.GetName()
|
||||
}
|
||||
|
||||
for _, childName := range childNames {
|
||||
if len(line) >= len(childName) {
|
||||
if runes.HasPrefix(line, childName) {
|
||||
if len(line) == len(childName) {
|
||||
newLine = append(newLine, []rune{' '})
|
||||
} else {
|
||||
newLine = append(newLine, childName)
|
||||
}
|
||||
offset = len(childName)
|
||||
lineCompleter = child
|
||||
goNext = true
|
||||
}
|
||||
} else {
|
||||
if runes.HasPrefix(childName, line) {
|
||||
newLine = append(newLine, childName[len(line):])
|
||||
offset = len(line)
|
||||
lineCompleter = child
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(newLine) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
tmpLine := make([]rune, 0, len(line))
|
||||
for i := offset; i < len(line); i++ {
|
||||
if line[i] == ' ' {
|
||||
continue
|
||||
}
|
||||
|
||||
tmpLine = append(tmpLine, line[i:]...)
|
||||
return doInternal(lineCompleter, tmpLine, len(tmpLine), origLine)
|
||||
}
|
||||
|
||||
if goNext {
|
||||
return doInternal(lineCompleter, nil, 0, origLine)
|
||||
}
|
||||
return
|
||||
}
|
82
vendor/github.com/chzyer/readline/complete_segment.go
generated
vendored
Normal file
82
vendor/github.com/chzyer/readline/complete_segment.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
package readline
|
||||
|
||||
type SegmentCompleter interface {
|
||||
// a
|
||||
// |- a1
|
||||
// |--- a11
|
||||
// |- a2
|
||||
// b
|
||||
// input:
|
||||
// DoTree([], 0) [a, b]
|
||||
// DoTree([a], 1) [a]
|
||||
// DoTree([a, ], 0) [a1, a2]
|
||||
// DoTree([a, a], 1) [a1, a2]
|
||||
// DoTree([a, a1], 2) [a1]
|
||||
// DoTree([a, a1, ], 0) [a11]
|
||||
// DoTree([a, a1, a], 1) [a11]
|
||||
DoSegment([][]rune, int) [][]rune
|
||||
}
|
||||
|
||||
type dumpSegmentCompleter struct {
|
||||
f func([][]rune, int) [][]rune
|
||||
}
|
||||
|
||||
func (d *dumpSegmentCompleter) DoSegment(segment [][]rune, n int) [][]rune {
|
||||
return d.f(segment, n)
|
||||
}
|
||||
|
||||
func SegmentFunc(f func([][]rune, int) [][]rune) AutoCompleter {
|
||||
return &SegmentComplete{&dumpSegmentCompleter{f}}
|
||||
}
|
||||
|
||||
func SegmentAutoComplete(completer SegmentCompleter) *SegmentComplete {
|
||||
return &SegmentComplete{
|
||||
SegmentCompleter: completer,
|
||||
}
|
||||
}
|
||||
|
||||
type SegmentComplete struct {
|
||||
SegmentCompleter
|
||||
}
|
||||
|
||||
func RetSegment(segments [][]rune, cands [][]rune, idx int) ([][]rune, int) {
|
||||
ret := make([][]rune, 0, len(cands))
|
||||
lastSegment := segments[len(segments)-1]
|
||||
for _, cand := range cands {
|
||||
if !runes.HasPrefix(cand, lastSegment) {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, cand[len(lastSegment):])
|
||||
}
|
||||
return ret, idx
|
||||
}
|
||||
|
||||
func SplitSegment(line []rune, pos int) ([][]rune, int) {
|
||||
segs := [][]rune{}
|
||||
lastIdx := -1
|
||||
line = line[:pos]
|
||||
pos = 0
|
||||
for idx, l := range line {
|
||||
if l == ' ' {
|
||||
pos = 0
|
||||
segs = append(segs, line[lastIdx+1:idx])
|
||||
lastIdx = idx
|
||||
} else {
|
||||
pos++
|
||||
}
|
||||
}
|
||||
segs = append(segs, line[lastIdx+1:])
|
||||
return segs, pos
|
||||
}
|
||||
|
||||
func (c *SegmentComplete) Do(line []rune, pos int) (newLine [][]rune, offset int) {
|
||||
|
||||
segment, idx := SplitSegment(line, pos)
|
||||
|
||||
cands := c.DoSegment(segment, idx)
|
||||
newLine, offset = RetSegment(segment, cands, idx)
|
||||
for idx := range newLine {
|
||||
newLine[idx] = append(newLine[idx], ' ')
|
||||
}
|
||||
return newLine, offset
|
||||
}
|
330
vendor/github.com/chzyer/readline/history.go
generated
vendored
Normal file
330
vendor/github.com/chzyer/readline/history.go
generated
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type hisItem struct {
|
||||
Source []rune
|
||||
Version int64
|
||||
Tmp []rune
|
||||
}
|
||||
|
||||
func (h *hisItem) Clean() {
|
||||
h.Source = nil
|
||||
h.Tmp = nil
|
||||
}
|
||||
|
||||
type opHistory struct {
|
||||
cfg *Config
|
||||
history *list.List
|
||||
historyVer int64
|
||||
current *list.Element
|
||||
fd *os.File
|
||||
fdLock sync.Mutex
|
||||
enable bool
|
||||
}
|
||||
|
||||
func newOpHistory(cfg *Config) (o *opHistory) {
|
||||
o = &opHistory{
|
||||
cfg: cfg,
|
||||
history: list.New(),
|
||||
enable: true,
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *opHistory) Reset() {
|
||||
o.history = list.New()
|
||||
o.current = nil
|
||||
}
|
||||
|
||||
func (o *opHistory) IsHistoryClosed() bool {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
return o.fd.Fd() == ^(uintptr(0))
|
||||
}
|
||||
|
||||
func (o *opHistory) Init() {
|
||||
if o.IsHistoryClosed() {
|
||||
o.initHistory()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) initHistory() {
|
||||
if o.cfg.HistoryFile != "" {
|
||||
o.historyUpdatePath(o.cfg.HistoryFile)
|
||||
}
|
||||
}
|
||||
|
||||
// only called by newOpHistory
|
||||
func (o *opHistory) historyUpdatePath(path string) {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
o.fd = f
|
||||
r := bufio.NewReader(o.fd)
|
||||
total := 0
|
||||
for ; ; total++ {
|
||||
line, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
// ignore the empty line
|
||||
line = strings.TrimSpace(line)
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
o.Push([]rune(line))
|
||||
o.Compact()
|
||||
}
|
||||
if total > o.cfg.HistoryLimit {
|
||||
o.rewriteLocked()
|
||||
}
|
||||
o.historyVer++
|
||||
o.Push(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Compact() {
|
||||
for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 {
|
||||
o.history.Remove(o.history.Front())
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) Rewrite() {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
o.rewriteLocked()
|
||||
}
|
||||
|
||||
func (o *opHistory) rewriteLocked() {
|
||||
if o.cfg.HistoryFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
tmpFile := o.cfg.HistoryFile + ".tmp"
|
||||
fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
buf := bufio.NewWriter(fd)
|
||||
for elem := o.history.Front(); elem != nil; elem = elem.Next() {
|
||||
buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n")
|
||||
}
|
||||
buf.Flush()
|
||||
|
||||
// replace history file
|
||||
if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil {
|
||||
fd.Close()
|
||||
return
|
||||
}
|
||||
|
||||
if o.fd != nil {
|
||||
o.fd.Close()
|
||||
}
|
||||
// fd is write only, just satisfy what we need.
|
||||
o.fd = fd
|
||||
}
|
||||
|
||||
func (o *opHistory) Close() {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
if o.fd != nil {
|
||||
o.fd.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Prev() {
|
||||
item := o.showItem(elem.Value)
|
||||
if isNewSearch {
|
||||
start += len(rs)
|
||||
}
|
||||
if elem == o.current {
|
||||
if len(item) >= start {
|
||||
item = item[:start]
|
||||
}
|
||||
}
|
||||
idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) {
|
||||
for elem := o.current; elem != nil; elem = elem.Next() {
|
||||
item := o.showItem(elem.Value)
|
||||
if isNewSearch {
|
||||
start -= len(rs)
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
}
|
||||
if elem == o.current {
|
||||
if len(item)-1 >= start {
|
||||
item = item[start:]
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold)
|
||||
if idx < 0 {
|
||||
continue
|
||||
}
|
||||
if elem == o.current {
|
||||
idx += start
|
||||
}
|
||||
return idx, elem
|
||||
}
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
func (o *opHistory) showItem(obj interface{}) []rune {
|
||||
item := obj.(*hisItem)
|
||||
if item.Version == o.historyVer {
|
||||
return item.Tmp
|
||||
}
|
||||
return item.Source
|
||||
}
|
||||
|
||||
func (o *opHistory) Prev() []rune {
|
||||
if o.current == nil {
|
||||
return nil
|
||||
}
|
||||
current := o.current.Prev()
|
||||
if current == nil {
|
||||
return nil
|
||||
}
|
||||
o.current = current
|
||||
return runes.Copy(o.showItem(current.Value))
|
||||
}
|
||||
|
||||
func (o *opHistory) Next() ([]rune, bool) {
|
||||
if o.current == nil {
|
||||
return nil, false
|
||||
}
|
||||
current := o.current.Next()
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
o.current = current
|
||||
return runes.Copy(o.showItem(current.Value)), true
|
||||
}
|
||||
|
||||
// Disable the current history
|
||||
func (o *opHistory) Disable() {
|
||||
o.enable = false
|
||||
}
|
||||
|
||||
// Enable the current history
|
||||
func (o *opHistory) Enable() {
|
||||
o.enable = true
|
||||
}
|
||||
|
||||
func (o *opHistory) debug() {
|
||||
Debug("-------")
|
||||
for item := o.history.Front(); item != nil; item = item.Next() {
|
||||
Debug(fmt.Sprintf("%+v", item.Value))
|
||||
}
|
||||
}
|
||||
|
||||
// save history
|
||||
func (o *opHistory) New(current []rune) (err error) {
|
||||
|
||||
// history deactivated
|
||||
if !o.enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
current = runes.Copy(current)
|
||||
|
||||
// if just use last command without modify
|
||||
// just clean lastest history
|
||||
if back := o.history.Back(); back != nil {
|
||||
prev := back.Prev()
|
||||
if prev != nil {
|
||||
if runes.Equal(current, prev.Value.(*hisItem).Source) {
|
||||
o.current = o.history.Back()
|
||||
o.current.Value.(*hisItem).Clean()
|
||||
o.historyVer++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(current) == 0 {
|
||||
o.current = o.history.Back()
|
||||
if o.current != nil {
|
||||
o.current.Value.(*hisItem).Clean()
|
||||
o.historyVer++
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if o.current != o.history.Back() {
|
||||
// move history item to current command
|
||||
currentItem := o.current.Value.(*hisItem)
|
||||
// set current to last item
|
||||
o.current = o.history.Back()
|
||||
|
||||
current = runes.Copy(currentItem.Tmp)
|
||||
}
|
||||
|
||||
// err only can be a IO error, just report
|
||||
err = o.Update(current, true)
|
||||
|
||||
// push a new one to commit current command
|
||||
o.historyVer++
|
||||
o.Push(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Revert() {
|
||||
o.historyVer++
|
||||
o.current = o.history.Back()
|
||||
}
|
||||
|
||||
func (o *opHistory) Update(s []rune, commit bool) (err error) {
|
||||
o.fdLock.Lock()
|
||||
defer o.fdLock.Unlock()
|
||||
s = runes.Copy(s)
|
||||
if o.current == nil {
|
||||
o.Push(s)
|
||||
o.Compact()
|
||||
return
|
||||
}
|
||||
r := o.current.Value.(*hisItem)
|
||||
r.Version = o.historyVer
|
||||
if commit {
|
||||
r.Source = s
|
||||
if o.fd != nil {
|
||||
// just report the error
|
||||
_, err = o.fd.Write([]byte(string(r.Source) + "\n"))
|
||||
}
|
||||
} else {
|
||||
r.Tmp = append(r.Tmp[:0], s...)
|
||||
}
|
||||
o.current.Value = r
|
||||
o.Compact()
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opHistory) Push(s []rune) {
|
||||
s = runes.Copy(s)
|
||||
elem := o.history.PushBack(&hisItem{Source: s})
|
||||
o.current = elem
|
||||
}
|
531
vendor/github.com/chzyer/readline/operation.go
generated
vendored
Normal file
531
vendor/github.com/chzyer/readline/operation.go
generated
vendored
Normal file
@@ -0,0 +1,531 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInterrupt = errors.New("Interrupt")
|
||||
)
|
||||
|
||||
type InterruptError struct {
|
||||
Line []rune
|
||||
}
|
||||
|
||||
func (*InterruptError) Error() string {
|
||||
return "Interrupted"
|
||||
}
|
||||
|
||||
type Operation struct {
|
||||
m sync.Mutex
|
||||
cfg *Config
|
||||
t *Terminal
|
||||
buf *RuneBuffer
|
||||
outchan chan []rune
|
||||
errchan chan error
|
||||
w io.Writer
|
||||
|
||||
history *opHistory
|
||||
*opSearch
|
||||
*opCompleter
|
||||
*opPassword
|
||||
*opVim
|
||||
}
|
||||
|
||||
func (o *Operation) SetBuffer(what string) {
|
||||
o.buf.Set([]rune(what))
|
||||
}
|
||||
|
||||
type wrapWriter struct {
|
||||
r *Operation
|
||||
t *Terminal
|
||||
target io.Writer
|
||||
}
|
||||
|
||||
func (w *wrapWriter) Write(b []byte) (int, error) {
|
||||
if !w.t.IsReading() {
|
||||
return w.target.Write(b)
|
||||
}
|
||||
|
||||
var (
|
||||
n int
|
||||
err error
|
||||
)
|
||||
w.r.buf.Refresh(func() {
|
||||
n, err = w.target.Write(b)
|
||||
})
|
||||
|
||||
if w.r.IsSearchMode() {
|
||||
w.r.SearchRefresh(-1)
|
||||
}
|
||||
if w.r.IsInCompleteMode() {
|
||||
w.r.CompleteRefresh()
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||
width := cfg.FuncGetWidth()
|
||||
op := &Operation{
|
||||
t: t,
|
||||
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width),
|
||||
outchan: make(chan []rune),
|
||||
errchan: make(chan error, 1),
|
||||
}
|
||||
op.w = op.buf.w
|
||||
op.SetConfig(cfg)
|
||||
op.opVim = newVimMode(op)
|
||||
op.opCompleter = newOpCompleter(op.buf.w, op, width)
|
||||
op.opPassword = newOpPassword(op)
|
||||
op.cfg.FuncOnWidthChanged(func() {
|
||||
newWidth := cfg.FuncGetWidth()
|
||||
op.opCompleter.OnWidthChange(newWidth)
|
||||
op.opSearch.OnWidthChange(newWidth)
|
||||
op.buf.OnWidthChange(newWidth)
|
||||
})
|
||||
go op.ioloop()
|
||||
return op
|
||||
}
|
||||
|
||||
func (o *Operation) SetPrompt(s string) {
|
||||
o.buf.SetPrompt(s)
|
||||
}
|
||||
|
||||
func (o *Operation) SetMaskRune(r rune) {
|
||||
o.buf.SetMask(r)
|
||||
}
|
||||
|
||||
func (o *Operation) GetConfig() *Config {
|
||||
o.m.Lock()
|
||||
cfg := *o.cfg
|
||||
o.m.Unlock()
|
||||
return &cfg
|
||||
}
|
||||
|
||||
func (o *Operation) ioloop() {
|
||||
for {
|
||||
keepInSearchMode := false
|
||||
keepInCompleteMode := false
|
||||
r := o.t.ReadRune()
|
||||
if o.GetConfig().FuncFilterInputRune != nil {
|
||||
var process bool
|
||||
r, process = o.GetConfig().FuncFilterInputRune(r)
|
||||
if !process {
|
||||
o.buf.Refresh(nil) // to refresh the line
|
||||
continue // ignore this rune
|
||||
}
|
||||
}
|
||||
|
||||
if r == 0 { // io.EOF
|
||||
if o.buf.Len() == 0 {
|
||||
o.buf.Clean()
|
||||
select {
|
||||
case o.errchan <- io.EOF:
|
||||
}
|
||||
break
|
||||
} else {
|
||||
// if stdin got io.EOF and there is something left in buffer,
|
||||
// let's flush them by sending CharEnter.
|
||||
// And we will got io.EOF int next loop.
|
||||
r = CharEnter
|
||||
}
|
||||
}
|
||||
isUpdateHistory := true
|
||||
|
||||
if o.IsInCompleteSelectMode() {
|
||||
keepInCompleteMode = o.HandleCompleteSelect(r)
|
||||
if keepInCompleteMode {
|
||||
continue
|
||||
}
|
||||
|
||||
o.buf.Refresh(nil)
|
||||
switch r {
|
||||
case CharEnter, CharCtrlJ:
|
||||
o.history.Update(o.buf.Runes(), false)
|
||||
fallthrough
|
||||
case CharInterrupt:
|
||||
o.t.KickRead()
|
||||
fallthrough
|
||||
case CharBell:
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if o.IsEnableVimMode() {
|
||||
r = o.HandleVim(r, o.t.ReadRune)
|
||||
if r == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch r {
|
||||
case CharBell:
|
||||
if o.IsSearchMode() {
|
||||
o.ExitSearchMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
if o.IsInCompleteMode() {
|
||||
o.ExitCompleteMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
case CharTab:
|
||||
if o.GetConfig().AutoComplete == nil {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
if o.OnComplete() {
|
||||
keepInCompleteMode = true
|
||||
} else {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
|
||||
case CharBckSearch:
|
||||
if !o.SearchMode(S_DIR_BCK) {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
keepInSearchMode = true
|
||||
case CharCtrlU:
|
||||
o.buf.KillFront()
|
||||
case CharFwdSearch:
|
||||
if !o.SearchMode(S_DIR_FWD) {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
keepInSearchMode = true
|
||||
case CharKill:
|
||||
o.buf.Kill()
|
||||
keepInCompleteMode = true
|
||||
case MetaForward:
|
||||
o.buf.MoveToNextWord()
|
||||
case CharTranspose:
|
||||
o.buf.Transpose()
|
||||
case MetaBackward:
|
||||
o.buf.MoveToPrevWord()
|
||||
case MetaDelete:
|
||||
o.buf.DeleteWord()
|
||||
case CharLineStart:
|
||||
o.buf.MoveToLineStart()
|
||||
case CharLineEnd:
|
||||
o.buf.MoveToLineEnd()
|
||||
case CharBackspace, CharCtrlH:
|
||||
if o.IsSearchMode() {
|
||||
o.SearchBackspace()
|
||||
keepInSearchMode = true
|
||||
break
|
||||
}
|
||||
|
||||
if o.buf.Len() == 0 {
|
||||
o.t.Bell()
|
||||
break
|
||||
}
|
||||
o.buf.Backspace()
|
||||
if o.IsInCompleteMode() {
|
||||
o.OnComplete()
|
||||
}
|
||||
case CharCtrlZ:
|
||||
o.buf.Clean()
|
||||
o.t.SleepToResume()
|
||||
o.Refresh()
|
||||
case CharCtrlL:
|
||||
ClearScreen(o.w)
|
||||
o.Refresh()
|
||||
case MetaBackspace, CharCtrlW:
|
||||
o.buf.BackEscapeWord()
|
||||
case CharCtrlY:
|
||||
o.buf.Yank()
|
||||
case CharEnter, CharCtrlJ:
|
||||
if o.IsSearchMode() {
|
||||
o.ExitSearchMode(false)
|
||||
}
|
||||
o.buf.MoveToLineEnd()
|
||||
var data []rune
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
o.buf.WriteRune('\n')
|
||||
data = o.buf.Reset()
|
||||
data = data[:len(data)-1] // trim \n
|
||||
} else {
|
||||
o.buf.Clean()
|
||||
data = o.buf.Reset()
|
||||
}
|
||||
o.outchan <- data
|
||||
if !o.GetConfig().DisableAutoSaveHistory {
|
||||
// ignore IO error
|
||||
_ = o.history.New(data)
|
||||
} else {
|
||||
isUpdateHistory = false
|
||||
}
|
||||
case CharBackward:
|
||||
o.buf.MoveBackward()
|
||||
case CharForward:
|
||||
o.buf.MoveForward()
|
||||
case CharPrev:
|
||||
buf := o.history.Prev()
|
||||
if buf != nil {
|
||||
o.buf.Set(buf)
|
||||
} else {
|
||||
o.t.Bell()
|
||||
}
|
||||
case CharNext:
|
||||
buf, ok := o.history.Next()
|
||||
if ok {
|
||||
o.buf.Set(buf)
|
||||
} else {
|
||||
o.t.Bell()
|
||||
}
|
||||
case CharDelete:
|
||||
if o.buf.Len() > 0 || !o.IsNormalMode() {
|
||||
o.t.KickRead()
|
||||
if !o.buf.Delete() {
|
||||
o.t.Bell()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// treat as EOF
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")
|
||||
}
|
||||
o.buf.Reset()
|
||||
isUpdateHistory = false
|
||||
o.history.Revert()
|
||||
o.errchan <- io.EOF
|
||||
if o.GetConfig().UniqueEditLine {
|
||||
o.buf.Clean()
|
||||
}
|
||||
case CharInterrupt:
|
||||
if o.IsSearchMode() {
|
||||
o.t.KickRead()
|
||||
o.ExitSearchMode(true)
|
||||
break
|
||||
}
|
||||
if o.IsInCompleteMode() {
|
||||
o.t.KickRead()
|
||||
o.ExitCompleteMode(true)
|
||||
o.buf.Refresh(nil)
|
||||
break
|
||||
}
|
||||
o.buf.MoveToLineEnd()
|
||||
o.buf.Refresh(nil)
|
||||
hint := o.GetConfig().InterruptPrompt + "\n"
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
o.buf.WriteString(hint)
|
||||
}
|
||||
remain := o.buf.Reset()
|
||||
if !o.GetConfig().UniqueEditLine {
|
||||
remain = remain[:len(remain)-len([]rune(hint))]
|
||||
}
|
||||
isUpdateHistory = false
|
||||
o.history.Revert()
|
||||
o.errchan <- &InterruptError{remain}
|
||||
default:
|
||||
if o.IsSearchMode() {
|
||||
o.SearchChar(r)
|
||||
keepInSearchMode = true
|
||||
break
|
||||
}
|
||||
o.buf.WriteRune(r)
|
||||
if o.IsInCompleteMode() {
|
||||
o.OnComplete()
|
||||
keepInCompleteMode = true
|
||||
}
|
||||
}
|
||||
|
||||
listener := o.GetConfig().Listener
|
||||
if listener != nil {
|
||||
newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
|
||||
if ok {
|
||||
o.buf.SetWithIdx(newPos, newLine)
|
||||
}
|
||||
}
|
||||
|
||||
o.m.Lock()
|
||||
if !keepInSearchMode && o.IsSearchMode() {
|
||||
o.ExitSearchMode(false)
|
||||
o.buf.Refresh(nil)
|
||||
} else if o.IsInCompleteMode() {
|
||||
if !keepInCompleteMode {
|
||||
o.ExitCompleteMode(false)
|
||||
o.Refresh()
|
||||
} else {
|
||||
o.buf.Refresh(nil)
|
||||
o.CompleteRefresh()
|
||||
}
|
||||
}
|
||||
if isUpdateHistory && !o.IsSearchMode() {
|
||||
// it will cause null history
|
||||
o.history.Update(o.buf.Runes(), false)
|
||||
}
|
||||
o.m.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Operation) Stderr() io.Writer {
|
||||
return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t}
|
||||
}
|
||||
|
||||
func (o *Operation) Stdout() io.Writer {
|
||||
return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t}
|
||||
}
|
||||
|
||||
func (o *Operation) String() (string, error) {
|
||||
r, err := o.Runes()
|
||||
return string(r), err
|
||||
}
|
||||
|
||||
func (o *Operation) Runes() ([]rune, error) {
|
||||
o.t.EnterRawMode()
|
||||
defer o.t.ExitRawMode()
|
||||
|
||||
listener := o.GetConfig().Listener
|
||||
if listener != nil {
|
||||
listener.OnChange(nil, 0, 0)
|
||||
}
|
||||
|
||||
o.buf.Refresh(nil) // print prompt
|
||||
o.t.KickRead()
|
||||
select {
|
||||
case r := <-o.outchan:
|
||||
return r, nil
|
||||
case err := <-o.errchan:
|
||||
if e, ok := err.(*InterruptError); ok {
|
||||
return e.Line, ErrInterrupt
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) {
|
||||
cfg := o.GenPasswordConfig()
|
||||
cfg.Prompt = prompt
|
||||
cfg.Listener = l
|
||||
return o.PasswordWithConfig(cfg)
|
||||
}
|
||||
|
||||
func (o *Operation) GenPasswordConfig() *Config {
|
||||
return o.opPassword.PasswordConfig()
|
||||
}
|
||||
|
||||
func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {
|
||||
if err := o.opPassword.EnterPasswordMode(cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer o.opPassword.ExitPasswordMode()
|
||||
return o.Slice()
|
||||
}
|
||||
|
||||
func (o *Operation) Password(prompt string) ([]byte, error) {
|
||||
return o.PasswordEx(prompt, nil)
|
||||
}
|
||||
|
||||
func (o *Operation) SetTitle(t string) {
|
||||
o.w.Write([]byte("\033[2;" + t + "\007"))
|
||||
}
|
||||
|
||||
func (o *Operation) Slice() ([]byte, error) {
|
||||
r, err := o.Runes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(string(r)), nil
|
||||
}
|
||||
|
||||
func (o *Operation) Close() {
|
||||
o.history.Close()
|
||||
}
|
||||
|
||||
func (o *Operation) SetHistoryPath(path string) {
|
||||
if o.history != nil {
|
||||
o.history.Close()
|
||||
}
|
||||
o.cfg.HistoryFile = path
|
||||
o.history = newOpHistory(o.cfg)
|
||||
}
|
||||
|
||||
func (o *Operation) IsNormalMode() bool {
|
||||
return !o.IsInCompleteMode() && !o.IsSearchMode()
|
||||
}
|
||||
|
||||
func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
||||
op.m.Lock()
|
||||
defer op.m.Unlock()
|
||||
if op.cfg == cfg {
|
||||
return op.cfg, nil
|
||||
}
|
||||
if err := cfg.Init(); err != nil {
|
||||
return op.cfg, err
|
||||
}
|
||||
old := op.cfg
|
||||
op.cfg = cfg
|
||||
op.SetPrompt(cfg.Prompt)
|
||||
op.SetMaskRune(cfg.MaskRune)
|
||||
op.buf.SetConfig(cfg)
|
||||
width := op.cfg.FuncGetWidth()
|
||||
|
||||
if cfg.opHistory == nil {
|
||||
op.SetHistoryPath(cfg.HistoryFile)
|
||||
cfg.opHistory = op.history
|
||||
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width)
|
||||
}
|
||||
op.history = cfg.opHistory
|
||||
|
||||
// SetHistoryPath will close opHistory which already exists
|
||||
// so if we use it next time, we need to reopen it by `InitHistory()`
|
||||
op.history.Init()
|
||||
|
||||
if op.cfg.AutoComplete != nil {
|
||||
op.opCompleter = newOpCompleter(op.buf.w, op, width)
|
||||
}
|
||||
|
||||
op.opSearch = cfg.opSearch
|
||||
return old, nil
|
||||
}
|
||||
|
||||
func (o *Operation) ResetHistory() {
|
||||
o.history.Reset()
|
||||
}
|
||||
|
||||
// if err is not nil, it just mean it fail to write to file
|
||||
// other things goes fine.
|
||||
func (o *Operation) SaveHistory(content string) error {
|
||||
return o.history.New([]rune(content))
|
||||
}
|
||||
|
||||
func (o *Operation) Refresh() {
|
||||
if o.t.IsReading() {
|
||||
o.buf.Refresh(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *Operation) Clean() {
|
||||
o.buf.Clean()
|
||||
}
|
||||
|
||||
func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener {
|
||||
return &DumpListener{f: f}
|
||||
}
|
||||
|
||||
type DumpListener struct {
|
||||
f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
|
||||
}
|
||||
|
||||
func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
return d.f(line, pos, key)
|
||||
}
|
||||
|
||||
type Listener interface {
|
||||
OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
|
||||
}
|
||||
|
||||
type Painter interface {
|
||||
Paint(line []rune, pos int) []rune
|
||||
}
|
||||
|
||||
type defaultPainter struct{}
|
||||
|
||||
func (p *defaultPainter) Paint(line []rune, _ int) []rune {
|
||||
return line
|
||||
}
|
33
vendor/github.com/chzyer/readline/password.go
generated
vendored
Normal file
33
vendor/github.com/chzyer/readline/password.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package readline
|
||||
|
||||
type opPassword struct {
|
||||
o *Operation
|
||||
backupCfg *Config
|
||||
}
|
||||
|
||||
func newOpPassword(o *Operation) *opPassword {
|
||||
return &opPassword{o: o}
|
||||
}
|
||||
|
||||
func (o *opPassword) ExitPasswordMode() {
|
||||
o.o.SetConfig(o.backupCfg)
|
||||
o.backupCfg = nil
|
||||
}
|
||||
|
||||
func (o *opPassword) EnterPasswordMode(cfg *Config) (err error) {
|
||||
o.backupCfg, err = o.o.SetConfig(cfg)
|
||||
return
|
||||
}
|
||||
|
||||
func (o *opPassword) PasswordConfig() *Config {
|
||||
return &Config{
|
||||
EnableMask: true,
|
||||
InterruptPrompt: "\n",
|
||||
EOFPrompt: "\n",
|
||||
HistoryLimit: -1,
|
||||
Painter: &defaultPainter{},
|
||||
|
||||
Stdout: o.o.cfg.Stdout,
|
||||
Stderr: o.o.cfg.Stderr,
|
||||
}
|
||||
}
|
125
vendor/github.com/chzyer/readline/rawreader_windows.go
generated
vendored
Normal file
125
vendor/github.com/chzyer/readline/rawreader_windows.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const (
|
||||
VK_CANCEL = 0x03
|
||||
VK_BACK = 0x08
|
||||
VK_TAB = 0x09
|
||||
VK_RETURN = 0x0D
|
||||
VK_SHIFT = 0x10
|
||||
VK_CONTROL = 0x11
|
||||
VK_MENU = 0x12
|
||||
VK_ESCAPE = 0x1B
|
||||
VK_LEFT = 0x25
|
||||
VK_UP = 0x26
|
||||
VK_RIGHT = 0x27
|
||||
VK_DOWN = 0x28
|
||||
VK_DELETE = 0x2E
|
||||
VK_LSHIFT = 0xA0
|
||||
VK_RSHIFT = 0xA1
|
||||
VK_LCONTROL = 0xA2
|
||||
VK_RCONTROL = 0xA3
|
||||
)
|
||||
|
||||
// RawReader translate input record to ANSI escape sequence.
|
||||
// To provides same behavior as unix terminal.
|
||||
type RawReader struct {
|
||||
ctrlKey bool
|
||||
altKey bool
|
||||
}
|
||||
|
||||
func NewRawReader() *RawReader {
|
||||
r := new(RawReader)
|
||||
return r
|
||||
}
|
||||
|
||||
// only process one action in one read
|
||||
func (r *RawReader) Read(buf []byte) (int, error) {
|
||||
ir := new(_INPUT_RECORD)
|
||||
var read int
|
||||
var err error
|
||||
next:
|
||||
err = kernel.ReadConsoleInputW(stdin,
|
||||
uintptr(unsafe.Pointer(ir)),
|
||||
1,
|
||||
uintptr(unsafe.Pointer(&read)),
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if ir.EventType != EVENT_KEY {
|
||||
goto next
|
||||
}
|
||||
ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0]))
|
||||
if ker.bKeyDown == 0 { // keyup
|
||||
if r.ctrlKey || r.altKey {
|
||||
switch ker.wVirtualKeyCode {
|
||||
case VK_RCONTROL, VK_LCONTROL:
|
||||
r.ctrlKey = false
|
||||
case VK_MENU: //alt
|
||||
r.altKey = false
|
||||
}
|
||||
}
|
||||
goto next
|
||||
}
|
||||
|
||||
if ker.unicodeChar == 0 {
|
||||
var target rune
|
||||
switch ker.wVirtualKeyCode {
|
||||
case VK_RCONTROL, VK_LCONTROL:
|
||||
r.ctrlKey = true
|
||||
case VK_MENU: //alt
|
||||
r.altKey = true
|
||||
case VK_LEFT:
|
||||
target = CharBackward
|
||||
case VK_RIGHT:
|
||||
target = CharForward
|
||||
case VK_UP:
|
||||
target = CharPrev
|
||||
case VK_DOWN:
|
||||
target = CharNext
|
||||
}
|
||||
if target != 0 {
|
||||
return r.write(buf, target)
|
||||
}
|
||||
goto next
|
||||
}
|
||||
char := rune(ker.unicodeChar)
|
||||
if r.ctrlKey {
|
||||
switch char {
|
||||
case 'A':
|
||||
char = CharLineStart
|
||||
case 'E':
|
||||
char = CharLineEnd
|
||||
case 'R':
|
||||
char = CharBckSearch
|
||||
case 'S':
|
||||
char = CharFwdSearch
|
||||
}
|
||||
} else if r.altKey {
|
||||
switch char {
|
||||
case VK_BACK:
|
||||
char = CharBackspace
|
||||
}
|
||||
return r.writeEsc(buf, char)
|
||||
}
|
||||
return r.write(buf, char)
|
||||
}
|
||||
|
||||
func (r *RawReader) writeEsc(b []byte, char rune) (int, error) {
|
||||
b[0] = '\033'
|
||||
n := copy(b[1:], []byte(string(char)))
|
||||
return n + 1, nil
|
||||
}
|
||||
|
||||
func (r *RawReader) write(b []byte, char rune) (int, error) {
|
||||
n := copy(b, []byte(string(char)))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *RawReader) Close() error {
|
||||
return nil
|
||||
}
|
326
vendor/github.com/chzyer/readline/readline.go
generated
vendored
Normal file
326
vendor/github.com/chzyer/readline/readline.go
generated
vendored
Normal file
@@ -0,0 +1,326 @@
|
||||
// Readline is a pure go implementation for GNU-Readline kind library.
|
||||
//
|
||||
// example:
|
||||
// rl, err := readline.New("> ")
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// defer rl.Close()
|
||||
//
|
||||
// for {
|
||||
// line, err := rl.Readline()
|
||||
// if err != nil { // io.EOF
|
||||
// break
|
||||
// }
|
||||
// println(line)
|
||||
// }
|
||||
//
|
||||
package readline
|
||||
|
||||
import "io"
|
||||
|
||||
type Instance struct {
|
||||
Config *Config
|
||||
Terminal *Terminal
|
||||
Operation *Operation
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
// prompt supports ANSI escape sequence, so we can color some characters even in windows
|
||||
Prompt string
|
||||
|
||||
// readline will persist historys to file where HistoryFile specified
|
||||
HistoryFile string
|
||||
// specify the max length of historys, it's 500 by default, set it to -1 to disable history
|
||||
HistoryLimit int
|
||||
DisableAutoSaveHistory bool
|
||||
// enable case-insensitive history searching
|
||||
HistorySearchFold bool
|
||||
|
||||
// AutoCompleter will called once user press TAB
|
||||
AutoComplete AutoCompleter
|
||||
|
||||
// Any key press will pass to Listener
|
||||
// NOTE: Listener will be triggered by (nil, 0, 0) immediately
|
||||
Listener Listener
|
||||
|
||||
Painter Painter
|
||||
|
||||
// If VimMode is true, readline will in vim.insert mode by default
|
||||
VimMode bool
|
||||
|
||||
InterruptPrompt string
|
||||
EOFPrompt string
|
||||
|
||||
FuncGetWidth func() int
|
||||
|
||||
Stdin io.ReadCloser
|
||||
StdinWriter io.Writer
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
EnableMask bool
|
||||
MaskRune rune
|
||||
|
||||
// erase the editing line after user submited it
|
||||
// it use in IM usually.
|
||||
UniqueEditLine bool
|
||||
|
||||
// filter input runes (may be used to disable CtrlZ or for translating some keys to different actions)
|
||||
// -> output = new (translated) rune and true/false if continue with processing this one
|
||||
FuncFilterInputRune func(rune) (rune, bool)
|
||||
|
||||
// force use interactive even stdout is not a tty
|
||||
FuncIsTerminal func() bool
|
||||
FuncMakeRaw func() error
|
||||
FuncExitRaw func() error
|
||||
FuncOnWidthChanged func(func())
|
||||
ForceUseInteractive bool
|
||||
|
||||
// private fields
|
||||
inited bool
|
||||
opHistory *opHistory
|
||||
opSearch *opSearch
|
||||
}
|
||||
|
||||
func (c *Config) useInteractive() bool {
|
||||
if c.ForceUseInteractive {
|
||||
return true
|
||||
}
|
||||
return c.FuncIsTerminal()
|
||||
}
|
||||
|
||||
func (c *Config) Init() error {
|
||||
if c.inited {
|
||||
return nil
|
||||
}
|
||||
c.inited = true
|
||||
if c.Stdin == nil {
|
||||
c.Stdin = NewCancelableStdin(Stdin)
|
||||
}
|
||||
|
||||
c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin)
|
||||
|
||||
if c.Stdout == nil {
|
||||
c.Stdout = Stdout
|
||||
}
|
||||
if c.Stderr == nil {
|
||||
c.Stderr = Stderr
|
||||
}
|
||||
if c.HistoryLimit == 0 {
|
||||
c.HistoryLimit = 500
|
||||
}
|
||||
|
||||
if c.InterruptPrompt == "" {
|
||||
c.InterruptPrompt = "^C"
|
||||
} else if c.InterruptPrompt == "\n" {
|
||||
c.InterruptPrompt = ""
|
||||
}
|
||||
if c.EOFPrompt == "" {
|
||||
c.EOFPrompt = "^D"
|
||||
} else if c.EOFPrompt == "\n" {
|
||||
c.EOFPrompt = ""
|
||||
}
|
||||
|
||||
if c.AutoComplete == nil {
|
||||
c.AutoComplete = &TabCompleter{}
|
||||
}
|
||||
if c.FuncGetWidth == nil {
|
||||
c.FuncGetWidth = GetScreenWidth
|
||||
}
|
||||
if c.FuncIsTerminal == nil {
|
||||
c.FuncIsTerminal = DefaultIsTerminal
|
||||
}
|
||||
rm := new(RawMode)
|
||||
if c.FuncMakeRaw == nil {
|
||||
c.FuncMakeRaw = rm.Enter
|
||||
}
|
||||
if c.FuncExitRaw == nil {
|
||||
c.FuncExitRaw = rm.Exit
|
||||
}
|
||||
if c.FuncOnWidthChanged == nil {
|
||||
c.FuncOnWidthChanged = DefaultOnWidthChanged
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) Clone() *Config {
|
||||
c.opHistory = nil
|
||||
c.opSearch = nil
|
||||
return &c
|
||||
}
|
||||
|
||||
func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) {
|
||||
c.Listener = FuncListener(f)
|
||||
}
|
||||
|
||||
func (c *Config) SetPainter(p Painter) {
|
||||
c.Painter = p
|
||||
}
|
||||
|
||||
func NewEx(cfg *Config) (*Instance, error) {
|
||||
t, err := NewTerminal(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rl := t.Readline()
|
||||
if cfg.Painter == nil {
|
||||
cfg.Painter = &defaultPainter{}
|
||||
}
|
||||
return &Instance{
|
||||
Config: cfg,
|
||||
Terminal: t,
|
||||
Operation: rl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func New(prompt string) (*Instance, error) {
|
||||
return NewEx(&Config{Prompt: prompt})
|
||||
}
|
||||
|
||||
func (i *Instance) ResetHistory() {
|
||||
i.Operation.ResetHistory()
|
||||
}
|
||||
|
||||
func (i *Instance) SetPrompt(s string) {
|
||||
i.Operation.SetPrompt(s)
|
||||
}
|
||||
|
||||
func (i *Instance) SetMaskRune(r rune) {
|
||||
i.Operation.SetMaskRune(r)
|
||||
}
|
||||
|
||||
// change history persistence in runtime
|
||||
func (i *Instance) SetHistoryPath(p string) {
|
||||
i.Operation.SetHistoryPath(p)
|
||||
}
|
||||
|
||||
// readline will refresh automatic when write through Stdout()
|
||||
func (i *Instance) Stdout() io.Writer {
|
||||
return i.Operation.Stdout()
|
||||
}
|
||||
|
||||
// readline will refresh automatic when write through Stdout()
|
||||
func (i *Instance) Stderr() io.Writer {
|
||||
return i.Operation.Stderr()
|
||||
}
|
||||
|
||||
// switch VimMode in runtime
|
||||
func (i *Instance) SetVimMode(on bool) {
|
||||
i.Operation.SetVimMode(on)
|
||||
}
|
||||
|
||||
func (i *Instance) IsVimMode() bool {
|
||||
return i.Operation.IsEnableVimMode()
|
||||
}
|
||||
|
||||
func (i *Instance) GenPasswordConfig() *Config {
|
||||
return i.Operation.GenPasswordConfig()
|
||||
}
|
||||
|
||||
// we can generate a config by `i.GenPasswordConfig()`
|
||||
func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) {
|
||||
return i.Operation.PasswordWithConfig(cfg)
|
||||
}
|
||||
|
||||
func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) {
|
||||
return i.Operation.PasswordEx(prompt, l)
|
||||
}
|
||||
|
||||
func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
|
||||
return i.Operation.Password(prompt)
|
||||
}
|
||||
|
||||
type Result struct {
|
||||
Line string
|
||||
Error error
|
||||
}
|
||||
|
||||
func (l *Result) CanContinue() bool {
|
||||
return len(l.Line) != 0 && l.Error == ErrInterrupt
|
||||
}
|
||||
|
||||
func (l *Result) CanBreak() bool {
|
||||
return !l.CanContinue() && l.Error != nil
|
||||
}
|
||||
|
||||
func (i *Instance) Line() *Result {
|
||||
ret, err := i.Readline()
|
||||
return &Result{ret, err}
|
||||
}
|
||||
|
||||
// err is one of (nil, io.EOF, readline.ErrInterrupt)
|
||||
func (i *Instance) Readline() (string, error) {
|
||||
return i.Operation.String()
|
||||
}
|
||||
|
||||
func (i *Instance) ReadlineWithDefault(what string) (string, error) {
|
||||
i.Operation.SetBuffer(what)
|
||||
return i.Operation.String()
|
||||
}
|
||||
|
||||
func (i *Instance) SaveHistory(content string) error {
|
||||
return i.Operation.SaveHistory(content)
|
||||
}
|
||||
|
||||
// same as readline
|
||||
func (i *Instance) ReadSlice() ([]byte, error) {
|
||||
return i.Operation.Slice()
|
||||
}
|
||||
|
||||
// we must make sure that call Close() before process exit.
|
||||
func (i *Instance) Close() error {
|
||||
if err := i.Terminal.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Config.Stdin.Close()
|
||||
i.Operation.Close()
|
||||
return nil
|
||||
}
|
||||
func (i *Instance) Clean() {
|
||||
i.Operation.Clean()
|
||||
}
|
||||
|
||||
func (i *Instance) Write(b []byte) (int, error) {
|
||||
return i.Stdout().Write(b)
|
||||
}
|
||||
|
||||
// WriteStdin prefill the next Stdin fetch
|
||||
// Next time you call ReadLine() this value will be writen before the user input
|
||||
// ie :
|
||||
// i := readline.New()
|
||||
// i.WriteStdin([]byte("test"))
|
||||
// _, _= i.Readline()
|
||||
//
|
||||
// gives
|
||||
//
|
||||
// > test[cursor]
|
||||
func (i *Instance) WriteStdin(val []byte) (int, error) {
|
||||
return i.Terminal.WriteStdin(val)
|
||||
}
|
||||
|
||||
func (i *Instance) SetConfig(cfg *Config) *Config {
|
||||
if i.Config == cfg {
|
||||
return cfg
|
||||
}
|
||||
old := i.Config
|
||||
i.Config = cfg
|
||||
i.Operation.SetConfig(cfg)
|
||||
i.Terminal.SetConfig(cfg)
|
||||
return old
|
||||
}
|
||||
|
||||
func (i *Instance) Refresh() {
|
||||
i.Operation.Refresh()
|
||||
}
|
||||
|
||||
// HistoryDisable the save of the commands into the history
|
||||
func (i *Instance) HistoryDisable() {
|
||||
i.Operation.history.Disable()
|
||||
}
|
||||
|
||||
// HistoryEnable the save of the commands into the history (default on)
|
||||
func (i *Instance) HistoryEnable() {
|
||||
i.Operation.history.Enable()
|
||||
}
|
475
vendor/github.com/chzyer/readline/remote.go
generated
vendored
Normal file
475
vendor/github.com/chzyer/readline/remote.go
generated
vendored
Normal file
@@ -0,0 +1,475 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type MsgType int16
|
||||
|
||||
const (
|
||||
T_DATA = MsgType(iota)
|
||||
T_WIDTH
|
||||
T_WIDTH_REPORT
|
||||
T_ISTTY_REPORT
|
||||
T_RAW
|
||||
T_ERAW // exit raw
|
||||
T_EOF
|
||||
)
|
||||
|
||||
type RemoteSvr struct {
|
||||
eof int32
|
||||
closed int32
|
||||
width int32
|
||||
reciveChan chan struct{}
|
||||
writeChan chan *writeCtx
|
||||
conn net.Conn
|
||||
isTerminal bool
|
||||
funcWidthChan func()
|
||||
stopChan chan struct{}
|
||||
|
||||
dataBufM sync.Mutex
|
||||
dataBuf bytes.Buffer
|
||||
}
|
||||
|
||||
type writeReply struct {
|
||||
n int
|
||||
err error
|
||||
}
|
||||
|
||||
type writeCtx struct {
|
||||
msg *Message
|
||||
reply chan *writeReply
|
||||
}
|
||||
|
||||
func newWriteCtx(msg *Message) *writeCtx {
|
||||
return &writeCtx{
|
||||
msg: msg,
|
||||
reply: make(chan *writeReply),
|
||||
}
|
||||
}
|
||||
|
||||
func NewRemoteSvr(conn net.Conn) (*RemoteSvr, error) {
|
||||
rs := &RemoteSvr{
|
||||
width: -1,
|
||||
conn: conn,
|
||||
writeChan: make(chan *writeCtx),
|
||||
reciveChan: make(chan struct{}),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
buf := bufio.NewReader(rs.conn)
|
||||
|
||||
if err := rs.init(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go rs.readLoop(buf)
|
||||
go rs.writeLoop()
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) init(buf *bufio.Reader) error {
|
||||
m, err := ReadMessage(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// receive isTerminal
|
||||
if m.Type != T_ISTTY_REPORT {
|
||||
return fmt.Errorf("unexpected init message")
|
||||
}
|
||||
r.GotIsTerminal(m.Data)
|
||||
|
||||
// receive width
|
||||
m, err = ReadMessage(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m.Type != T_WIDTH_REPORT {
|
||||
return fmt.Errorf("unexpected init message")
|
||||
}
|
||||
r.GotReportWidth(m.Data)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) HandleConfig(cfg *Config) {
|
||||
cfg.Stderr = r
|
||||
cfg.Stdout = r
|
||||
cfg.Stdin = r
|
||||
cfg.FuncExitRaw = r.ExitRawMode
|
||||
cfg.FuncIsTerminal = r.IsTerminal
|
||||
cfg.FuncMakeRaw = r.EnterRawMode
|
||||
cfg.FuncExitRaw = r.ExitRawMode
|
||||
cfg.FuncGetWidth = r.GetWidth
|
||||
cfg.FuncOnWidthChanged = func(f func()) {
|
||||
r.funcWidthChan = f
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) IsTerminal() bool {
|
||||
return r.isTerminal
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) checkEOF() error {
|
||||
if atomic.LoadInt32(&r.eof) == 1 {
|
||||
return io.EOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) Read(b []byte) (int, error) {
|
||||
r.dataBufM.Lock()
|
||||
n, err := r.dataBuf.Read(b)
|
||||
r.dataBufM.Unlock()
|
||||
if n == 0 {
|
||||
if err := r.checkEOF(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
if n == 0 && err == io.EOF {
|
||||
<-r.reciveChan
|
||||
r.dataBufM.Lock()
|
||||
n, err = r.dataBuf.Read(b)
|
||||
r.dataBufM.Unlock()
|
||||
}
|
||||
if n == 0 {
|
||||
if err := r.checkEOF(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) writeMsg(m *Message) error {
|
||||
ctx := newWriteCtx(m)
|
||||
r.writeChan <- ctx
|
||||
reply := <-ctx.reply
|
||||
return reply.err
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) Write(b []byte) (int, error) {
|
||||
ctx := newWriteCtx(NewMessage(T_DATA, b))
|
||||
r.writeChan <- ctx
|
||||
reply := <-ctx.reply
|
||||
return reply.n, reply.err
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) EnterRawMode() error {
|
||||
return r.writeMsg(NewMessage(T_RAW, nil))
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) ExitRawMode() error {
|
||||
return r.writeMsg(NewMessage(T_ERAW, nil))
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) writeLoop() {
|
||||
defer r.Close()
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case ctx, ok := <-r.writeChan:
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
n, err := ctx.msg.WriteTo(r.conn)
|
||||
ctx.reply <- &writeReply{n, err}
|
||||
case <-r.stopChan:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&r.closed, 0, 1) {
|
||||
close(r.stopChan)
|
||||
r.conn.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) readLoop(buf *bufio.Reader) {
|
||||
defer r.Close()
|
||||
for {
|
||||
m, err := ReadMessage(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch m.Type {
|
||||
case T_EOF:
|
||||
atomic.StoreInt32(&r.eof, 1)
|
||||
select {
|
||||
case r.reciveChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case T_DATA:
|
||||
r.dataBufM.Lock()
|
||||
r.dataBuf.Write(m.Data)
|
||||
r.dataBufM.Unlock()
|
||||
select {
|
||||
case r.reciveChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
case T_WIDTH_REPORT:
|
||||
r.GotReportWidth(m.Data)
|
||||
case T_ISTTY_REPORT:
|
||||
r.GotIsTerminal(m.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) GotIsTerminal(data []byte) {
|
||||
if binary.BigEndian.Uint16(data) == 0 {
|
||||
r.isTerminal = false
|
||||
} else {
|
||||
r.isTerminal = true
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) GotReportWidth(data []byte) {
|
||||
atomic.StoreInt32(&r.width, int32(binary.BigEndian.Uint16(data)))
|
||||
if r.funcWidthChan != nil {
|
||||
r.funcWidthChan()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteSvr) GetWidth() int {
|
||||
return int(atomic.LoadInt32(&r.width))
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type Message struct {
|
||||
Type MsgType
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func ReadMessage(r io.Reader) (*Message, error) {
|
||||
m := new(Message)
|
||||
var length int32
|
||||
if err := binary.Read(r, binary.BigEndian, &length); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := binary.Read(r, binary.BigEndian, &m.Type); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m.Data = make([]byte, int(length)-2)
|
||||
if _, err := io.ReadFull(r, m.Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func NewMessage(t MsgType, data []byte) *Message {
|
||||
return &Message{t, data}
|
||||
}
|
||||
|
||||
func (m *Message) WriteTo(w io.Writer) (int, error) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, len(m.Data)+2+4))
|
||||
binary.Write(buf, binary.BigEndian, int32(len(m.Data)+2))
|
||||
binary.Write(buf, binary.BigEndian, m.Type)
|
||||
buf.Write(m.Data)
|
||||
n, err := buf.WriteTo(w)
|
||||
return int(n), err
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
type RemoteCli struct {
|
||||
conn net.Conn
|
||||
raw RawMode
|
||||
receiveChan chan struct{}
|
||||
inited int32
|
||||
isTerminal *bool
|
||||
|
||||
data bytes.Buffer
|
||||
dataM sync.Mutex
|
||||
}
|
||||
|
||||
func NewRemoteCli(conn net.Conn) (*RemoteCli, error) {
|
||||
r := &RemoteCli{
|
||||
conn: conn,
|
||||
receiveChan: make(chan struct{}),
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) MarkIsTerminal(is bool) {
|
||||
r.isTerminal = &is
|
||||
}
|
||||
|
||||
func (r *RemoteCli) init() error {
|
||||
if !atomic.CompareAndSwapInt32(&r.inited, 0, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := r.reportIsTerminal(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.reportWidth(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// register sig for width changed
|
||||
DefaultOnWidthChanged(func() {
|
||||
r.reportWidth()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) writeMsg(m *Message) error {
|
||||
r.dataM.Lock()
|
||||
_, err := m.WriteTo(r.conn)
|
||||
r.dataM.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *RemoteCli) Write(b []byte) (int, error) {
|
||||
m := NewMessage(T_DATA, b)
|
||||
r.dataM.Lock()
|
||||
_, err := m.WriteTo(r.conn)
|
||||
r.dataM.Unlock()
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
func (r *RemoteCli) reportWidth() error {
|
||||
screenWidth := GetScreenWidth()
|
||||
data := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(data, uint16(screenWidth))
|
||||
msg := NewMessage(T_WIDTH_REPORT, data)
|
||||
|
||||
if err := r.writeMsg(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) reportIsTerminal() error {
|
||||
var isTerminal bool
|
||||
if r.isTerminal != nil {
|
||||
isTerminal = *r.isTerminal
|
||||
} else {
|
||||
isTerminal = DefaultIsTerminal()
|
||||
}
|
||||
data := make([]byte, 2)
|
||||
if isTerminal {
|
||||
binary.BigEndian.PutUint16(data, 1)
|
||||
} else {
|
||||
binary.BigEndian.PutUint16(data, 0)
|
||||
}
|
||||
msg := NewMessage(T_ISTTY_REPORT, data)
|
||||
if err := r.writeMsg(msg); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) readLoop() {
|
||||
buf := bufio.NewReader(r.conn)
|
||||
for {
|
||||
msg, err := ReadMessage(buf)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
switch msg.Type {
|
||||
case T_ERAW:
|
||||
r.raw.Exit()
|
||||
case T_RAW:
|
||||
r.raw.Enter()
|
||||
case T_DATA:
|
||||
os.Stdout.Write(msg.Data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RemoteCli) ServeBy(source io.Reader) error {
|
||||
if err := r.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer r.Close()
|
||||
for {
|
||||
n, _ := io.Copy(r, source)
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
defer r.raw.Exit()
|
||||
r.readLoop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RemoteCli) Close() {
|
||||
r.writeMsg(NewMessage(T_EOF, nil))
|
||||
}
|
||||
|
||||
func (r *RemoteCli) Serve() error {
|
||||
return r.ServeBy(os.Stdin)
|
||||
}
|
||||
|
||||
func ListenRemote(n, addr string, cfg *Config, h func(*Instance), onListen ...func(net.Listener) error) error {
|
||||
ln, err := net.Listen(n, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(onListen) > 0 {
|
||||
if err := onListen[0](ln); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
go func() {
|
||||
defer conn.Close()
|
||||
rl, err := HandleConn(*cfg, conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h(rl)
|
||||
}()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleConn(cfg Config, conn net.Conn) (*Instance, error) {
|
||||
r, err := NewRemoteSvr(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.HandleConfig(&cfg)
|
||||
|
||||
rl, err := NewEx(&cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rl, nil
|
||||
}
|
||||
|
||||
func DialRemote(n, addr string) error {
|
||||
conn, err := net.Dial(n, addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
cli, err := NewRemoteCli(conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return cli.Serve()
|
||||
}
|
629
vendor/github.com/chzyer/readline/runebuf.go
generated
vendored
Normal file
629
vendor/github.com/chzyer/readline/runebuf.go
generated
vendored
Normal file
@@ -0,0 +1,629 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type runeBufferBck struct {
|
||||
buf []rune
|
||||
idx int
|
||||
}
|
||||
|
||||
type RuneBuffer struct {
|
||||
buf []rune
|
||||
idx int
|
||||
prompt []rune
|
||||
w io.Writer
|
||||
|
||||
hadClean bool
|
||||
interactive bool
|
||||
cfg *Config
|
||||
|
||||
width int
|
||||
|
||||
bck *runeBufferBck
|
||||
|
||||
offset string
|
||||
|
||||
lastKill []rune
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (r* RuneBuffer) pushKill(text []rune) {
|
||||
r.lastKill = append([]rune{}, text...)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) OnWidthChange(newWidth int) {
|
||||
r.Lock()
|
||||
r.width = newWidth
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Backup() {
|
||||
r.Lock()
|
||||
r.bck = &runeBufferBck{r.buf, r.idx}
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Restore() {
|
||||
r.Refresh(func() {
|
||||
if r.bck == nil {
|
||||
return
|
||||
}
|
||||
r.buf = r.bck.buf
|
||||
r.idx = r.bck.idx
|
||||
})
|
||||
}
|
||||
|
||||
func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int) *RuneBuffer {
|
||||
rb := &RuneBuffer{
|
||||
w: w,
|
||||
interactive: cfg.useInteractive(),
|
||||
cfg: cfg,
|
||||
width: width,
|
||||
}
|
||||
rb.SetPrompt(prompt)
|
||||
return rb
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetConfig(cfg *Config) {
|
||||
r.Lock()
|
||||
r.cfg = cfg
|
||||
r.interactive = cfg.useInteractive()
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetMask(m rune) {
|
||||
r.Lock()
|
||||
r.cfg.MaskRune = m
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) CurrentWidth(x int) int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return runes.WidthAll(r.buf[:x])
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) PromptLen() int {
|
||||
r.Lock()
|
||||
width := r.promptLen()
|
||||
r.Unlock()
|
||||
return width
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) promptLen() int {
|
||||
return runes.WidthAll(runes.ColorFilter(r.prompt))
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) RuneSlice(i int) []rune {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if i > 0 {
|
||||
rs := make([]rune, i)
|
||||
copy(rs, r.buf[r.idx:r.idx+i])
|
||||
return rs
|
||||
}
|
||||
rs := make([]rune, -i)
|
||||
copy(rs, r.buf[r.idx+i:r.idx])
|
||||
return rs
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Runes() []rune {
|
||||
r.Lock()
|
||||
newr := make([]rune, len(r.buf))
|
||||
copy(newr, r.buf)
|
||||
r.Unlock()
|
||||
return newr
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Pos() int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idx
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Len() int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return len(r.buf)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToLineStart() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
r.idx = 0
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveBackward() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
r.idx--
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) WriteString(s string) {
|
||||
r.WriteRunes([]rune(s))
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) WriteRune(s rune) {
|
||||
r.WriteRunes([]rune{s})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) WriteRunes(s []rune) {
|
||||
r.Refresh(func() {
|
||||
tail := append(s, r.buf[r.idx:]...)
|
||||
r.buf = append(r.buf[:r.idx], tail...)
|
||||
r.idx += len(s)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveForward() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
r.idx++
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) IsCursorInEnd() bool {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idx == len(r.buf)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Replace(ch rune) {
|
||||
r.Refresh(func() {
|
||||
r.buf[r.idx] = ch
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Erase() {
|
||||
r.Refresh(func() {
|
||||
r.idx = 0
|
||||
r.pushKill(r.buf[:])
|
||||
r.buf = r.buf[:0]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Delete() (success bool) {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
r.pushKill(r.buf[r.idx : r.idx+1])
|
||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||
success = true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) DeleteWord() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
init := r.idx
|
||||
for init < len(r.buf) && IsWordBreak(r.buf[init]) {
|
||||
init++
|
||||
}
|
||||
for i := init + 1; i < len(r.buf); i++ {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.pushKill(r.buf[r.idx:i-1])
|
||||
r.Refresh(func() {
|
||||
r.buf = append(r.buf[:r.idx], r.buf[i-1:]...)
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
r.Kill()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToPrevWord() (success bool) {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := r.idx - 1; i > 0; i-- {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
r.idx = 0
|
||||
success = true
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) KillFront() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
length := len(r.buf) - r.idx
|
||||
r.pushKill(r.buf[:r.idx])
|
||||
copy(r.buf[:length], r.buf[r.idx:])
|
||||
r.idx = 0
|
||||
r.buf = r.buf[:length]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Kill() {
|
||||
r.Refresh(func() {
|
||||
r.pushKill(r.buf[r.idx:])
|
||||
r.buf = r.buf[:r.idx]
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Transpose() {
|
||||
r.Refresh(func() {
|
||||
if len(r.buf) == 1 {
|
||||
r.idx++
|
||||
}
|
||||
|
||||
if len(r.buf) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
if r.idx == 0 {
|
||||
r.idx = 1
|
||||
} else if r.idx >= len(r.buf) {
|
||||
r.idx = len(r.buf) - 1
|
||||
}
|
||||
r.buf[r.idx], r.buf[r.idx-1] = r.buf[r.idx-1], r.buf[r.idx]
|
||||
r.idx++
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToNextWord() {
|
||||
r.Refresh(func() {
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToEndWord() {
|
||||
r.Refresh(func() {
|
||||
// already at the end, so do nothing
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
// if we are at the end of a word already, go to next
|
||||
if !IsWordBreak(r.buf[r.idx]) && IsWordBreak(r.buf[r.idx+1]) {
|
||||
r.idx++
|
||||
}
|
||||
|
||||
// keep going until at the end of a word
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if IsWordBreak(r.buf[i]) && !IsWordBreak(r.buf[i-1]) {
|
||||
r.idx = i - 1
|
||||
return
|
||||
}
|
||||
}
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) BackEscapeWord() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
for i := r.idx - 1; i > 0; i-- {
|
||||
if !IsWordBreak(r.buf[i]) && IsWordBreak(r.buf[i-1]) {
|
||||
r.pushKill(r.buf[i:r.idx])
|
||||
r.buf = append(r.buf[:i], r.buf[r.idx:]...)
|
||||
r.idx = i
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.buf = r.buf[:0]
|
||||
r.idx = 0
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Yank() {
|
||||
if len(r.lastKill) == 0 {
|
||||
return
|
||||
}
|
||||
r.Refresh(func() {
|
||||
buf := make([]rune, 0, len(r.buf) + len(r.lastKill))
|
||||
buf = append(buf, r.buf[:r.idx]...)
|
||||
buf = append(buf, r.lastKill...)
|
||||
buf = append(buf, r.buf[r.idx:]...)
|
||||
r.buf = buf
|
||||
r.idx += len(r.lastKill)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Backspace() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
r.idx--
|
||||
r.buf = append(r.buf[:r.idx], r.buf[r.idx+1:]...)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveToLineEnd() {
|
||||
r.Refresh(func() {
|
||||
if r.idx == len(r.buf) {
|
||||
return
|
||||
}
|
||||
|
||||
r.idx = len(r.buf)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) LineCount(width int) int {
|
||||
if width == -1 {
|
||||
width = r.width
|
||||
}
|
||||
return LineCount(width,
|
||||
runes.WidthAll(r.buf)+r.PromptLen())
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) MoveTo(ch rune, prevChar, reverse bool) (success bool) {
|
||||
r.Refresh(func() {
|
||||
if reverse {
|
||||
for i := r.idx - 1; i >= 0; i-- {
|
||||
if r.buf[i] == ch {
|
||||
r.idx = i
|
||||
if prevChar {
|
||||
r.idx++
|
||||
}
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
for i := r.idx + 1; i < len(r.buf); i++ {
|
||||
if r.buf[i] == ch {
|
||||
r.idx = i
|
||||
if prevChar {
|
||||
r.idx--
|
||||
}
|
||||
success = true
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) isInLineEdge() bool {
|
||||
if isWindows {
|
||||
return false
|
||||
}
|
||||
sp := r.getSplitByLine(r.buf)
|
||||
return len(sp[len(sp)-1]) == 0
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) getSplitByLine(rs []rune) []string {
|
||||
return SplitByLine(r.promptLen(), r.width, rs)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) IdxLine(width int) int {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
return r.idxLine(width)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) idxLine(width int) int {
|
||||
if width == 0 {
|
||||
return 0
|
||||
}
|
||||
sp := r.getSplitByLine(r.buf[:r.idx])
|
||||
return len(sp) - 1
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) CursorLineCount() int {
|
||||
return r.LineCount(r.width) - r.IdxLine(r.width)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Refresh(f func()) {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if !r.interactive {
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
r.clean()
|
||||
if f != nil {
|
||||
f()
|
||||
}
|
||||
r.print()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetOffset(offset string) {
|
||||
r.Lock()
|
||||
r.offset = offset
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) print() {
|
||||
r.w.Write(r.output())
|
||||
r.hadClean = false
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) output() []byte {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString(string(r.prompt))
|
||||
if r.cfg.EnableMask && len(r.buf) > 0 {
|
||||
buf.Write([]byte(strings.Repeat(string(r.cfg.MaskRune), len(r.buf)-1)))
|
||||
if r.buf[len(r.buf)-1] == '\n' {
|
||||
buf.Write([]byte{'\n'})
|
||||
} else {
|
||||
buf.Write([]byte(string(r.cfg.MaskRune)))
|
||||
}
|
||||
if len(r.buf) > r.idx {
|
||||
buf.Write(r.getBackspaceSequence())
|
||||
}
|
||||
|
||||
} else {
|
||||
for _, e := range r.cfg.Painter.Paint(r.buf, r.idx) {
|
||||
if e == '\t' {
|
||||
buf.WriteString(strings.Repeat(" ", TabWidth))
|
||||
} else {
|
||||
buf.WriteRune(e)
|
||||
}
|
||||
}
|
||||
if r.isInLineEdge() {
|
||||
buf.Write([]byte(" \b"))
|
||||
}
|
||||
}
|
||||
// cursor position
|
||||
if len(r.buf) > r.idx {
|
||||
buf.Write(r.getBackspaceSequence())
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) getBackspaceSequence() []byte {
|
||||
var sep = map[int]bool{}
|
||||
|
||||
var i int
|
||||
for {
|
||||
if i >= runes.WidthAll(r.buf) {
|
||||
break
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
i -= r.promptLen()
|
||||
}
|
||||
i += r.width
|
||||
|
||||
sep[i] = true
|
||||
}
|
||||
var buf []byte
|
||||
for i := len(r.buf); i > r.idx; i-- {
|
||||
// move input to the left of one
|
||||
buf = append(buf, '\b')
|
||||
if sep[i] {
|
||||
// up one line, go to the start of the line and move cursor right to the end (r.width)
|
||||
buf = append(buf, "\033[A\r"+"\033["+strconv.Itoa(r.width)+"C"...)
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Reset() []rune {
|
||||
ret := runes.Copy(r.buf)
|
||||
r.buf = r.buf[:0]
|
||||
r.idx = 0
|
||||
return ret
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) calWidth(m int) int {
|
||||
if m > 0 {
|
||||
return runes.WidthAll(r.buf[r.idx : r.idx+m])
|
||||
}
|
||||
return runes.WidthAll(r.buf[r.idx+m : r.idx])
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetStyle(start, end int, style string) {
|
||||
if end < start {
|
||||
panic("end < start")
|
||||
}
|
||||
|
||||
// goto start
|
||||
move := start - r.idx
|
||||
if move > 0 {
|
||||
r.w.Write([]byte(string(r.buf[r.idx : r.idx+move])))
|
||||
} else {
|
||||
r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move)))
|
||||
}
|
||||
r.w.Write([]byte("\033[" + style + "m"))
|
||||
r.w.Write([]byte(string(r.buf[start:end])))
|
||||
r.w.Write([]byte("\033[0m"))
|
||||
// TODO: move back
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {
|
||||
r.Refresh(func() {
|
||||
r.buf = buf
|
||||
r.idx = idx
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Set(buf []rune) {
|
||||
r.SetWithIdx(len(buf), buf)
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) SetPrompt(prompt string) {
|
||||
r.Lock()
|
||||
r.prompt = []rune(prompt)
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
|
||||
buf := bufio.NewWriter(w)
|
||||
|
||||
if r.width == 0 {
|
||||
buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen()))
|
||||
buf.Write([]byte("\033[J"))
|
||||
} else {
|
||||
buf.Write([]byte("\033[J")) // just like ^k :)
|
||||
if idxLine == 0 {
|
||||
buf.WriteString("\033[2K")
|
||||
buf.WriteString("\r")
|
||||
} else {
|
||||
for i := 0; i < idxLine; i++ {
|
||||
io.WriteString(buf, "\033[2K\r\033[A")
|
||||
}
|
||||
io.WriteString(buf, "\033[2K\r")
|
||||
}
|
||||
}
|
||||
buf.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) Clean() {
|
||||
r.Lock()
|
||||
r.clean()
|
||||
r.Unlock()
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) clean() {
|
||||
r.cleanWithIdxLine(r.idxLine(r.width))
|
||||
}
|
||||
|
||||
func (r *RuneBuffer) cleanWithIdxLine(idxLine int) {
|
||||
if r.hadClean || !r.interactive {
|
||||
return
|
||||
}
|
||||
r.hadClean = true
|
||||
r.cleanOutput(r.w, idxLine)
|
||||
}
|
223
vendor/github.com/chzyer/readline/runes.go
generated
vendored
Normal file
223
vendor/github.com/chzyer/readline/runes.go
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var runes = Runes{}
|
||||
var TabWidth = 4
|
||||
|
||||
type Runes struct{}
|
||||
|
||||
func (Runes) EqualRune(a, b rune, fold bool) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if !fold {
|
||||
return false
|
||||
}
|
||||
if a > b {
|
||||
a, b = b, a
|
||||
}
|
||||
if b < utf8.RuneSelf && 'A' <= a && a <= 'Z' {
|
||||
if b == a+'a'-'A' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r Runes) EqualRuneFold(a, b rune) bool {
|
||||
return r.EqualRune(a, b, true)
|
||||
}
|
||||
|
||||
func (r Runes) EqualFold(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if r.EqualRuneFold(a[i], b[i]) {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (Runes) Equal(a, b []rune) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(a); i++ {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (rs Runes) IndexAllBckEx(r, sub []rune, fold bool) int {
|
||||
for i := len(r) - len(sub); i >= 0; i-- {
|
||||
found := true
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if !rs.EqualRune(r[i+j], sub[j], fold) {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Search in runes from end to front
|
||||
func (rs Runes) IndexAllBck(r, sub []rune) int {
|
||||
return rs.IndexAllBckEx(r, sub, false)
|
||||
}
|
||||
|
||||
// Search in runes from front to end
|
||||
func (rs Runes) IndexAll(r, sub []rune) int {
|
||||
return rs.IndexAllEx(r, sub, false)
|
||||
}
|
||||
|
||||
func (rs Runes) IndexAllEx(r, sub []rune, fold bool) int {
|
||||
for i := 0; i < len(r); i++ {
|
||||
found := true
|
||||
if len(r[i:]) < len(sub) {
|
||||
return -1
|
||||
}
|
||||
for j := 0; j < len(sub); j++ {
|
||||
if !rs.EqualRune(r[i+j], sub[j], fold) {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (Runes) Index(r rune, rs []rune) int {
|
||||
for i := 0; i < len(rs); i++ {
|
||||
if rs[i] == r {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (Runes) ColorFilter(r []rune) []rune {
|
||||
newr := make([]rune, 0, len(r))
|
||||
for pos := 0; pos < len(r); pos++ {
|
||||
if r[pos] == '\033' && r[pos+1] == '[' {
|
||||
idx := runes.Index('m', r[pos+2:])
|
||||
if idx == -1 {
|
||||
continue
|
||||
}
|
||||
pos += idx + 2
|
||||
continue
|
||||
}
|
||||
newr = append(newr, r[pos])
|
||||
}
|
||||
return newr
|
||||
}
|
||||
|
||||
var zeroWidth = []*unicode.RangeTable{
|
||||
unicode.Mn,
|
||||
unicode.Me,
|
||||
unicode.Cc,
|
||||
unicode.Cf,
|
||||
}
|
||||
|
||||
var doubleWidth = []*unicode.RangeTable{
|
||||
unicode.Han,
|
||||
unicode.Hangul,
|
||||
unicode.Hiragana,
|
||||
unicode.Katakana,
|
||||
}
|
||||
|
||||
func (Runes) Width(r rune) int {
|
||||
if r == '\t' {
|
||||
return TabWidth
|
||||
}
|
||||
if unicode.IsOneOf(zeroWidth, r) {
|
||||
return 0
|
||||
}
|
||||
if unicode.IsOneOf(doubleWidth, r) {
|
||||
return 2
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func (Runes) WidthAll(r []rune) (length int) {
|
||||
for i := 0; i < len(r); i++ {
|
||||
length += runes.Width(r[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (Runes) Backspace(r []rune) []byte {
|
||||
return bytes.Repeat([]byte{'\b'}, runes.WidthAll(r))
|
||||
}
|
||||
|
||||
func (Runes) Copy(r []rune) []rune {
|
||||
n := make([]rune, len(r))
|
||||
copy(n, r)
|
||||
return n
|
||||
}
|
||||
|
||||
func (Runes) HasPrefixFold(r, prefix []rune) bool {
|
||||
if len(r) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return runes.EqualFold(r[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func (Runes) HasPrefix(r, prefix []rune) bool {
|
||||
if len(r) < len(prefix) {
|
||||
return false
|
||||
}
|
||||
return runes.Equal(r[:len(prefix)], prefix)
|
||||
}
|
||||
|
||||
func (Runes) Aggregate(candicate [][]rune) (same []rune, size int) {
|
||||
for i := 0; i < len(candicate[0]); i++ {
|
||||
for j := 0; j < len(candicate)-1; j++ {
|
||||
if i >= len(candicate[j]) || i >= len(candicate[j+1]) {
|
||||
goto aggregate
|
||||
}
|
||||
if candicate[j][i] != candicate[j+1][i] {
|
||||
goto aggregate
|
||||
}
|
||||
}
|
||||
size = i + 1
|
||||
}
|
||||
aggregate:
|
||||
if size > 0 {
|
||||
same = runes.Copy(candicate[0][:size])
|
||||
for i := 0; i < len(candicate); i++ {
|
||||
n := runes.Copy(candicate[i])
|
||||
copy(n, n[size:])
|
||||
candicate[i] = n[:len(n)-size]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (Runes) TrimSpaceLeft(in []rune) []rune {
|
||||
firstIndex := len(in)
|
||||
for i, r := range in {
|
||||
if unicode.IsSpace(r) == false {
|
||||
firstIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return in[firstIndex:]
|
||||
}
|
164
vendor/github.com/chzyer/readline/search.go
generated
vendored
Normal file
164
vendor/github.com/chzyer/readline/search.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
const (
|
||||
S_STATE_FOUND = iota
|
||||
S_STATE_FAILING
|
||||
)
|
||||
|
||||
const (
|
||||
S_DIR_BCK = iota
|
||||
S_DIR_FWD
|
||||
)
|
||||
|
||||
type opSearch struct {
|
||||
inMode bool
|
||||
state int
|
||||
dir int
|
||||
source *list.Element
|
||||
w io.Writer
|
||||
buf *RuneBuffer
|
||||
data []rune
|
||||
history *opHistory
|
||||
cfg *Config
|
||||
markStart int
|
||||
markEnd int
|
||||
width int
|
||||
}
|
||||
|
||||
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch {
|
||||
return &opSearch{
|
||||
w: w,
|
||||
buf: buf,
|
||||
cfg: cfg,
|
||||
history: history,
|
||||
width: width,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) OnWidthChange(newWidth int) {
|
||||
o.width = newWidth
|
||||
}
|
||||
|
||||
func (o *opSearch) IsSearchMode() bool {
|
||||
return o.inMode
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchBackspace() {
|
||||
if len(o.data) > 0 {
|
||||
o.data = o.data[:len(o.data)-1]
|
||||
o.search(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) {
|
||||
if o.dir == S_DIR_BCK {
|
||||
return o.history.FindBck(isNewSearch, o.data, o.buf.idx)
|
||||
}
|
||||
return o.history.FindFwd(isNewSearch, o.data, o.buf.idx)
|
||||
}
|
||||
|
||||
func (o *opSearch) search(isChange bool) bool {
|
||||
if len(o.data) == 0 {
|
||||
o.state = S_STATE_FOUND
|
||||
o.SearchRefresh(-1)
|
||||
return true
|
||||
}
|
||||
idx, elem := o.findHistoryBy(isChange)
|
||||
if elem == nil {
|
||||
o.SearchRefresh(-2)
|
||||
return false
|
||||
}
|
||||
o.history.current = elem
|
||||
|
||||
item := o.history.showItem(o.history.current.Value)
|
||||
start, end := 0, 0
|
||||
if o.dir == S_DIR_BCK {
|
||||
start, end = idx, idx+len(o.data)
|
||||
} else {
|
||||
start, end = idx, idx+len(o.data)
|
||||
idx += len(o.data)
|
||||
}
|
||||
o.buf.SetWithIdx(idx, item)
|
||||
o.markStart, o.markEnd = start, end
|
||||
o.SearchRefresh(idx)
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchChar(r rune) {
|
||||
o.data = append(o.data, r)
|
||||
o.search(true)
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchMode(dir int) bool {
|
||||
if o.width == 0 {
|
||||
return false
|
||||
}
|
||||
alreadyInMode := o.inMode
|
||||
o.inMode = true
|
||||
o.dir = dir
|
||||
o.source = o.history.current
|
||||
if alreadyInMode {
|
||||
o.search(false)
|
||||
} else {
|
||||
o.SearchRefresh(-1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *opSearch) ExitSearchMode(revert bool) {
|
||||
if revert {
|
||||
o.history.current = o.source
|
||||
o.buf.Set(o.history.showItem(o.history.current.Value))
|
||||
}
|
||||
o.markStart, o.markEnd = 0, 0
|
||||
o.state = S_STATE_FOUND
|
||||
o.inMode = false
|
||||
o.source = nil
|
||||
o.data = nil
|
||||
}
|
||||
|
||||
func (o *opSearch) SearchRefresh(x int) {
|
||||
if x == -2 {
|
||||
o.state = S_STATE_FAILING
|
||||
} else if x >= 0 {
|
||||
o.state = S_STATE_FOUND
|
||||
}
|
||||
if x < 0 {
|
||||
x = o.buf.idx
|
||||
}
|
||||
x = o.buf.CurrentWidth(x)
|
||||
x += o.buf.PromptLen()
|
||||
x = x % o.width
|
||||
|
||||
if o.markStart > 0 {
|
||||
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
||||
}
|
||||
|
||||
lineCnt := o.buf.CursorLineCount()
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||
buf.WriteString("\033[J")
|
||||
if o.state == S_STATE_FAILING {
|
||||
buf.WriteString("failing ")
|
||||
}
|
||||
if o.dir == S_DIR_BCK {
|
||||
buf.WriteString("bck")
|
||||
} else if o.dir == S_DIR_FWD {
|
||||
buf.WriteString("fwd")
|
||||
}
|
||||
buf.WriteString("-i-search: ")
|
||||
buf.WriteString(string(o.data)) // keyword
|
||||
buf.WriteString("\033[4m \033[0m") // _
|
||||
fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev
|
||||
if x > 0 {
|
||||
fmt.Fprintf(buf, "\033[%dC", x) // move forward
|
||||
}
|
||||
o.w.Write(buf.Bytes())
|
||||
}
|
197
vendor/github.com/chzyer/readline/std.go
generated
vendored
Normal file
197
vendor/github.com/chzyer/readline/std.go
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
Stdin io.ReadCloser = os.Stdin
|
||||
Stdout io.WriteCloser = os.Stdout
|
||||
Stderr io.WriteCloser = os.Stderr
|
||||
)
|
||||
|
||||
var (
|
||||
std *Instance
|
||||
stdOnce sync.Once
|
||||
)
|
||||
|
||||
// global instance will not submit history automatic
|
||||
func getInstance() *Instance {
|
||||
stdOnce.Do(func() {
|
||||
std, _ = NewEx(&Config{
|
||||
DisableAutoSaveHistory: true,
|
||||
})
|
||||
})
|
||||
return std
|
||||
}
|
||||
|
||||
// let readline load history from filepath
|
||||
// and try to persist history into disk
|
||||
// set fp to "" to prevent readline persisting history to disk
|
||||
// so the `AddHistory` will return nil error forever.
|
||||
func SetHistoryPath(fp string) {
|
||||
ins := getInstance()
|
||||
cfg := ins.Config.Clone()
|
||||
cfg.HistoryFile = fp
|
||||
ins.SetConfig(cfg)
|
||||
}
|
||||
|
||||
// set auto completer to global instance
|
||||
func SetAutoComplete(completer AutoCompleter) {
|
||||
ins := getInstance()
|
||||
cfg := ins.Config.Clone()
|
||||
cfg.AutoComplete = completer
|
||||
ins.SetConfig(cfg)
|
||||
}
|
||||
|
||||
// add history to global instance manually
|
||||
// raise error only if `SetHistoryPath` is set with a non-empty path
|
||||
func AddHistory(content string) error {
|
||||
ins := getInstance()
|
||||
return ins.SaveHistory(content)
|
||||
}
|
||||
|
||||
func Password(prompt string) ([]byte, error) {
|
||||
ins := getInstance()
|
||||
return ins.ReadPassword(prompt)
|
||||
}
|
||||
|
||||
// readline with global configs
|
||||
func Line(prompt string) (string, error) {
|
||||
ins := getInstance()
|
||||
ins.SetPrompt(prompt)
|
||||
return ins.Readline()
|
||||
}
|
||||
|
||||
type CancelableStdin struct {
|
||||
r io.Reader
|
||||
mutex sync.Mutex
|
||||
stop chan struct{}
|
||||
closed int32
|
||||
notify chan struct{}
|
||||
data []byte
|
||||
read int
|
||||
err error
|
||||
}
|
||||
|
||||
func NewCancelableStdin(r io.Reader) *CancelableStdin {
|
||||
c := &CancelableStdin{
|
||||
r: r,
|
||||
notify: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
go c.ioloop()
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CancelableStdin) ioloop() {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-c.notify:
|
||||
c.read, c.err = c.r.Read(c.data)
|
||||
select {
|
||||
case c.notify <- struct{}{}:
|
||||
case <-c.stop:
|
||||
break loop
|
||||
}
|
||||
case <-c.stop:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CancelableStdin) Read(b []byte) (n int, err error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
if atomic.LoadInt32(&c.closed) == 1 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
c.data = b
|
||||
select {
|
||||
case c.notify <- struct{}{}:
|
||||
case <-c.stop:
|
||||
return 0, io.EOF
|
||||
}
|
||||
select {
|
||||
case <-c.notify:
|
||||
return c.read, c.err
|
||||
case <-c.stop:
|
||||
return 0, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CancelableStdin) Close() error {
|
||||
if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
|
||||
close(c.stop)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillableStdin is a stdin reader which can prepend some data before
|
||||
// reading into the real stdin
|
||||
type FillableStdin struct {
|
||||
sync.Mutex
|
||||
stdin io.Reader
|
||||
stdinBuffer io.ReadCloser
|
||||
buf []byte
|
||||
bufErr error
|
||||
}
|
||||
|
||||
// NewFillableStdin gives you FillableStdin
|
||||
func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) {
|
||||
r, w := io.Pipe()
|
||||
s := &FillableStdin{
|
||||
stdinBuffer: r,
|
||||
stdin: stdin,
|
||||
}
|
||||
s.ioloop()
|
||||
return s, w
|
||||
}
|
||||
|
||||
func (s *FillableStdin) ioloop() {
|
||||
go func() {
|
||||
for {
|
||||
bufR := make([]byte, 100)
|
||||
var n int
|
||||
n, s.bufErr = s.stdinBuffer.Read(bufR)
|
||||
if s.bufErr != nil {
|
||||
if s.bufErr == io.ErrClosedPipe {
|
||||
break
|
||||
}
|
||||
}
|
||||
s.Lock()
|
||||
s.buf = append(s.buf, bufR[:n]...)
|
||||
s.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Read will read from the local buffer and if no data, read from stdin
|
||||
func (s *FillableStdin) Read(p []byte) (n int, err error) {
|
||||
s.Lock()
|
||||
i := len(s.buf)
|
||||
if len(p) < i {
|
||||
i = len(p)
|
||||
}
|
||||
if i > 0 {
|
||||
n := copy(p, s.buf)
|
||||
s.buf = s.buf[:0]
|
||||
cerr := s.bufErr
|
||||
s.bufErr = nil
|
||||
s.Unlock()
|
||||
return n, cerr
|
||||
}
|
||||
s.Unlock()
|
||||
n, err = s.stdin.Read(p)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (s *FillableStdin) Close() error {
|
||||
s.stdinBuffer.Close()
|
||||
return nil
|
||||
}
|
9
vendor/github.com/chzyer/readline/std_windows.go
generated
vendored
Normal file
9
vendor/github.com/chzyer/readline/std_windows.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
func init() {
|
||||
Stdin = NewRawReader()
|
||||
Stdout = NewANSIWriter(Stdout)
|
||||
Stderr = NewANSIWriter(Stderr)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user