Bo-Yi Wu
5 years ago
committed by
GitHub
307 changed files with 29 additions and 178827 deletions
@ -1,8 +1,8 @@ |
|||||
module github.com/appleboy/drone-packer |
module github.com/appleboy/drone-packer |
||||
|
|
||||
|
go 1.13 |
||||
|
|
||||
require ( |
require ( |
||||
github.com/rs/zerolog v0.0.0-20180928011143-8e36cbf881d3 |
github.com/rs/zerolog v1.15.0 |
||||
github.com/urfave/cli v0.0.0-20180226030253-8e01ec4cd3e2 |
github.com/urfave/cli v1.22.1 |
||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb |
|
||||
golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e |
|
||||
) |
) |
||||
|
@ -1,6 +1,25 @@ |
|||||
github.com/rs/zerolog v0.0.0-20180928011143-8e36cbf881d3 h1:aUpd+wvlP6ZkvMSQElHeMMQ6JkkkjgglOhkN0TDRRRs= |
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
||||
github.com/rs/zerolog v0.0.0-20180928011143-8e36cbf881d3/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= |
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= |
||||
github.com/urfave/cli v0.0.0-20180226030253-8e01ec4cd3e2 h1:98aNRvvwi7jK4ObVo/OnEKs9hw1bZ8JQNpwlB21ILD0= |
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= |
||||
github.com/urfave/cli v0.0.0-20180226030253-8e01ec4cd3e2/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= |
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= |
||||
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= |
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
golang.org/x/sys v0.0.0-20180727230415-bd9dbc187b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
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/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= |
||||
|
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY= |
||||
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= |
||||
|
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= |
||||
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= |
||||
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= |
||||
|
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= |
||||
|
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= |
||||
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= |
||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
||||
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= |
||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
||||
|
@ -1 +0,0 @@ |
|||||
zerolog.io |
|
@ -1,21 +0,0 @@ |
|||||
MIT License |
|
||||
|
|
||||
Copyright (c) 2017 Olivier Poitrey |
|
||||
|
|
||||
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. |
|
@ -1,566 +0,0 @@ |
|||||
# Zero Allocation JSON Logger |
|
||||
|
|
||||
[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/zerolog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/zerolog/master/LICENSE) [![Build Status](https://travis-ci.org/rs/zerolog.svg?branch=master)](https://travis-ci.org/rs/zerolog) [![Coverage](http://gocover.io/_badge/github.com/rs/zerolog)](http://gocover.io/github.com/rs/zerolog) |
|
||||
|
|
||||
The zerolog package provides a fast and simple logger dedicated to JSON output. |
|
||||
|
|
||||
Zerolog's API is designed to provide both a great developer experience and stunning [performance](#benchmarks). Its unique chaining API allows zerolog to write JSON (or CBOR) log events by avoiding allocations and reflection. |
|
||||
|
|
||||
Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a simpler to use API and even better performance. |
|
||||
|
|
||||
To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) `zerolog.ConsoleWriter`. |
|
||||
|
|
||||
![Pretty Logging Image](pretty.png) |
|
||||
|
|
||||
## Who uses zerolog |
|
||||
|
|
||||
Find out [who uses zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog) and add your company / project to the list. |
|
||||
|
|
||||
## Features |
|
||||
|
|
||||
* Blazing fast |
|
||||
* Low to zero allocation |
|
||||
* Level logging |
|
||||
* Sampling |
|
||||
* Hooks |
|
||||
* Contextual fields |
|
||||
* `context.Context` integration |
|
||||
* `net/http` helpers |
|
||||
* JSON and CBOR encoding formats |
|
||||
* Pretty logging for development |
|
||||
|
|
||||
## Installation |
|
||||
|
|
||||
```go |
|
||||
go get -u github.com/rs/zerolog/log |
|
||||
``` |
|
||||
|
|
||||
## Getting Started |
|
||||
|
|
||||
### Simple Logging Example |
|
||||
|
|
||||
For simple logging, import the global logger package **github.com/rs/zerolog/log** |
|
||||
|
|
||||
```go |
|
||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"github.com/rs/zerolog" |
|
||||
"github.com/rs/zerolog/log" |
|
||||
) |
|
||||
|
|
||||
func main() { |
|
||||
// UNIX Time is faster and smaller than most timestamps |
|
||||
// If you set zerolog.TimeFieldFormat to an empty string, |
|
||||
// logs will write with UNIX time |
|
||||
zerolog.TimeFieldFormat = "" |
|
||||
|
|
||||
log.Print("hello world") |
|
||||
} |
|
||||
|
|
||||
// Output: {"time":1516134303,"level":"debug","message":"hello world"} |
|
||||
``` |
|
||||
> Note: By default log writes to `os.Stderr` |
|
||||
> Note: The default log level for `log.Print` is *debug* |
|
||||
|
|
||||
### Contextual Logging |
|
||||
|
|
||||
**zerolog** allows data to be added to log messages in the form of key:value pairs. The data added to the message adds "context" about the log event that can be critical for debugging as well as myriad other purposes. An example of this is below: |
|
||||
|
|
||||
```go |
|
||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"github.com/rs/zerolog" |
|
||||
"github.com/rs/zerolog/log" |
|
||||
) |
|
||||
|
|
||||
func main() { |
|
||||
zerolog.TimeFieldFormat = "" |
|
||||
|
|
||||
log.Debug(). |
|
||||
Str("Scale", "833 cents"). |
|
||||
Float64("Interval", 833.09). |
|
||||
Msg("Fibonacci is everywhere") |
|
||||
} |
|
||||
|
|
||||
// Output: {"time":1524104936,"level":"debug","Scale":"833 cents","Interval":833.09,"message":"Fibonacci is everywhere"} |
|
||||
``` |
|
||||
|
|
||||
> You'll note in the above example that when adding contextual fields, the fields are strongly typed. You can find the full list of supported fields [here](#standard-types) |
|
||||
|
|
||||
### Leveled Logging |
|
||||
|
|
||||
#### Simple Leveled Logging Example |
|
||||
|
|
||||
```go |
|
||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"github.com/rs/zerolog" |
|
||||
"github.com/rs/zerolog/log" |
|
||||
) |
|
||||
|
|
||||
func main() { |
|
||||
zerolog.TimeFieldFormat = "" |
|
||||
|
|
||||
log.Info().Msg("hello world") |
|
||||
} |
|
||||
|
|
||||
// Output: {"time":1516134303,"level":"info","message":"hello world"} |
|
||||
``` |
|
||||
|
|
||||
> It is very important to note that when using the **zerolog** chaining API, as shown above (`log.Info().Msg("hello world"`), the chain must have either the `Msg` or `Msgf` method call. If you forget to add either of these, the log will not occur and there is no compile time error to alert you of this. |
|
||||
|
|
||||
**zerolog** allows for logging at the following levels (from highest to lowest): |
|
||||
|
|
||||
* panic (`zerolog.PanicLevel`, 5) |
|
||||
* fatal (`zerolog.FatalLevel`, 4) |
|
||||
* error (`zerolog.ErrorLevel`, 3) |
|
||||
* warn (`zerolog.WarnLevel`, 2) |
|
||||
* info (`zerolog.InfoLevel`, 1) |
|
||||
* debug (`zerolog.DebugLevel`, 0) |
|
||||
|
|
||||
You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant. |
|
||||
|
|
||||
#### Setting Global Log Level |
|
||||
|
|
||||
This example uses command-line flags to demonstrate various outputs depending on the chosen log level. |
|
||||
|
|
||||
```go |
|
||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"flag" |
|
||||
|
|
||||
"github.com/rs/zerolog" |
|
||||
"github.com/rs/zerolog/log" |
|
||||
) |
|
||||
|
|
||||
func main() { |
|
||||
zerolog.TimeFieldFormat = "" |
|
||||
debug := flag.Bool("debug", false, "sets log level to debug") |
|
||||
|
|
||||
flag.Parse() |
|
||||
|
|
||||
// Default level for this example is info, unless debug flag is present |
|
||||
zerolog.SetGlobalLevel(zerolog.InfoLevel) |
|
||||
if *debug { |
|
||||
zerolog.SetGlobalLevel(zerolog.DebugLevel) |
|
||||
} |
|
||||
|
|
||||
log.Debug().Msg("This message appears only when log level set to Debug") |
|
||||
log.Info().Msg("This message appears when log level set to Debug or Info") |
|
||||
|
|
||||
if e := log.Debug(); e.Enabled() { |
|
||||
// Compute log output only if enabled. |
|
||||
value := "bar" |
|
||||
e.Str("foo", value).Msg("some debug message") |
|
||||
} |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
Info Output (no flag) |
|
||||
|
|
||||
```bash |
|
||||
$ ./logLevelExample |
|
||||
{"time":1516387492,"level":"info","message":"This message appears when log level set to Debug or Info"} |
|
||||
``` |
|
||||
|
|
||||
Debug Output (debug flag set) |
|
||||
|
|
||||
```bash |
|
||||
$ ./logLevelExample -debug |
|
||||
{"time":1516387573,"level":"debug","message":"This message appears only when log level set to Debug"} |
|
||||
{"time":1516387573,"level":"info","message":"This message appears when log level set to Debug or Info"} |
|
||||
{"time":1516387573,"level":"debug","foo":"bar","message":"some debug message"} |
|
||||
``` |
|
||||
|
|
||||
#### Logging without Level or Message |
|
||||
|
|
||||
You may choose to log without a specific level by using the `Log` method. You may also write without a message by setting an empty string in the `msg string` parameter of the `Msg` method. Both are demonstrated in the example below. |
|
||||
|
|
||||
```go |
|
||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"github.com/rs/zerolog" |
|
||||
"github.com/rs/zerolog/log" |
|
||||
) |
|
||||
|
|
||||
func main() { |
|
||||
zerolog.TimeFieldFormat = "" |
|
||||
|
|
||||
log.Log(). |
|
||||
Str("foo", "bar"). |
|
||||
Msg("") |
|
||||
} |
|
||||
|
|
||||
// Output: {"time":1494567715,"foo":"bar"} |
|
||||
``` |
|
||||
|
|
||||
#### Logging Fatal Messages |
|
||||
|
|
||||
```go |
|
||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
|
|
||||
"github.com/rs/zerolog" |
|
||||
"github.com/rs/zerolog/log" |
|
||||
) |
|
||||
|
|
||||
func main() { |
|
||||
err := errors.New("A repo man spends his life getting into tense situations") |
|
||||
service := "myservice" |
|
||||
|
|
||||
zerolog.TimeFieldFormat = "" |
|
||||
|
|
||||
log.Fatal(). |
|
||||
Err(err). |
|
||||
Str("service", service). |
|
||||
Msgf("Cannot start %s", service) |
|
||||
} |
|
||||
|
|
||||
// Output: {"time":1516133263,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} |
|
||||
// exit status 1 |
|
||||
``` |
|
||||
|
|
||||
> NOTE: Using `Msgf` generates one allocation even when the logger is disabled. |
|
||||
|
|
||||
### Create logger instance to manage different outputs |
|
||||
|
|
||||
```go |
|
||||
logger := zerolog.New(os.Stderr).With().Timestamp().Logger() |
|
||||
|
|
||||
logger.Info().Str("foo", "bar").Msg("hello world") |
|
||||
|
|
||||
// Output: {"level":"info","time":1494567715,"message":"hello world","foo":"bar"} |
|
||||
``` |
|
||||
|
|
||||
### Sub-loggers let you chain loggers with additional context |
|
||||
|
|
||||
```go |
|
||||
sublogger := log.With(). |
|
||||
Str("component", "foo"). |
|
||||
Logger() |
|
||||
sublogger.Info().Msg("hello world") |
|
||||
|
|
||||
// Output: {"level":"info","time":1494567715,"message":"hello world","component":"foo"} |
|
||||
``` |
|
||||
|
|
||||
### Pretty logging |
|
||||
|
|
||||
```go |
|
||||
if isConsole { |
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) |
|
||||
} |
|
||||
|
|
||||
log.Info().Str("foo", "bar").Msg("Hello world") |
|
||||
|
|
||||
// Output: 1494567715 |INFO| Hello world foo=bar |
|
||||
``` |
|
||||
|
|
||||
### Sub dictionary |
|
||||
|
|
||||
```go |
|
||||
log.Info(). |
|
||||
Str("foo", "bar"). |
|
||||
Dict("dict", zerolog.Dict(). |
|
||||
Str("bar", "baz"). |
|
||||
Int("n", 1), |
|
||||
).Msg("hello world") |
|
||||
|
|
||||
// Output: {"level":"info","time":1494567715,"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"} |
|
||||
``` |
|
||||
|
|
||||
### Customize automatic field names |
|
||||
|
|
||||
```go |
|
||||
zerolog.TimestampFieldName = "t" |
|
||||
zerolog.LevelFieldName = "l" |
|
||||
zerolog.MessageFieldName = "m" |
|
||||
|
|
||||
log.Info().Msg("hello world") |
|
||||
|
|
||||
// Output: {"l":"info","t":1494567715,"m":"hello world"} |
|
||||
``` |
|
||||
|
|
||||
### Add contextual fields to the global logger |
|
||||
|
|
||||
```go |
|
||||
log.Logger = log.With().Str("foo", "bar").Logger() |
|
||||
``` |
|
||||
|
|
||||
### Add file and line number to log |
|
||||
|
|
||||
```go |
|
||||
log.Logger = log.With().Caller().Logger() |
|
||||
log.Info().Msg("hello world") |
|
||||
|
|
||||
// Output: {"level": "info", "message": "hello world", "caller": "/go/src/your_project/some_file:21"} |
|
||||
``` |
|
||||
|
|
||||
|
|
||||
### Thread-safe, lock-free, non-blocking writer |
|
||||
|
|
||||
If your writer might be slow or not thread-safe and you need your log producers to never get slowed down by a slow writer, you can use a `diode.Writer` as follow: |
|
||||
|
|
||||
```go |
|
||||
wr := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, func(missed int) { |
|
||||
fmt.Printf("Logger Dropped %d messages", missed) |
|
||||
}) |
|
||||
log := zerolog.New(w) |
|
||||
log.Print("test") |
|
||||
``` |
|
||||
|
|
||||
You will need to install `code.cloudfoundry.org/go-diodes` to use this feature. |
|
||||
|
|
||||
### Log Sampling |
|
||||
|
|
||||
```go |
|
||||
sampled := log.Sample(&zerolog.BasicSampler{N: 10}) |
|
||||
sampled.Info().Msg("will be logged every 10 messages") |
|
||||
|
|
||||
// Output: {"time":1494567715,"level":"info","message":"will be logged every 10 messages"} |
|
||||
``` |
|
||||
|
|
||||
More advanced sampling: |
|
||||
|
|
||||
```go |
|
||||
// Will let 5 debug messages per period of 1 second. |
|
||||
// Over 5 debug message, 1 every 100 debug messages are logged. |
|
||||
// Other levels are not sampled. |
|
||||
sampled := log.Sample(zerolog.LevelSampler{ |
|
||||
DebugSampler: &zerolog.BurstSampler{ |
|
||||
Burst: 5, |
|
||||
Period: 1*time.Second, |
|
||||
NextSampler: &zerolog.BasicSampler{N: 100}, |
|
||||
}, |
|
||||
}) |
|
||||
sampled.Debug().Msg("hello world") |
|
||||
|
|
||||
// Output: {"time":1494567715,"level":"debug","message":"hello world"} |
|
||||
``` |
|
||||
|
|
||||
### Hooks |
|
||||
|
|
||||
```go |
|
||||
type SeverityHook struct{} |
|
||||
|
|
||||
func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { |
|
||||
if level != zerolog.NoLevel { |
|
||||
e.Str("severity", level.String()) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
hooked := log.Hook(SeverityHook{}) |
|
||||
hooked.Warn().Msg("") |
|
||||
|
|
||||
// Output: {"level":"warn","severity":"warn"} |
|
||||
``` |
|
||||
|
|
||||
### Pass a sub-logger by context |
|
||||
|
|
||||
```go |
|
||||
ctx := log.With().Str("component", "module").Logger().WithContext(ctx) |
|
||||
|
|
||||
log.Ctx(ctx).Info().Msg("hello world") |
|
||||
|
|
||||
// Output: {"component":"module","level":"info","message":"hello world"} |
|
||||
``` |
|
||||
|
|
||||
### Set as standard logger output |
|
||||
|
|
||||
```go |
|
||||
log := zerolog.New(os.Stdout).With(). |
|
||||
Str("foo", "bar"). |
|
||||
Logger() |
|
||||
|
|
||||
stdlog.SetFlags(0) |
|
||||
stdlog.SetOutput(log) |
|
||||
|
|
||||
stdlog.Print("hello world") |
|
||||
|
|
||||
// Output: {"foo":"bar","message":"hello world"} |
|
||||
``` |
|
||||
|
|
||||
### Integration with `net/http` |
|
||||
|
|
||||
The `github.com/rs/zerolog/hlog` package provides some helpers to integrate zerolog with `http.Handler`. |
|
||||
|
|
||||
In this example we use [alice](https://github.com/justinas/alice) to install logger for better readability. |
|
||||
|
|
||||
```go |
|
||||
log := zerolog.New(os.Stdout).With(). |
|
||||
Timestamp(). |
|
||||
Str("role", "my-service"). |
|
||||
Str("host", host). |
|
||||
Logger() |
|
||||
|
|
||||
c := alice.New() |
|
||||
|
|
||||
// Install the logger handler with default output on the console |
|
||||
c = c.Append(hlog.NewHandler(log)) |
|
||||
|
|
||||
// Install some provided extra handler to set some request's context fields. |
|
||||
// Thanks to those handler, all our logs will come with some pre-populated fields. |
|
||||
c = c.Append(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { |
|
||||
hlog.FromRequest(r).Info(). |
|
||||
Str("method", r.Method). |
|
||||
Str("url", r.URL.String()). |
|
||||
Int("status", status). |
|
||||
Int("size", size). |
|
||||
Dur("duration", duration). |
|
||||
Msg("") |
|
||||
})) |
|
||||
c = c.Append(hlog.RemoteAddrHandler("ip")) |
|
||||
c = c.Append(hlog.UserAgentHandler("user_agent")) |
|
||||
c = c.Append(hlog.RefererHandler("referer")) |
|
||||
c = c.Append(hlog.RequestIDHandler("req_id", "Request-Id")) |
|
||||
|
|
||||
// Here is your final handler |
|
||||
h := c.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|
||||
// Get the logger from the request's context. You can safely assume it |
|
||||
// will be always there: if the handler is removed, hlog.FromRequest |
|
||||
// will return a no-op logger. |
|
||||
hlog.FromRequest(r).Info(). |
|
||||
Str("user", "current user"). |
|
||||
Str("status", "ok"). |
|
||||
Msg("Something happened") |
|
||||
|
|
||||
// Output: {"level":"info","time":"2001-02-03T04:05:06Z","role":"my-service","host":"local-hostname","req_id":"b4g0l5t6tfid6dtrapu0","user":"current user","status":"ok","message":"Something happened"} |
|
||||
})) |
|
||||
http.Handle("/", h) |
|
||||
|
|
||||
if err := http.ListenAndServe(":8080", nil); err != nil { |
|
||||
log.Fatal().Err(err).Msg("Startup failed") |
|
||||
} |
|
||||
``` |
|
||||
|
|
||||
## Global Settings |
|
||||
|
|
||||
Some settings can be changed and will by applied to all loggers: |
|
||||
|
|
||||
* `log.Logger`: You can set this value to customize the global logger (the one used by package level methods). |
|
||||
* `zerolog.SetGlobalLevel`: Can raise the minimum level of all loggers. Set this to `zerolog.Disabled` to disable logging altogether (quiet mode). |
|
||||
* `zerolog.DisableSampling`: If argument is `true`, all sampled loggers will stop sampling and issue 100% of their log events. |
|
||||
* `zerolog.TimestampFieldName`: Can be set to customize `Timestamp` field name. |
|
||||
* `zerolog.LevelFieldName`: Can be set to customize level field name. |
|
||||
* `zerolog.MessageFieldName`: Can be set to customize message field name. |
|
||||
* `zerolog.ErrorFieldName`: Can be set to customize `Err` field name. |
|
||||
* `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with an empty string, times are formated as UNIX timestamp. |
|
||||
// DurationFieldUnit defines the unit for time.Duration type fields added |
|
||||
// using the Dur method. |
|
||||
* `DurationFieldUnit`: Sets the unit of the fields added by `Dur` (default: `time.Millisecond`). |
|
||||
* `DurationFieldInteger`: If set to true, `Dur` fields are formatted as integers instead of floats. |
|
||||
|
|
||||
## Field Types |
|
||||
|
|
||||
### Standard Types |
|
||||
|
|
||||
* `Str` |
|
||||
* `Bool` |
|
||||
* `Int`, `Int8`, `Int16`, `Int32`, `Int64` |
|
||||
* `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64` |
|
||||
* `Float32`, `Float64` |
|
||||
|
|
||||
### Advanced Fields |
|
||||
|
|
||||
* `Err`: Takes an `error` and render it as a string using the `zerolog.ErrorFieldName` field name. |
|
||||
* `Timestamp`: Insert a timestamp field with `zerolog.TimestampFieldName` field name and formatted using `zerolog.TimeFieldFormat`. |
|
||||
* `Time`: Adds a field with the time formated with the `zerolog.TimeFieldFormat`. |
|
||||
* `Dur`: Adds a field with a `time.Duration`. |
|
||||
* `Dict`: Adds a sub-key/value as a field of the event. |
|
||||
* `Interface`: Uses reflection to marshal the type. |
|
||||
|
|
||||
## Binary Encoding |
|
||||
|
|
||||
In addition to the default JSON encoding, `zerolog` can produce binary logs using [CBOR](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows: |
|
||||
|
|
||||
```bash |
|
||||
go build -tags binary_log . |
|
||||
``` |
|
||||
|
|
||||
To Decode binary encoded log files you can use any CBOR decoder. One has been tested to work |
|
||||
with zerolog library is [CSD](https://github.com/toravir/csd/). |
|
||||
|
|
||||
## Related Projects |
|
||||
|
|
||||
* [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog` |
|
||||
|
|
||||
## Benchmarks |
|
||||
|
|
||||
See [logbench](http://hackemist.com/logbench/) for more comprehensive and up-to-date benchmarks. |
|
||||
|
|
||||
All operations are allocation free (those numbers *include* JSON encoding): |
|
||||
|
|
||||
```text |
|
||||
BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op |
|
||||
BenchmarkDisabled-8 500000000 4.07 ns/op 0 B/op 0 allocs/op |
|
||||
BenchmarkInfo-8 30000000 42.5 ns/op 0 B/op 0 allocs/op |
|
||||
BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op |
|
||||
BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op |
|
||||
``` |
|
||||
|
|
||||
There are a few Go logging benchmarks and comparisons that include zerolog. |
|
||||
|
|
||||
* [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) |
|
||||
* [uber-common/zap](https://github.com/uber-go/zap#performance) |
|
||||
|
|
||||
Using Uber's zap comparison benchmark: |
|
||||
|
|
||||
Log a message and 10 fields: |
|
||||
|
|
||||
| Library | Time | Bytes Allocated | Objects Allocated | |
|
||||
| :--- | :---: | :---: | :---: | |
|
||||
| zerolog | 767 ns/op | 552 B/op | 6 allocs/op | |
|
||||
| :zap: zap | 848 ns/op | 704 B/op | 2 allocs/op | |
|
||||
| :zap: zap (sugared) | 1363 ns/op | 1610 B/op | 20 allocs/op | |
|
||||
| go-kit | 3614 ns/op | 2895 B/op | 66 allocs/op | |
|
||||
| lion | 5392 ns/op | 5807 B/op | 63 allocs/op | |
|
||||
| logrus | 5661 ns/op | 6092 B/op | 78 allocs/op | |
|
||||
| apex/log | 15332 ns/op | 3832 B/op | 65 allocs/op | |
|
||||
| log15 | 20657 ns/op | 5632 B/op | 93 allocs/op | |
|
||||
|
|
||||
Log a message with a logger that already has 10 fields of context: |
|
||||
|
|
||||
| Library | Time | Bytes Allocated | Objects Allocated | |
|
||||
| :--- | :---: | :---: | :---: | |
|
||||
| zerolog | 52 ns/op | 0 B/op | 0 allocs/op | |
|
||||
| :zap: zap | 283 ns/op | 0 B/op | 0 allocs/op | |
|
||||
| :zap: zap (sugared) | 337 ns/op | 80 B/op | 2 allocs/op | |
|
||||
| lion | 2702 ns/op | 4074 B/op | 38 allocs/op | |
|
||||
| go-kit | 3378 ns/op | 3046 B/op | 52 allocs/op | |
|
||||
| logrus | 4309 ns/op | 4564 B/op | 63 allocs/op | |
|
||||
| apex/log | 13456 ns/op | 2898 B/op | 51 allocs/op | |
|
||||
| log15 | 14179 ns/op | 2642 B/op | 44 allocs/op | |
|
||||
|
|
||||
Log a static string, without any context or `printf`-style templating: |
|
||||
|
|
||||
| Library | Time | Bytes Allocated | Objects Allocated | |
|
||||
| :--- | :---: | :---: | :---: | |
|
||||
| zerolog | 50 ns/op | 0 B/op | 0 allocs/op | |
|
||||
| :zap: zap | 236 ns/op | 0 B/op | 0 allocs/op | |
|
||||
| standard library | 453 ns/op | 80 B/op | 2 allocs/op | |
|
||||
| :zap: zap (sugared) | 337 ns/op | 80 B/op | 2 allocs/op | |
|
||||
| go-kit | 508 ns/op | 656 B/op | 13 allocs/op | |
|
||||
| lion | 771 ns/op | 1224 B/op | 10 allocs/op | |
|
||||
| logrus | 1244 ns/op | 1505 B/op | 27 allocs/op | |
|
||||
| apex/log | 2751 ns/op | 584 B/op | 11 allocs/op | |
|
||||
| log15 | 5181 ns/op | 1592 B/op | 26 allocs/op | |
|
||||
|
|
||||
## Caveats |
|
||||
|
|
||||
Note that zerolog does de-duplication fields. Using the same key multiple times creates multiple keys in final JSON: |
|
||||
|
|
||||
```go |
|
||||
logger := zerolog.New(os.Stderr).With().Timestamp().Logger() |
|
||||
logger.Info(). |
|
||||
Timestamp(). |
|
||||
Msg("dup") |
|
||||
// Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} |
|
||||
``` |
|
||||
|
|
||||
However, it’s not a big deal as JSON accepts dup keys; the last one prevails. |
|
@ -1 +0,0 @@ |
|||||
remote_theme: rs/gh-readme |
|
@ -1,224 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"net" |
|
||||
"sync" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
var arrayPool = &sync.Pool{ |
|
||||
New: func() interface{} { |
|
||||
return &Array{ |
|
||||
buf: make([]byte, 0, 500), |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
// Array is used to prepopulate an array of items
|
|
||||
// which can be re-used to add to log messages.
|
|
||||
type Array struct { |
|
||||
buf []byte |
|
||||
} |
|
||||
|
|
||||
func putArray(a *Array) { |
|
||||
// Proper usage of a sync.Pool requires each entry to have approximately
|
|
||||
// the same memory cost. To obtain this property when the stored type
|
|
||||
// contains a variably-sized buffer, we add a hard limit on the maximum buffer
|
|
||||
// to place back in the pool.
|
|
||||
//
|
|
||||
// See https://golang.org/issue/23199
|
|
||||
const maxSize = 1 << 16 // 64KiB
|
|
||||
if cap(a.buf) > maxSize { |
|
||||
return |
|
||||
} |
|
||||
arrayPool.Put(a) |
|
||||
} |
|
||||
|
|
||||
// Arr creates an array to be added to an Event or Context.
|
|
||||
func Arr() *Array { |
|
||||
a := arrayPool.Get().(*Array) |
|
||||
a.buf = a.buf[:0] |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// MarshalZerologArray method here is no-op - since data is
|
|
||||
// already in the needed format.
|
|
||||
func (*Array) MarshalZerologArray(*Array) { |
|
||||
} |
|
||||
|
|
||||
func (a *Array) write(dst []byte) []byte { |
|
||||
dst = enc.AppendArrayStart(dst) |
|
||||
if len(a.buf) > 0 { |
|
||||
dst = append(append(dst, a.buf...)) |
|
||||
} |
|
||||
dst = enc.AppendArrayEnd(dst) |
|
||||
putArray(a) |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// Object marshals an object that implement the LogObjectMarshaler
|
|
||||
// interface and append append it to the array.
|
|
||||
func (a *Array) Object(obj LogObjectMarshaler) *Array { |
|
||||
e := Dict() |
|
||||
obj.MarshalZerologObject(e) |
|
||||
e.buf = enc.AppendEndMarker(e.buf) |
|
||||
a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) |
|
||||
putEvent(e) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Str append append the val as a string to the array.
|
|
||||
func (a *Array) Str(val string) *Array { |
|
||||
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Bytes append append the val as a string to the array.
|
|
||||
func (a *Array) Bytes(val []byte) *Array { |
|
||||
a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Hex append append the val as a hex string to the array.
|
|
||||
func (a *Array) Hex(val []byte) *Array { |
|
||||
a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Err serializes and appends the err to the array.
|
|
||||
func (a *Array) Err(err error) *Array { |
|
||||
marshaled := ErrorMarshalFunc(err) |
|
||||
switch m := marshaled.(type) { |
|
||||
case LogObjectMarshaler: |
|
||||
e := newEvent(nil, 0) |
|
||||
e.buf = e.buf[:0] |
|
||||
e.appendObject(m) |
|
||||
a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) |
|
||||
putEvent(e) |
|
||||
case error: |
|
||||
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error()) |
|
||||
case string: |
|
||||
a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m) |
|
||||
default: |
|
||||
a.buf = enc.AppendInterface(enc.AppendArrayDelim(a.buf), m) |
|
||||
} |
|
||||
|
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Bool append append the val as a bool to the array.
|
|
||||
func (a *Array) Bool(b bool) *Array { |
|
||||
a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Int append append i as a int to the array.
|
|
||||
func (a *Array) Int(i int) *Array { |
|
||||
a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Int8 append append i as a int8 to the array.
|
|
||||
func (a *Array) Int8(i int8) *Array { |
|
||||
a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Int16 append append i as a int16 to the array.
|
|
||||
func (a *Array) Int16(i int16) *Array { |
|
||||
a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Int32 append append i as a int32 to the array.
|
|
||||
func (a *Array) Int32(i int32) *Array { |
|
||||
a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Int64 append append i as a int64 to the array.
|
|
||||
func (a *Array) Int64(i int64) *Array { |
|
||||
a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Uint append append i as a uint to the array.
|
|
||||
func (a *Array) Uint(i uint) *Array { |
|
||||
a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Uint8 append append i as a uint8 to the array.
|
|
||||
func (a *Array) Uint8(i uint8) *Array { |
|
||||
a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Uint16 append append i as a uint16 to the array.
|
|
||||
func (a *Array) Uint16(i uint16) *Array { |
|
||||
a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Uint32 append append i as a uint32 to the array.
|
|
||||
func (a *Array) Uint32(i uint32) *Array { |
|
||||
a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Uint64 append append i as a uint64 to the array.
|
|
||||
func (a *Array) Uint64(i uint64) *Array { |
|
||||
a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Float32 append append f as a float32 to the array.
|
|
||||
func (a *Array) Float32(f float32) *Array { |
|
||||
a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Float64 append append f as a float64 to the array.
|
|
||||
func (a *Array) Float64(f float64) *Array { |
|
||||
a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Time append append t formated as string using zerolog.TimeFieldFormat.
|
|
||||
func (a *Array) Time(t time.Time) *Array { |
|
||||
a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Dur append append d to the array.
|
|
||||
func (a *Array) Dur(d time.Duration) *Array { |
|
||||
a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// Interface append append i marshaled using reflection.
|
|
||||
func (a *Array) Interface(i interface{}) *Array { |
|
||||
if obj, ok := i.(LogObjectMarshaler); ok { |
|
||||
return a.Object(obj) |
|
||||
} |
|
||||
a.buf = enc.AppendInterface(enc.AppendArrayDelim(a.buf), i) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// IPAddr adds IPv4 or IPv6 address to the array
|
|
||||
func (a *Array) IPAddr(ip net.IP) *Array { |
|
||||
a.buf = enc.AppendIPAddr(enc.AppendArrayDelim(a.buf), ip) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// IPPrefix adds IPv4 or IPv6 Prefix (IP + mask) to the array
|
|
||||
func (a *Array) IPPrefix(pfx net.IPNet) *Array { |
|
||||
a.buf = enc.AppendIPPrefix(enc.AppendArrayDelim(a.buf), pfx) |
|
||||
return a |
|
||||
} |
|
||||
|
|
||||
// MACAddr adds a MAC (Ethernet) address to the array
|
|
||||
func (a *Array) MACAddr(ha net.HardwareAddr) *Array { |
|
||||
a.buf = enc.AppendMACAddr(enc.AppendArrayDelim(a.buf), ha) |
|
||||
return a |
|
||||
} |
|
@ -1,150 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"bytes" |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"io" |
|
||||
"sort" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
"sync" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
cReset = 0 |
|
||||
cBold = 1 |
|
||||
cRed = 31 |
|
||||
cGreen = 32 |
|
||||
cYellow = 33 |
|
||||
cBlue = 34 |
|
||||
cMagenta = 35 |
|
||||
cCyan = 36 |
|
||||
cGray = 37 |
|
||||
cDarkGray = 90 |
|
||||
) |
|
||||
|
|
||||
var consoleBufPool = sync.Pool{ |
|
||||
New: func() interface{} { |
|
||||
return bytes.NewBuffer(make([]byte, 0, 100)) |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
// LevelWidth defines the desired character width of the log level column.
|
|
||||
// Default 0 does not trim or pad (variable width based level text, e.g. "INFO" or "ERROR")
|
|
||||
var LevelWidth = 0 |
|
||||
|
|
||||
// ConsoleWriter reads a JSON object per write operation and output an
|
|
||||
// optionally colored human readable version on the Out writer.
|
|
||||
type ConsoleWriter struct { |
|
||||
Out io.Writer |
|
||||
NoColor bool |
|
||||
} |
|
||||
|
|
||||
func (w ConsoleWriter) Write(p []byte) (n int, err error) { |
|
||||
var event map[string]interface{} |
|
||||
p = decodeIfBinaryToBytes(p) |
|
||||
d := json.NewDecoder(bytes.NewReader(p)) |
|
||||
d.UseNumber() |
|
||||
err = d.Decode(&event) |
|
||||
if err != nil { |
|
||||
return |
|
||||
} |
|
||||
buf := consoleBufPool.Get().(*bytes.Buffer) |
|
||||
defer consoleBufPool.Put(buf) |
|
||||
lvlColor := cReset |
|
||||
level := "????" |
|
||||
if l, ok := event[LevelFieldName].(string); ok { |
|
||||
if !w.NoColor { |
|
||||
lvlColor = levelColor(l) |
|
||||
} |
|
||||
level = strings.ToUpper(l) |
|
||||
if LevelWidth > 0 { |
|
||||
if padding := LevelWidth - len(level); padding > 0 { |
|
||||
level += strings.Repeat(" ", padding) |
|
||||
} else { |
|
||||
level = level[0:LevelWidth] |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
fmt.Fprintf(buf, "%s |%s| %s", |
|
||||
colorize(formatTime(event[TimestampFieldName]), cDarkGray, !w.NoColor), |
|
||||
colorize(level, lvlColor, !w.NoColor), |
|
||||
colorize(event[MessageFieldName], cReset, !w.NoColor)) |
|
||||
fields := make([]string, 0, len(event)) |
|
||||
for field := range event { |
|
||||
switch field { |
|
||||
case LevelFieldName, TimestampFieldName, MessageFieldName: |
|
||||
continue |
|
||||
} |
|
||||
fields = append(fields, field) |
|
||||
} |
|
||||
sort.Strings(fields) |
|
||||
for _, field := range fields { |
|
||||
fmt.Fprintf(buf, " %s=", colorize(field, cCyan, !w.NoColor)) |
|
||||
switch value := event[field].(type) { |
|
||||
case string: |
|
||||
if needsQuote(value) { |
|
||||
buf.WriteString(strconv.Quote(value)) |
|
||||
} else { |
|
||||
buf.WriteString(value) |
|
||||
} |
|
||||
case json.Number: |
|
||||
fmt.Fprint(buf, value) |
|
||||
default: |
|
||||
b, err := json.Marshal(value) |
|
||||
if err != nil { |
|
||||
fmt.Fprintf(buf, "[error: %v]", err) |
|
||||
} else { |
|
||||
fmt.Fprint(buf, string(b)) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
buf.WriteByte('\n') |
|
||||
buf.WriteTo(w.Out) |
|
||||
n = len(p) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func formatTime(t interface{}) string { |
|
||||
switch t := t.(type) { |
|
||||
case string: |
|
||||
return t |
|
||||
case json.Number: |
|
||||
u, _ := t.Int64() |
|
||||
return time.Unix(u, 0).Format(time.RFC3339) |
|
||||
} |
|
||||
return "<nil>" |
|
||||
} |
|
||||
|
|
||||
func colorize(s interface{}, color int, enabled bool) string { |
|
||||
if !enabled { |
|
||||
return fmt.Sprintf("%v", s) |
|
||||
} |
|
||||
return fmt.Sprintf("\x1b[%dm%v\x1b[0m", color, s) |
|
||||
} |
|
||||
|
|
||||
func levelColor(level string) int { |
|
||||
switch level { |
|
||||
case "debug": |
|
||||
return cMagenta |
|
||||
case "info": |
|
||||
return cGreen |
|
||||
case "warn": |
|
||||
return cYellow |
|
||||
case "error", "fatal", "panic": |
|
||||
return cRed |
|
||||
default: |
|
||||
return cReset |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func needsQuote(s string) bool { |
|
||||
for i := range s { |
|
||||
if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' { |
|
||||
return true |
|
||||
} |
|
||||
} |
|
||||
return false |
|
||||
} |
|
@ -1,381 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"io/ioutil" |
|
||||
"net" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
// Context configures a new sub-logger with contextual fields.
|
|
||||
type Context struct { |
|
||||
l Logger |
|
||||
} |
|
||||
|
|
||||
// Logger returns the logger with the context previously set.
|
|
||||
func (c Context) Logger() Logger { |
|
||||
return c.l |
|
||||
} |
|
||||
|
|
||||
// Fields is a helper function to use a map to set fields using type assertion.
|
|
||||
func (c Context) Fields(fields map[string]interface{}) Context { |
|
||||
c.l.context = appendFields(c.l.context, fields) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Dict adds the field key with the dict to the logger context.
|
|
||||
func (c Context) Dict(key string, dict *Event) Context { |
|
||||
dict.buf = enc.AppendEndMarker(dict.buf) |
|
||||
c.l.context = append(enc.AppendKey(c.l.context, key), dict.buf...) |
|
||||
putEvent(dict) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Array adds the field key with an array to the event context.
|
|
||||
// Use zerolog.Arr() to create the array or pass a type that
|
|
||||
// implement the LogArrayMarshaler interface.
|
|
||||
func (c Context) Array(key string, arr LogArrayMarshaler) Context { |
|
||||
c.l.context = enc.AppendKey(c.l.context, key) |
|
||||
if arr, ok := arr.(*Array); ok { |
|
||||
c.l.context = arr.write(c.l.context) |
|
||||
return c |
|
||||
} |
|
||||
var a *Array |
|
||||
if aa, ok := arr.(*Array); ok { |
|
||||
a = aa |
|
||||
} else { |
|
||||
a = Arr() |
|
||||
arr.MarshalZerologArray(a) |
|
||||
} |
|
||||
c.l.context = a.write(c.l.context) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Object marshals an object that implement the LogObjectMarshaler interface.
|
|
||||
func (c Context) Object(key string, obj LogObjectMarshaler) Context { |
|
||||
e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) |
|
||||
e.Object(key, obj) |
|
||||
c.l.context = enc.AppendObjectData(c.l.context, e.buf) |
|
||||
putEvent(e) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// EmbedObject marshals and Embeds an object that implement the LogObjectMarshaler interface.
|
|
||||
func (c Context) EmbedObject(obj LogObjectMarshaler) Context { |
|
||||
e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) |
|
||||
e.EmbedObject(obj) |
|
||||
c.l.context = enc.AppendObjectData(c.l.context, e.buf) |
|
||||
putEvent(e) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Str adds the field key with val as a string to the logger context.
|
|
||||
func (c Context) Str(key, val string) Context { |
|
||||
c.l.context = enc.AppendString(enc.AppendKey(c.l.context, key), val) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Strs adds the field key with val as a string to the logger context.
|
|
||||
func (c Context) Strs(key string, vals []string) Context { |
|
||||
c.l.context = enc.AppendStrings(enc.AppendKey(c.l.context, key), vals) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Bytes adds the field key with val as a []byte to the logger context.
|
|
||||
func (c Context) Bytes(key string, val []byte) Context { |
|
||||
c.l.context = enc.AppendBytes(enc.AppendKey(c.l.context, key), val) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Hex adds the field key with val as a hex string to the logger context.
|
|
||||
func (c Context) Hex(key string, val []byte) Context { |
|
||||
c.l.context = enc.AppendHex(enc.AppendKey(c.l.context, key), val) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// RawJSON adds already encoded JSON to context.
|
|
||||
//
|
|
||||
// No sanity check is performed on b; it must not contain carriage returns and
|
|
||||
// be valid JSON.
|
|
||||
func (c Context) RawJSON(key string, b []byte) Context { |
|
||||
c.l.context = appendJSON(enc.AppendKey(c.l.context, key), b) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// AnErr adds the field key with serialized err to the logger context.
|
|
||||
func (c Context) AnErr(key string, err error) Context { |
|
||||
marshaled := ErrorMarshalFunc(err) |
|
||||
switch m := marshaled.(type) { |
|
||||
case nil: |
|
||||
return c |
|
||||
case LogObjectMarshaler: |
|
||||
return c.Object(key, m) |
|
||||
case error: |
|
||||
return c.Str(key, m.Error()) |
|
||||
case string: |
|
||||
return c.Str(key, m) |
|
||||
default: |
|
||||
return c.Interface(key, m) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Errs adds the field key with errs as an array of serialized errors to the
|
|
||||
// logger context.
|
|
||||
func (c Context) Errs(key string, errs []error) Context { |
|
||||
arr := Arr() |
|
||||
for _, err := range errs { |
|
||||
marshaled := ErrorMarshalFunc(err) |
|
||||
switch m := marshaled.(type) { |
|
||||
case LogObjectMarshaler: |
|
||||
arr = arr.Object(m) |
|
||||
case error: |
|
||||
arr = arr.Str(m.Error()) |
|
||||
case string: |
|
||||
arr = arr.Str(m) |
|
||||
default: |
|
||||
arr = arr.Interface(m) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return c.Array(key, arr) |
|
||||
} |
|
||||
|
|
||||
// Err adds the field "error" with serialized err to the logger context.
|
|
||||
func (c Context) Err(err error) Context { |
|
||||
return c.AnErr(ErrorFieldName, err) |
|
||||
} |
|
||||
|
|
||||
// Bool adds the field key with val as a bool to the logger context.
|
|
||||
func (c Context) Bool(key string, b bool) Context { |
|
||||
c.l.context = enc.AppendBool(enc.AppendKey(c.l.context, key), b) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Bools adds the field key with val as a []bool to the logger context.
|
|
||||
func (c Context) Bools(key string, b []bool) Context { |
|
||||
c.l.context = enc.AppendBools(enc.AppendKey(c.l.context, key), b) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Int adds the field key with i as a int to the logger context.
|
|
||||
func (c Context) Int(key string, i int) Context { |
|
||||
c.l.context = enc.AppendInt(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Ints adds the field key with i as a []int to the logger context.
|
|
||||
func (c Context) Ints(key string, i []int) Context { |
|
||||
c.l.context = enc.AppendInts(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Int8 adds the field key with i as a int8 to the logger context.
|
|
||||
func (c Context) Int8(key string, i int8) Context { |
|
||||
c.l.context = enc.AppendInt8(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Ints8 adds the field key with i as a []int8 to the logger context.
|
|
||||
func (c Context) Ints8(key string, i []int8) Context { |
|
||||
c.l.context = enc.AppendInts8(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Int16 adds the field key with i as a int16 to the logger context.
|
|
||||
func (c Context) Int16(key string, i int16) Context { |
|
||||
c.l.context = enc.AppendInt16(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Ints16 adds the field key with i as a []int16 to the logger context.
|
|
||||
func (c Context) Ints16(key string, i []int16) Context { |
|
||||
c.l.context = enc.AppendInts16(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Int32 adds the field key with i as a int32 to the logger context.
|
|
||||
func (c Context) Int32(key string, i int32) Context { |
|
||||
c.l.context = enc.AppendInt32(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Ints32 adds the field key with i as a []int32 to the logger context.
|
|
||||
func (c Context) Ints32(key string, i []int32) Context { |
|
||||
c.l.context = enc.AppendInts32(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Int64 adds the field key with i as a int64 to the logger context.
|
|
||||
func (c Context) Int64(key string, i int64) Context { |
|
||||
c.l.context = enc.AppendInt64(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Ints64 adds the field key with i as a []int64 to the logger context.
|
|
||||
func (c Context) Ints64(key string, i []int64) Context { |
|
||||
c.l.context = enc.AppendInts64(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uint adds the field key with i as a uint to the logger context.
|
|
||||
func (c Context) Uint(key string, i uint) Context { |
|
||||
c.l.context = enc.AppendUint(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uints adds the field key with i as a []uint to the logger context.
|
|
||||
func (c Context) Uints(key string, i []uint) Context { |
|
||||
c.l.context = enc.AppendUints(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uint8 adds the field key with i as a uint8 to the logger context.
|
|
||||
func (c Context) Uint8(key string, i uint8) Context { |
|
||||
c.l.context = enc.AppendUint8(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uints8 adds the field key with i as a []uint8 to the logger context.
|
|
||||
func (c Context) Uints8(key string, i []uint8) Context { |
|
||||
c.l.context = enc.AppendUints8(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uint16 adds the field key with i as a uint16 to the logger context.
|
|
||||
func (c Context) Uint16(key string, i uint16) Context { |
|
||||
c.l.context = enc.AppendUint16(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uints16 adds the field key with i as a []uint16 to the logger context.
|
|
||||
func (c Context) Uints16(key string, i []uint16) Context { |
|
||||
c.l.context = enc.AppendUints16(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uint32 adds the field key with i as a uint32 to the logger context.
|
|
||||
func (c Context) Uint32(key string, i uint32) Context { |
|
||||
c.l.context = enc.AppendUint32(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uints32 adds the field key with i as a []uint32 to the logger context.
|
|
||||
func (c Context) Uints32(key string, i []uint32) Context { |
|
||||
c.l.context = enc.AppendUints32(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uint64 adds the field key with i as a uint64 to the logger context.
|
|
||||
func (c Context) Uint64(key string, i uint64) Context { |
|
||||
c.l.context = enc.AppendUint64(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Uints64 adds the field key with i as a []uint64 to the logger context.
|
|
||||
func (c Context) Uints64(key string, i []uint64) Context { |
|
||||
c.l.context = enc.AppendUints64(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Float32 adds the field key with f as a float32 to the logger context.
|
|
||||
func (c Context) Float32(key string, f float32) Context { |
|
||||
c.l.context = enc.AppendFloat32(enc.AppendKey(c.l.context, key), f) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Floats32 adds the field key with f as a []float32 to the logger context.
|
|
||||
func (c Context) Floats32(key string, f []float32) Context { |
|
||||
c.l.context = enc.AppendFloats32(enc.AppendKey(c.l.context, key), f) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Float64 adds the field key with f as a float64 to the logger context.
|
|
||||
func (c Context) Float64(key string, f float64) Context { |
|
||||
c.l.context = enc.AppendFloat64(enc.AppendKey(c.l.context, key), f) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Floats64 adds the field key with f as a []float64 to the logger context.
|
|
||||
func (c Context) Floats64(key string, f []float64) Context { |
|
||||
c.l.context = enc.AppendFloats64(enc.AppendKey(c.l.context, key), f) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
type timestampHook struct{} |
|
||||
|
|
||||
func (ts timestampHook) Run(e *Event, level Level, msg string) { |
|
||||
e.Timestamp() |
|
||||
} |
|
||||
|
|
||||
var th = timestampHook{} |
|
||||
|
|
||||
// Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key.
|
|
||||
// To customize the key name, change zerolog.TimestampFieldName.
|
|
||||
//
|
|
||||
// NOTE: It won't dedupe the "time" key if the *Context has one already.
|
|
||||
func (c Context) Timestamp() Context { |
|
||||
c.l = c.l.Hook(th) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Time adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
|
||||
func (c Context) Time(key string, t time.Time) Context { |
|
||||
c.l.context = enc.AppendTime(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Times adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
|
||||
func (c Context) Times(key string, t []time.Time) Context { |
|
||||
c.l.context = enc.AppendTimes(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Dur adds the fields key with d divided by unit and stored as a float.
|
|
||||
func (c Context) Dur(key string, d time.Duration) Context { |
|
||||
c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Durs adds the fields key with d divided by unit and stored as a float.
|
|
||||
func (c Context) Durs(key string, d []time.Duration) Context { |
|
||||
c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// Interface adds the field key with obj marshaled using reflection.
|
|
||||
func (c Context) Interface(key string, i interface{}) Context { |
|
||||
c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), i) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
type callerHook struct{} |
|
||||
|
|
||||
func (ch callerHook) Run(e *Event, level Level, msg string) { |
|
||||
// Three extra frames to skip (added by hook infra).
|
|
||||
e.caller(CallerSkipFrameCount + 3) |
|
||||
} |
|
||||
|
|
||||
var ch = callerHook{} |
|
||||
|
|
||||
// Caller adds the file:line of the caller with the zerolog.CallerFieldName key.
|
|
||||
func (c Context) Caller() Context { |
|
||||
c.l = c.l.Hook(ch) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// IPAddr adds IPv4 or IPv6 Address to the context
|
|
||||
func (c Context) IPAddr(key string, ip net.IP) Context { |
|
||||
c.l.context = enc.AppendIPAddr(enc.AppendKey(c.l.context, key), ip) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the context
|
|
||||
func (c Context) IPPrefix(key string, pfx net.IPNet) Context { |
|
||||
c.l.context = enc.AppendIPPrefix(enc.AppendKey(c.l.context, key), pfx) |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// MACAddr adds MAC address to the context
|
|
||||
func (c Context) MACAddr(key string, ha net.HardwareAddr) Context { |
|
||||
c.l.context = enc.AppendMACAddr(enc.AppendKey(c.l.context, key), ha) |
|
||||
return c |
|
||||
} |
|
@ -1,47 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"context" |
|
||||
) |
|
||||
|
|
||||
var disabledLogger *Logger |
|
||||
|
|
||||
func init() { |
|
||||
l := Nop() |
|
||||
disabledLogger = &l |
|
||||
} |
|
||||
|
|
||||
type ctxKey struct{} |
|
||||
|
|
||||
// WithContext returns a copy of ctx with l associated. If an instance of Logger
|
|
||||
// is already in the context, the context is not updated.
|
|
||||
//
|
|
||||
// For instance, to add a field to an existing logger in the context, use this
|
|
||||
// notation:
|
|
||||
//
|
|
||||
// ctx := r.Context()
|
|
||||
// l := zerolog.Ctx(ctx)
|
|
||||
// l.UpdateContext(func(c Context) Context {
|
|
||||
// return c.Str("bar", "baz")
|
|
||||
// })
|
|
||||
func (l *Logger) WithContext(ctx context.Context) context.Context { |
|
||||
if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok { |
|
||||
if lp == l { |
|
||||
// Do not store same logger.
|
|
||||
return ctx |
|
||||
} |
|
||||
} else if l.level == Disabled { |
|
||||
// Do not store disabled logger.
|
|
||||
return ctx |
|
||||
} |
|
||||
return context.WithValue(ctx, ctxKey{}, l) |
|
||||
} |
|
||||
|
|
||||
// Ctx returns the Logger associated with the ctx. If no logger
|
|
||||
// is associated, a disabled logger is returned.
|
|
||||
func Ctx(ctx context.Context) *Logger { |
|
||||
if l, ok := ctx.Value(ctxKey{}).(*Logger); ok { |
|
||||
return l |
|
||||
} |
|
||||
return disabledLogger |
|
||||
} |
|
@ -1,56 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"net" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
type encoder interface { |
|
||||
AppendArrayDelim(dst []byte) []byte |
|
||||
AppendArrayEnd(dst []byte) []byte |
|
||||
AppendArrayStart(dst []byte) []byte |
|
||||
AppendBeginMarker(dst []byte) []byte |
|
||||
AppendBool(dst []byte, val bool) []byte |
|
||||
AppendBools(dst []byte, vals []bool) []byte |
|
||||
AppendBytes(dst, s []byte) []byte |
|
||||
AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte |
|
||||
AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte |
|
||||
AppendEndMarker(dst []byte) []byte |
|
||||
AppendFloat32(dst []byte, val float32) []byte |
|
||||
AppendFloat64(dst []byte, val float64) []byte |
|
||||
AppendFloats32(dst []byte, vals []float32) []byte |
|
||||
AppendFloats64(dst []byte, vals []float64) []byte |
|
||||
AppendHex(dst, s []byte) []byte |
|
||||
AppendIPAddr(dst []byte, ip net.IP) []byte |
|
||||
AppendIPPrefix(dst []byte, pfx net.IPNet) []byte |
|
||||
AppendInt(dst []byte, val int) []byte |
|
||||
AppendInt16(dst []byte, val int16) []byte |
|
||||
AppendInt32(dst []byte, val int32) []byte |
|
||||
AppendInt64(dst []byte, val int64) []byte |
|
||||
AppendInt8(dst []byte, val int8) []byte |
|
||||
AppendInterface(dst []byte, i interface{}) []byte |
|
||||
AppendInts(dst []byte, vals []int) []byte |
|
||||
AppendInts16(dst []byte, vals []int16) []byte |
|
||||
AppendInts32(dst []byte, vals []int32) []byte |
|
||||
AppendInts64(dst []byte, vals []int64) []byte |
|
||||
AppendInts8(dst []byte, vals []int8) []byte |
|
||||
AppendKey(dst []byte, key string) []byte |
|
||||
AppendLineBreak(dst []byte) []byte |
|
||||
AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte |
|
||||
AppendNil(dst []byte) []byte |
|
||||
AppendObjectData(dst []byte, o []byte) []byte |
|
||||
AppendString(dst []byte, s string) []byte |
|
||||
AppendStrings(dst []byte, vals []string) []byte |
|
||||
AppendTime(dst []byte, t time.Time, format string) []byte |
|
||||
AppendTimes(dst []byte, vals []time.Time, format string) []byte |
|
||||
AppendUint(dst []byte, val uint) []byte |
|
||||
AppendUint16(dst []byte, val uint16) []byte |
|
||||
AppendUint32(dst []byte, val uint32) []byte |
|
||||
AppendUint64(dst []byte, val uint64) []byte |
|
||||
AppendUint8(dst []byte, val uint8) []byte |
|
||||
AppendUints(dst []byte, vals []uint) []byte |
|
||||
AppendUints16(dst []byte, vals []uint16) []byte |
|
||||
AppendUints32(dst []byte, vals []uint32) []byte |
|
||||
AppendUints64(dst []byte, vals []uint64) []byte |
|
||||
AppendUints8(dst []byte, vals []uint8) []byte |
|
||||
} |
|
@ -1,35 +0,0 @@ |
|||||
// +build binary_log
|
|
||||
|
|
||||
package zerolog |
|
||||
|
|
||||
// This file contains bindings to do binary encoding.
|
|
||||
|
|
||||
import ( |
|
||||
"github.com/rs/zerolog/internal/cbor" |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
_ encoder = (*cbor.Encoder)(nil) |
|
||||
|
|
||||
enc = cbor.Encoder{} |
|
||||
) |
|
||||
|
|
||||
func appendJSON(dst []byte, j []byte) []byte { |
|
||||
return cbor.AppendEmbeddedJSON(dst, j) |
|
||||
} |
|
||||
|
|
||||
// decodeIfBinaryToString - converts a binary formatted log msg to a
|
|
||||
// JSON formatted String Log message.
|
|
||||
func decodeIfBinaryToString(in []byte) string { |
|
||||
return cbor.DecodeIfBinaryToString(in) |
|
||||
} |
|
||||
|
|
||||
func decodeObjectToStr(in []byte) string { |
|
||||
return cbor.DecodeObjectToStr(in) |
|
||||
} |
|
||||
|
|
||||
// decodeIfBinaryToBytes - converts a binary formatted log msg to a
|
|
||||
// JSON formatted Bytes Log message.
|
|
||||
func decodeIfBinaryToBytes(in []byte) []byte { |
|
||||
return cbor.DecodeIfBinaryToBytes(in) |
|
||||
} |
|
@ -1,32 +0,0 @@ |
|||||
// +build !binary_log
|
|
||||
|
|
||||
package zerolog |
|
||||
|
|
||||
// encoder_json.go file contains bindings to generate
|
|
||||
// JSON encoded byte stream.
|
|
||||
|
|
||||
import ( |
|
||||
"github.com/rs/zerolog/internal/json" |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
_ encoder = (*json.Encoder)(nil) |
|
||||
|
|
||||
enc = json.Encoder{} |
|
||||
) |
|
||||
|
|
||||
func appendJSON(dst []byte, j []byte) []byte { |
|
||||
return append(dst, j...) |
|
||||
} |
|
||||
|
|
||||
func decodeIfBinaryToString(in []byte) string { |
|
||||
return string(in) |
|
||||
} |
|
||||
|
|
||||
func decodeObjectToStr(in []byte) string { |
|
||||
return string(in) |
|
||||
} |
|
||||
|
|
||||
func decodeIfBinaryToBytes(in []byte) []byte { |
|
||||
return in |
|
||||
} |
|
@ -1,673 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"net" |
|
||||
"os" |
|
||||
"runtime" |
|
||||
"strconv" |
|
||||
"sync" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
var eventPool = &sync.Pool{ |
|
||||
New: func() interface{} { |
|
||||
return &Event{ |
|
||||
buf: make([]byte, 0, 500), |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
// ErrorMarshalFunc allows customization of global error marshaling
|
|
||||
var ErrorMarshalFunc = func(err error) interface{} { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
// Event represents a log event. It is instanced by one of the level method of
|
|
||||
// Logger and finalized by the Msg or Msgf method.
|
|
||||
type Event struct { |
|
||||
buf []byte |
|
||||
w LevelWriter |
|
||||
level Level |
|
||||
done func(msg string) |
|
||||
ch []Hook // hooks from context
|
|
||||
} |
|
||||
|
|
||||
func putEvent(e *Event) { |
|
||||
// Proper usage of a sync.Pool requires each entry to have approximately
|
|
||||
// the same memory cost. To obtain this property when the stored type
|
|
||||
// contains a variably-sized buffer, we add a hard limit on the maximum buffer
|
|
||||
// to place back in the pool.
|
|
||||
//
|
|
||||
// See https://golang.org/issue/23199
|
|
||||
const maxSize = 1 << 16 // 64KiB
|
|
||||
if cap(e.buf) > maxSize { |
|
||||
return |
|
||||
} |
|
||||
eventPool.Put(e) |
|
||||
} |
|
||||
|
|
||||
// LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface
|
|
||||
// to be implemented by types used with Event/Context's Object methods.
|
|
||||
type LogObjectMarshaler interface { |
|
||||
MarshalZerologObject(e *Event) |
|
||||
} |
|
||||
|
|
||||
// LogArrayMarshaler provides a strongly-typed and encoding-agnostic interface
|
|
||||
// to be implemented by types used with Event/Context's Array methods.
|
|
||||
type LogArrayMarshaler interface { |
|
||||
MarshalZerologArray(a *Array) |
|
||||
} |
|
||||
|
|
||||
func newEvent(w LevelWriter, level Level) *Event { |
|
||||
e := eventPool.Get().(*Event) |
|
||||
e.buf = e.buf[:0] |
|
||||
e.ch = nil |
|
||||
e.buf = enc.AppendBeginMarker(e.buf) |
|
||||
e.w = w |
|
||||
e.level = level |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
func (e *Event) write() (err error) { |
|
||||
if e == nil { |
|
||||
return nil |
|
||||
} |
|
||||
if e.level != Disabled { |
|
||||
e.buf = enc.AppendEndMarker(e.buf) |
|
||||
e.buf = enc.AppendLineBreak(e.buf) |
|
||||
if e.w != nil { |
|
||||
_, err = e.w.WriteLevel(e.level, e.buf) |
|
||||
} |
|
||||
} |
|
||||
putEvent(e) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Enabled return false if the *Event is going to be filtered out by
|
|
||||
// log level or sampling.
|
|
||||
func (e *Event) Enabled() bool { |
|
||||
return e != nil && e.level != Disabled |
|
||||
} |
|
||||
|
|
||||
// Discard disables the event so Msg(f) won't print it.
|
|
||||
func (e *Event) Discard() *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.level = Disabled |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Msg sends the *Event with msg added as the message field if not empty.
|
|
||||
//
|
|
||||
// NOTICE: once this method is called, the *Event should be disposed.
|
|
||||
// Calling Msg twice can have unexpected result.
|
|
||||
func (e *Event) Msg(msg string) { |
|
||||
if e == nil { |
|
||||
return |
|
||||
} |
|
||||
e.msg(msg) |
|
||||
} |
|
||||
|
|
||||
// Msgf sends the event with formated msg added as the message field if not empty.
|
|
||||
//
|
|
||||
// NOTICE: once this methid is called, the *Event should be disposed.
|
|
||||
// Calling Msg twice can have unexpected result.
|
|
||||
func (e *Event) Msgf(format string, v ...interface{}) { |
|
||||
if e == nil { |
|
||||
return |
|
||||
} |
|
||||
e.msg(fmt.Sprintf(format, v...)) |
|
||||
} |
|
||||
|
|
||||
func (e *Event) msg(msg string) { |
|
||||
if len(e.ch) > 0 { |
|
||||
e.ch[0].Run(e, e.level, msg) |
|
||||
if len(e.ch) > 1 { |
|
||||
for _, hook := range e.ch[1:] { |
|
||||
hook.Run(e, e.level, msg) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
if msg != "" { |
|
||||
e.buf = enc.AppendString(enc.AppendKey(e.buf, MessageFieldName), msg) |
|
||||
} |
|
||||
if e.done != nil { |
|
||||
defer e.done(msg) |
|
||||
} |
|
||||
if err := e.write(); err != nil { |
|
||||
fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v", err) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Fields is a helper function to use a map to set fields using type assertion.
|
|
||||
func (e *Event) Fields(fields map[string]interface{}) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = appendFields(e.buf, fields) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Dict adds the field key with a dict to the event context.
|
|
||||
// Use zerolog.Dict() to create the dictionary.
|
|
||||
func (e *Event) Dict(key string, dict *Event) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
dict.buf = enc.AppendEndMarker(dict.buf) |
|
||||
e.buf = append(enc.AppendKey(e.buf, key), dict.buf...) |
|
||||
putEvent(dict) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Dict creates an Event to be used with the *Event.Dict method.
|
|
||||
// Call usual field methods like Str, Int etc to add fields to this
|
|
||||
// event and give it as argument the *Event.Dict method.
|
|
||||
func Dict() *Event { |
|
||||
return newEvent(nil, 0) |
|
||||
} |
|
||||
|
|
||||
// Array adds the field key with an array to the event context.
|
|
||||
// Use zerolog.Arr() to create the array or pass a type that
|
|
||||
// implement the LogArrayMarshaler interface.
|
|
||||
func (e *Event) Array(key string, arr LogArrayMarshaler) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendKey(e.buf, key) |
|
||||
var a *Array |
|
||||
if aa, ok := arr.(*Array); ok { |
|
||||
a = aa |
|
||||
} else { |
|
||||
a = Arr() |
|
||||
arr.MarshalZerologArray(a) |
|
||||
} |
|
||||
e.buf = a.write(e.buf) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
func (e *Event) appendObject(obj LogObjectMarshaler) { |
|
||||
e.buf = enc.AppendBeginMarker(e.buf) |
|
||||
obj.MarshalZerologObject(e) |
|
||||
e.buf = enc.AppendEndMarker(e.buf) |
|
||||
} |
|
||||
|
|
||||
// Object marshals an object that implement the LogObjectMarshaler interface.
|
|
||||
func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendKey(e.buf, key) |
|
||||
e.appendObject(obj) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Object marshals an object that implement the LogObjectMarshaler interface.
|
|
||||
func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
obj.MarshalZerologObject(e) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Str adds the field key with val as a string to the *Event context.
|
|
||||
func (e *Event) Str(key, val string) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendString(enc.AppendKey(e.buf, key), val) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Strs adds the field key with vals as a []string to the *Event context.
|
|
||||
func (e *Event) Strs(key string, vals []string) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendStrings(enc.AppendKey(e.buf, key), vals) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Bytes adds the field key with val as a string to the *Event context.
|
|
||||
//
|
|
||||
// Runes outside of normal ASCII ranges will be hex-encoded in the resulting
|
|
||||
// JSON.
|
|
||||
func (e *Event) Bytes(key string, val []byte) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendBytes(enc.AppendKey(e.buf, key), val) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Hex adds the field key with val as a hex string to the *Event context.
|
|
||||
func (e *Event) Hex(key string, val []byte) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendHex(enc.AppendKey(e.buf, key), val) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// RawJSON adds already encoded JSON to the log line under key.
|
|
||||
//
|
|
||||
// No sanity check is performed on b; it must not contain carriage returns and
|
|
||||
// be valid JSON.
|
|
||||
func (e *Event) RawJSON(key string, b []byte) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = appendJSON(enc.AppendKey(e.buf, key), b) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// AnErr adds the field key with serialized err to the *Event context.
|
|
||||
// If err is nil, no field is added.
|
|
||||
func (e *Event) AnErr(key string, err error) *Event { |
|
||||
marshaled := ErrorMarshalFunc(err) |
|
||||
switch m := marshaled.(type) { |
|
||||
case nil: |
|
||||
return e |
|
||||
case LogObjectMarshaler: |
|
||||
return e.Object(key, m) |
|
||||
case error: |
|
||||
return e.Str(key, m.Error()) |
|
||||
case string: |
|
||||
return e.Str(key, m) |
|
||||
default: |
|
||||
return e.Interface(key, m) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Errs adds the field key with errs as an array of serialized errors to the
|
|
||||
// *Event context.
|
|
||||
func (e *Event) Errs(key string, errs []error) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
arr := Arr() |
|
||||
for _, err := range errs { |
|
||||
marshaled := ErrorMarshalFunc(err) |
|
||||
switch m := marshaled.(type) { |
|
||||
case LogObjectMarshaler: |
|
||||
arr = arr.Object(m) |
|
||||
case error: |
|
||||
arr = arr.Err(m) |
|
||||
case string: |
|
||||
arr = arr.Str(m) |
|
||||
default: |
|
||||
arr = arr.Interface(m) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return e.Array(key, arr) |
|
||||
} |
|
||||
|
|
||||
// Err adds the field "error" with serialized err to the *Event context.
|
|
||||
// If err is nil, no field is added.
|
|
||||
// To customize the key name, change zerolog.ErrorFieldName.
|
|
||||
func (e *Event) Err(err error) *Event { |
|
||||
return e.AnErr(ErrorFieldName, err) |
|
||||
} |
|
||||
|
|
||||
// Bool adds the field key with val as a bool to the *Event context.
|
|
||||
func (e *Event) Bool(key string, b bool) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendBool(enc.AppendKey(e.buf, key), b) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Bools adds the field key with val as a []bool to the *Event context.
|
|
||||
func (e *Event) Bools(key string, b []bool) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendBools(enc.AppendKey(e.buf, key), b) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Int adds the field key with i as a int to the *Event context.
|
|
||||
func (e *Event) Int(key string, i int) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInt(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Ints adds the field key with i as a []int to the *Event context.
|
|
||||
func (e *Event) Ints(key string, i []int) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInts(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Int8 adds the field key with i as a int8 to the *Event context.
|
|
||||
func (e *Event) Int8(key string, i int8) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInt8(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Ints8 adds the field key with i as a []int8 to the *Event context.
|
|
||||
func (e *Event) Ints8(key string, i []int8) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInts8(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Int16 adds the field key with i as a int16 to the *Event context.
|
|
||||
func (e *Event) Int16(key string, i int16) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInt16(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Ints16 adds the field key with i as a []int16 to the *Event context.
|
|
||||
func (e *Event) Ints16(key string, i []int16) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInts16(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Int32 adds the field key with i as a int32 to the *Event context.
|
|
||||
func (e *Event) Int32(key string, i int32) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInt32(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Ints32 adds the field key with i as a []int32 to the *Event context.
|
|
||||
func (e *Event) Ints32(key string, i []int32) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInts32(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Int64 adds the field key with i as a int64 to the *Event context.
|
|
||||
func (e *Event) Int64(key string, i int64) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInt64(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Ints64 adds the field key with i as a []int64 to the *Event context.
|
|
||||
func (e *Event) Ints64(key string, i []int64) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendInts64(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uint adds the field key with i as a uint to the *Event context.
|
|
||||
func (e *Event) Uint(key string, i uint) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUint(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uints adds the field key with i as a []int to the *Event context.
|
|
||||
func (e *Event) Uints(key string, i []uint) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUints(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uint8 adds the field key with i as a uint8 to the *Event context.
|
|
||||
func (e *Event) Uint8(key string, i uint8) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUint8(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uints8 adds the field key with i as a []int8 to the *Event context.
|
|
||||
func (e *Event) Uints8(key string, i []uint8) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUints8(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uint16 adds the field key with i as a uint16 to the *Event context.
|
|
||||
func (e *Event) Uint16(key string, i uint16) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUint16(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uints16 adds the field key with i as a []int16 to the *Event context.
|
|
||||
func (e *Event) Uints16(key string, i []uint16) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUints16(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uint32 adds the field key with i as a uint32 to the *Event context.
|
|
||||
func (e *Event) Uint32(key string, i uint32) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUint32(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uints32 adds the field key with i as a []int32 to the *Event context.
|
|
||||
func (e *Event) Uints32(key string, i []uint32) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUints32(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uint64 adds the field key with i as a uint64 to the *Event context.
|
|
||||
func (e *Event) Uint64(key string, i uint64) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUint64(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Uints64 adds the field key with i as a []int64 to the *Event context.
|
|
||||
func (e *Event) Uints64(key string, i []uint64) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendUints64(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Float32 adds the field key with f as a float32 to the *Event context.
|
|
||||
func (e *Event) Float32(key string, f float32) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendFloat32(enc.AppendKey(e.buf, key), f) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Floats32 adds the field key with f as a []float32 to the *Event context.
|
|
||||
func (e *Event) Floats32(key string, f []float32) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendFloats32(enc.AppendKey(e.buf, key), f) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Float64 adds the field key with f as a float64 to the *Event context.
|
|
||||
func (e *Event) Float64(key string, f float64) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendFloat64(enc.AppendKey(e.buf, key), f) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Floats64 adds the field key with f as a []float64 to the *Event context.
|
|
||||
func (e *Event) Floats64(key string, f []float64) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendFloats64(enc.AppendKey(e.buf, key), f) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Timestamp adds the current local time as UNIX timestamp to the *Event context with the "time" key.
|
|
||||
// To customize the key name, change zerolog.TimestampFieldName.
|
|
||||
//
|
|
||||
// NOTE: It won't dedupe the "time" key if the *Event (or *Context) has one
|
|
||||
// already.
|
|
||||
func (e *Event) Timestamp() *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendTime(enc.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Time adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
|
||||
func (e *Event) Time(key string, t time.Time) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendTime(enc.AppendKey(e.buf, key), t, TimeFieldFormat) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Times adds the field key with t formated as string using zerolog.TimeFieldFormat.
|
|
||||
func (e *Event) Times(key string, t []time.Time) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendTimes(enc.AppendKey(e.buf, key), t, TimeFieldFormat) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Dur adds the field key with duration d stored as zerolog.DurationFieldUnit.
|
|
||||
// If zerolog.DurationFieldInteger is true, durations are rendered as integer
|
|
||||
// instead of float.
|
|
||||
func (e *Event) Dur(key string, d time.Duration) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Durs adds the field key with duration d stored as zerolog.DurationFieldUnit.
|
|
||||
// If zerolog.DurationFieldInteger is true, durations are rendered as integer
|
|
||||
// instead of float.
|
|
||||
func (e *Event) Durs(key string, d []time.Duration) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// TimeDiff adds the field key with positive duration between time t and start.
|
|
||||
// If time t is not greater than start, duration will be 0.
|
|
||||
// Duration format follows the same principle as Dur().
|
|
||||
func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
var d time.Duration |
|
||||
if t.After(start) { |
|
||||
d = t.Sub(start) |
|
||||
} |
|
||||
e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Interface adds the field key with i marshaled using reflection.
|
|
||||
func (e *Event) Interface(key string, i interface{}) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
if obj, ok := i.(LogObjectMarshaler); ok { |
|
||||
return e.Object(key, obj) |
|
||||
} |
|
||||
e.buf = enc.AppendInterface(enc.AppendKey(e.buf, key), i) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// Caller adds the file:line of the caller with the zerolog.CallerFieldName key.
|
|
||||
func (e *Event) Caller() *Event { |
|
||||
return e.caller(CallerSkipFrameCount) |
|
||||
} |
|
||||
|
|
||||
func (e *Event) caller(skip int) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
_, file, line, ok := runtime.Caller(skip) |
|
||||
if !ok { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), file+":"+strconv.Itoa(line)) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// IPAddr adds IPv4 or IPv6 Address to the event
|
|
||||
func (e *Event) IPAddr(key string, ip net.IP) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendIPAddr(enc.AppendKey(e.buf, key), ip) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the event
|
|
||||
func (e *Event) IPPrefix(key string, pfx net.IPNet) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendIPPrefix(enc.AppendKey(e.buf, key), pfx) |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// MACAddr adds MAC address to the event
|
|
||||
func (e *Event) MACAddr(key string, ha net.HardwareAddr) *Event { |
|
||||
if e == nil { |
|
||||
return e |
|
||||
} |
|
||||
e.buf = enc.AppendMACAddr(enc.AppendKey(e.buf, key), ha) |
|
||||
return e |
|
||||
} |
|
@ -1,178 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"net" |
|
||||
"sort" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
func appendFields(dst []byte, fields map[string]interface{}) []byte { |
|
||||
keys := make([]string, 0, len(fields)) |
|
||||
for key := range fields { |
|
||||
keys = append(keys, key) |
|
||||
} |
|
||||
sort.Strings(keys) |
|
||||
for _, key := range keys { |
|
||||
dst = enc.AppendKey(dst, key) |
|
||||
val := fields[key] |
|
||||
if val, ok := val.(LogObjectMarshaler); ok { |
|
||||
e := newEvent(nil, 0) |
|
||||
e.buf = e.buf[:0] |
|
||||
e.appendObject(val) |
|
||||
dst = append(dst, e.buf...) |
|
||||
putEvent(e) |
|
||||
continue |
|
||||
} |
|
||||
switch val := val.(type) { |
|
||||
case string: |
|
||||
dst = enc.AppendString(dst, val) |
|
||||
case []byte: |
|
||||
dst = enc.AppendBytes(dst, val) |
|
||||
case error: |
|
||||
marshaled := ErrorMarshalFunc(val) |
|
||||
switch m := marshaled.(type) { |
|
||||
case LogObjectMarshaler: |
|
||||
e := newEvent(nil, 0) |
|
||||
e.buf = e.buf[:0] |
|
||||
e.appendObject(m) |
|
||||
dst = append(dst, e.buf...) |
|
||||
putEvent(e) |
|
||||
case error: |
|
||||
dst = enc.AppendString(dst, m.Error()) |
|
||||
case string: |
|
||||
dst = enc.AppendString(dst, m) |
|
||||
default: |
|
||||
dst = enc.AppendInterface(dst, m) |
|
||||
} |
|
||||
case []error: |
|
||||
dst = enc.AppendArrayStart(dst) |
|
||||
for i, err := range val { |
|
||||
marshaled := ErrorMarshalFunc(err) |
|
||||
switch m := marshaled.(type) { |
|
||||
case LogObjectMarshaler: |
|
||||
e := newEvent(nil, 0) |
|
||||
e.buf = e.buf[:0] |
|
||||
e.appendObject(m) |
|
||||
dst = append(dst, e.buf...) |
|
||||
putEvent(e) |
|
||||
case error: |
|
||||
dst = enc.AppendString(dst, m.Error()) |
|
||||
case string: |
|
||||
dst = enc.AppendString(dst, m) |
|
||||
default: |
|
||||
dst = enc.AppendInterface(dst, m) |
|
||||
} |
|
||||
|
|
||||
if i < (len(val) - 1) { |
|
||||
enc.AppendArrayDelim(dst) |
|
||||
} |
|
||||
} |
|
||||
dst = enc.AppendArrayEnd(dst) |
|
||||
case bool: |
|
||||
dst = enc.AppendBool(dst, val) |
|
||||
case int: |
|
||||
dst = enc.AppendInt(dst, val) |
|
||||
case int8: |
|
||||
dst = enc.AppendInt8(dst, val) |
|
||||
case int16: |
|
||||
dst = enc.AppendInt16(dst, val) |
|
||||
case int32: |
|
||||
dst = enc.AppendInt32(dst, val) |
|
||||
case int64: |
|
||||
dst = enc.AppendInt64(dst, val) |
|
||||
case uint: |
|
||||
dst = enc.AppendUint(dst, val) |
|
||||
case uint8: |
|
||||
dst = enc.AppendUint8(dst, val) |
|
||||
case uint16: |
|
||||
dst = enc.AppendUint16(dst, val) |
|
||||
case uint32: |
|
||||
dst = enc.AppendUint32(dst, val) |
|
||||
case uint64: |
|
||||
dst = enc.AppendUint64(dst, val) |
|
||||
case float32: |
|
||||
dst = enc.AppendFloat32(dst, val) |
|
||||
case float64: |
|
||||
dst = enc.AppendFloat64(dst, val) |
|
||||
case time.Time: |
|
||||
dst = enc.AppendTime(dst, val, TimeFieldFormat) |
|
||||
case time.Duration: |
|
||||
dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) |
|
||||
case *string: |
|
||||
dst = enc.AppendString(dst, *val) |
|
||||
case *bool: |
|
||||
dst = enc.AppendBool(dst, *val) |
|
||||
case *int: |
|
||||
dst = enc.AppendInt(dst, *val) |
|
||||
case *int8: |
|
||||
dst = enc.AppendInt8(dst, *val) |
|
||||
case *int16: |
|
||||
dst = enc.AppendInt16(dst, *val) |
|
||||
case *int32: |
|
||||
dst = enc.AppendInt32(dst, *val) |
|
||||
case *int64: |
|
||||
dst = enc.AppendInt64(dst, *val) |
|
||||
case *uint: |
|
||||
dst = enc.AppendUint(dst, *val) |
|
||||
case *uint8: |
|
||||
dst = enc.AppendUint8(dst, *val) |
|
||||
case *uint16: |
|
||||
dst = enc.AppendUint16(dst, *val) |
|
||||
case *uint32: |
|
||||
dst = enc.AppendUint32(dst, *val) |
|
||||
case *uint64: |
|
||||
dst = enc.AppendUint64(dst, *val) |
|
||||
case *float32: |
|
||||
dst = enc.AppendFloat32(dst, *val) |
|
||||
case *float64: |
|
||||
dst = enc.AppendFloat64(dst, *val) |
|
||||
case *time.Time: |
|
||||
dst = enc.AppendTime(dst, *val, TimeFieldFormat) |
|
||||
case *time.Duration: |
|
||||
dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) |
|
||||
case []string: |
|
||||
dst = enc.AppendStrings(dst, val) |
|
||||
case []bool: |
|
||||
dst = enc.AppendBools(dst, val) |
|
||||
case []int: |
|
||||
dst = enc.AppendInts(dst, val) |
|
||||
case []int8: |
|
||||
dst = enc.AppendInts8(dst, val) |
|
||||
case []int16: |
|
||||
dst = enc.AppendInts16(dst, val) |
|
||||
case []int32: |
|
||||
dst = enc.AppendInts32(dst, val) |
|
||||
case []int64: |
|
||||
dst = enc.AppendInts64(dst, val) |
|
||||
case []uint: |
|
||||
dst = enc.AppendUints(dst, val) |
|
||||
// case []uint8:
|
|
||||
// dst = enc.AppendUints8(dst, val)
|
|
||||
case []uint16: |
|
||||
dst = enc.AppendUints16(dst, val) |
|
||||
case []uint32: |
|
||||
dst = enc.AppendUints32(dst, val) |
|
||||
case []uint64: |
|
||||
dst = enc.AppendUints64(dst, val) |
|
||||
case []float32: |
|
||||
dst = enc.AppendFloats32(dst, val) |
|
||||
case []float64: |
|
||||
dst = enc.AppendFloats64(dst, val) |
|
||||
case []time.Time: |
|
||||
dst = enc.AppendTimes(dst, val, TimeFieldFormat) |
|
||||
case []time.Duration: |
|
||||
dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger) |
|
||||
case nil: |
|
||||
dst = enc.AppendNil(dst) |
|
||||
case net.IP: |
|
||||
dst = enc.AppendIPAddr(dst, val) |
|
||||
case net.IPNet: |
|
||||
dst = enc.AppendIPPrefix(dst, val) |
|
||||
case net.HardwareAddr: |
|
||||
dst = enc.AppendMACAddr(dst, val) |
|
||||
default: |
|
||||
dst = enc.AppendInterface(dst, val) |
|
||||
} |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
@ -1,71 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import "time" |
|
||||
import "sync/atomic" |
|
||||
|
|
||||
var ( |
|
||||
// TimestampFieldName is the field name used for the timestamp field.
|
|
||||
TimestampFieldName = "time" |
|
||||
|
|
||||
// LevelFieldName is the field name used for the level field.
|
|
||||
LevelFieldName = "level" |
|
||||
|
|
||||
// MessageFieldName is the field name used for the message field.
|
|
||||
MessageFieldName = "message" |
|
||||
|
|
||||
// ErrorFieldName is the field name used for error fields.
|
|
||||
ErrorFieldName = "error" |
|
||||
|
|
||||
// CallerFieldName is the field name used for caller field.
|
|
||||
CallerFieldName = "caller" |
|
||||
|
|
||||
// CallerSkipFrameCount is the number of stack frames to skip to find the caller.
|
|
||||
CallerSkipFrameCount = 2 |
|
||||
|
|
||||
// TimeFieldFormat defines the time format of the Time field type.
|
|
||||
// If set to an empty string, the time is formatted as an UNIX timestamp
|
|
||||
// as integer.
|
|
||||
TimeFieldFormat = time.RFC3339 |
|
||||
|
|
||||
// TimestampFunc defines the function called to generate a timestamp.
|
|
||||
TimestampFunc = time.Now |
|
||||
|
|
||||
// DurationFieldUnit defines the unit for time.Duration type fields added
|
|
||||
// using the Dur method.
|
|
||||
DurationFieldUnit = time.Millisecond |
|
||||
|
|
||||
// DurationFieldInteger renders Dur fields as integer instead of float if
|
|
||||
// set to true.
|
|
||||
DurationFieldInteger = false |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
gLevel = new(uint32) |
|
||||
disableSampling = new(uint32) |
|
||||
) |
|
||||
|
|
||||
// SetGlobalLevel sets the global override for log level. If this
|
|
||||
// values is raised, all Loggers will use at least this value.
|
|
||||
//
|
|
||||
// To globally disable logs, set GlobalLevel to Disabled.
|
|
||||
func SetGlobalLevel(l Level) { |
|
||||
atomic.StoreUint32(gLevel, uint32(l)) |
|
||||
} |
|
||||
|
|
||||
// GlobalLevel returns the current global log level
|
|
||||
func GlobalLevel() Level { |
|
||||
return Level(atomic.LoadUint32(gLevel)) |
|
||||
} |
|
||||
|
|
||||
// DisableSampling will disable sampling in all Loggers if true.
|
|
||||
func DisableSampling(v bool) { |
|
||||
var i uint32 |
|
||||
if v { |
|
||||
i = 1 |
|
||||
} |
|
||||
atomic.StoreUint32(disableSampling, i) |
|
||||
} |
|
||||
|
|
||||
func samplingDisabled() bool { |
|
||||
return atomic.LoadUint32(disableSampling) == 1 |
|
||||
} |
|
@ -1 +0,0 @@ |
|||||
module github.com/rs/zerolog |
|
@ -1,60 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
// Hook defines an interface to a log hook.
|
|
||||
type Hook interface { |
|
||||
// Run runs the hook with the event.
|
|
||||
Run(e *Event, level Level, message string) |
|
||||
} |
|
||||
|
|
||||
// HookFunc is an adaptor to allow the use of an ordinary function
|
|
||||
// as a Hook.
|
|
||||
type HookFunc func(e *Event, level Level, message string) |
|
||||
|
|
||||
// Run implements the Hook interface.
|
|
||||
func (h HookFunc) Run(e *Event, level Level, message string) { |
|
||||
h(e, level, message) |
|
||||
} |
|
||||
|
|
||||
// LevelHook applies a different hook for each level.
|
|
||||
type LevelHook struct { |
|
||||
NoLevelHook, DebugHook, InfoHook, WarnHook, ErrorHook, FatalHook, PanicHook Hook |
|
||||
} |
|
||||
|
|
||||
// Run implements the Hook interface.
|
|
||||
func (h LevelHook) Run(e *Event, level Level, message string) { |
|
||||
switch level { |
|
||||
case DebugLevel: |
|
||||
if h.DebugHook != nil { |
|
||||
h.DebugHook.Run(e, level, message) |
|
||||
} |
|
||||
case InfoLevel: |
|
||||
if h.InfoHook != nil { |
|
||||
h.InfoHook.Run(e, level, message) |
|
||||
} |
|
||||
case WarnLevel: |
|
||||
if h.WarnHook != nil { |
|
||||
h.WarnHook.Run(e, level, message) |
|
||||
} |
|
||||
case ErrorLevel: |
|
||||
if h.ErrorHook != nil { |
|
||||
h.ErrorHook.Run(e, level, message) |
|
||||
} |
|
||||
case FatalLevel: |
|
||||
if h.FatalHook != nil { |
|
||||
h.FatalHook.Run(e, level, message) |
|
||||
} |
|
||||
case PanicLevel: |
|
||||
if h.PanicHook != nil { |
|
||||
h.PanicHook.Run(e, level, message) |
|
||||
} |
|
||||
case NoLevel: |
|
||||
if h.NoLevelHook != nil { |
|
||||
h.NoLevelHook.Run(e, level, message) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// NewLevelHook returns a new LevelHook.
|
|
||||
func NewLevelHook() LevelHook { |
|
||||
return LevelHook{} |
|
||||
} |
|
@ -1,56 +0,0 @@ |
|||||
## Reference: |
|
||||
CBOR Encoding is described in [RFC7049](https://tools.ietf.org/html/rfc7049) |
|
||||
|
|
||||
## Comparison of JSON vs CBOR |
|
||||
|
|
||||
Two main areas of reduction are: |
|
||||
|
|
||||
1. CPU usage to write a log msg |
|
||||
2. Size (in bytes) of log messages. |
|
||||
|
|
||||
|
|
||||
CPU Usage savings are below: |
|
||||
``` |
|
||||
name JSON time/op CBOR time/op delta |
|
||||
Info-32 15.3ns ± 1% 11.7ns ± 3% -23.78% (p=0.000 n=9+10) |
|
||||
ContextFields-32 16.2ns ± 2% 12.3ns ± 3% -23.97% (p=0.000 n=9+9) |
|
||||
ContextAppend-32 6.70ns ± 0% 6.20ns ± 0% -7.44% (p=0.000 n=9+9) |
|
||||
LogFields-32 66.4ns ± 0% 24.6ns ± 2% -62.89% (p=0.000 n=10+9) |
|
||||
LogArrayObject-32 911ns ±11% 768ns ± 6% -15.64% (p=0.000 n=10+10) |
|
||||
LogFieldType/Floats-32 70.3ns ± 2% 29.5ns ± 1% -57.98% (p=0.000 n=10+10) |
|
||||
LogFieldType/Err-32 14.0ns ± 3% 12.1ns ± 8% -13.20% (p=0.000 n=8+10) |
|
||||
LogFieldType/Dur-32 17.2ns ± 2% 13.1ns ± 1% -24.27% (p=0.000 n=10+9) |
|
||||
LogFieldType/Object-32 54.3ns ±11% 52.3ns ± 7% ~ (p=0.239 n=10+10) |
|
||||
LogFieldType/Ints-32 20.3ns ± 2% 15.1ns ± 2% -25.50% (p=0.000 n=9+10) |
|
||||
LogFieldType/Interfaces-32 642ns ±11% 621ns ± 9% ~ (p=0.118 n=10+10) |
|
||||
LogFieldType/Interface(Objects)-32 635ns ±13% 632ns ± 9% ~ (p=0.592 n=10+10) |
|
||||
LogFieldType/Times-32 294ns ± 0% 27ns ± 1% -90.71% (p=0.000 n=10+9) |
|
||||
LogFieldType/Durs-32 121ns ± 0% 33ns ± 2% -72.44% (p=0.000 n=9+9) |
|
||||
LogFieldType/Interface(Object)-32 56.6ns ± 8% 52.3ns ± 8% -7.54% (p=0.007 n=10+10) |
|
||||
LogFieldType/Errs-32 17.8ns ± 3% 16.1ns ± 2% -9.71% (p=0.000 n=10+9) |
|
||||
LogFieldType/Time-32 40.5ns ± 1% 12.7ns ± 6% -68.66% (p=0.000 n=8+9) |
|
||||
LogFieldType/Bool-32 12.0ns ± 5% 10.2ns ± 2% -15.18% (p=0.000 n=10+8) |
|
||||
LogFieldType/Bools-32 17.2ns ± 2% 12.6ns ± 4% -26.63% (p=0.000 n=10+10) |
|
||||
LogFieldType/Int-32 12.3ns ± 2% 11.2ns ± 4% -9.27% (p=0.000 n=9+10) |
|
||||
LogFieldType/Float-32 16.7ns ± 1% 12.6ns ± 2% -24.42% (p=0.000 n=7+9) |
|
||||
LogFieldType/Str-32 12.7ns ± 7% 11.3ns ± 7% -10.88% (p=0.000 n=10+9) |
|
||||
LogFieldType/Strs-32 20.3ns ± 3% 18.2ns ± 3% -10.25% (p=0.000 n=9+10) |
|
||||
LogFieldType/Interface-32 183ns ±12% 175ns ± 9% ~ (p=0.078 n=10+10) |
|
||||
``` |
|
||||
|
|
||||
Log message size savings is greatly dependent on the number and type of fields in the log message. |
|
||||
Assuming this log message (with an Integer, timestamp and string, in addition to level). |
|
||||
|
|
||||
`{"level":"error","Fault":41650,"time":"2018-04-01T15:18:19-07:00","message":"Some Message"}` |
|
||||
|
|
||||
Two measurements were done for the log file sizes - one without any compression, second |
|
||||
using [compress/zlib](https://golang.org/pkg/compress/zlib/). |
|
||||
|
|
||||
Results for 10,000 log messages: |
|
||||
|
|
||||
| Log Format | Plain File Size (in KB) | Compressed File Size (in KB) | |
|
||||
| :--- | :---: | :---: | |
|
||||
| JSON | 920 | 28 | |
|
||||
| CBOR | 550 | 28 | |
|
||||
|
|
||||
The example used to calculate the above data is available in [Examples](examples). |
|
@ -1,11 +0,0 @@ |
|||||
package cbor |
|
||||
|
|
||||
type Encoder struct{} |
|
||||
|
|
||||
// AppendKey adds a key (string) to the binary encoded log message
|
|
||||
func (e Encoder) AppendKey(dst []byte, key string) []byte { |
|
||||
if len(dst) < 1 { |
|
||||
dst = e.AppendBeginMarker(dst) |
|
||||
} |
|
||||
return e.AppendString(dst, key) |
|
||||
} |
|
@ -1,100 +0,0 @@ |
|||||
// Package cbor provides primitives for storing different data
|
|
||||
// in the CBOR (binary) format. CBOR is defined in RFC7049.
|
|
||||
package cbor |
|
||||
|
|
||||
import "time" |
|
||||
|
|
||||
const ( |
|
||||
majorOffset = 5 |
|
||||
additionalMax = 23 |
|
||||
|
|
||||
// Non Values.
|
|
||||
additionalTypeBoolFalse byte = 20 |
|
||||
additionalTypeBoolTrue byte = 21 |
|
||||
additionalTypeNull byte = 22 |
|
||||
|
|
||||
// Integer (+ve and -ve) Sub-types.
|
|
||||
additionalTypeIntUint8 byte = 24 |
|
||||
additionalTypeIntUint16 byte = 25 |
|
||||
additionalTypeIntUint32 byte = 26 |
|
||||
additionalTypeIntUint64 byte = 27 |
|
||||
|
|
||||
// Float Sub-types.
|
|
||||
additionalTypeFloat16 byte = 25 |
|
||||
additionalTypeFloat32 byte = 26 |
|
||||
additionalTypeFloat64 byte = 27 |
|
||||
additionalTypeBreak byte = 31 |
|
||||
|
|
||||
// Tag Sub-types.
|
|
||||
additionalTypeTimestamp byte = 01 |
|
||||
|
|
||||
// Extended Tags - from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml
|
|
||||
additionalTypeTagNetworkAddr uint16 = 260 |
|
||||
additionalTypeTagNetworkPrefix uint16 = 261 |
|
||||
additionalTypeEmbeddedJSON uint16 = 262 |
|
||||
additionalTypeTagHexString uint16 = 263 |
|
||||
|
|
||||
// Unspecified number of elements.
|
|
||||
additionalTypeInfiniteCount byte = 31 |
|
||||
) |
|
||||
const ( |
|
||||
majorTypeUnsignedInt byte = iota << majorOffset // Major type 0
|
|
||||
majorTypeNegativeInt // Major type 1
|
|
||||
majorTypeByteString // Major type 2
|
|
||||
majorTypeUtf8String // Major type 3
|
|
||||
majorTypeArray // Major type 4
|
|
||||
majorTypeMap // Major type 5
|
|
||||
majorTypeTags // Major type 6
|
|
||||
majorTypeSimpleAndFloat // Major type 7
|
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
maskOutAdditionalType byte = (7 << majorOffset) |
|
||||
maskOutMajorType byte = 31 |
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
float32Nan = "\xfa\x7f\xc0\x00\x00" |
|
||||
float32PosInfinity = "\xfa\x7f\x80\x00\x00" |
|
||||
float32NegInfinity = "\xfa\xff\x80\x00\x00" |
|
||||
float64Nan = "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00" |
|
||||
float64PosInfinity = "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00" |
|
||||
float64NegInfinity = "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00" |
|
||||
) |
|
||||
|
|
||||
// IntegerTimeFieldFormat indicates the format of timestamp decoded
|
|
||||
// from an integer (time in seconds).
|
|
||||
var IntegerTimeFieldFormat = time.RFC3339 |
|
||||
|
|
||||
// NanoTimeFieldFormat indicates the format of timestamp decoded
|
|
||||
// from a float value (time in seconds and nano seconds).
|
|
||||
var NanoTimeFieldFormat = time.RFC3339Nano |
|
||||
|
|
||||
func appendCborTypePrefix(dst []byte, major byte, number uint64) []byte { |
|
||||
byteCount := 8 |
|
||||
var minor byte |
|
||||
switch { |
|
||||
case number < 256: |
|
||||
byteCount = 1 |
|
||||
minor = additionalTypeIntUint8 |
|
||||
|
|
||||
case number < 65536: |
|
||||
byteCount = 2 |
|
||||
minor = additionalTypeIntUint16 |
|
||||
|
|
||||
case number < 4294967296: |
|
||||
byteCount = 4 |
|
||||
minor = additionalTypeIntUint32 |
|
||||
|
|
||||
default: |
|
||||
byteCount = 8 |
|
||||
minor = additionalTypeIntUint64 |
|
||||
|
|
||||
} |
|
||||
dst = append(dst, byte(major|minor)) |
|
||||
byteCount-- |
|
||||
for ; byteCount >= 0; byteCount-- { |
|
||||
dst = append(dst, byte(number>>(uint(byteCount)*8))) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
@ -1,614 +0,0 @@ |
|||||
package cbor |
|
||||
|
|
||||
// This file contains code to decode a stream of CBOR Data into JSON.
|
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"bytes" |
|
||||
"fmt" |
|
||||
"io" |
|
||||
"math" |
|
||||
"net" |
|
||||
"runtime" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
"time" |
|
||||
"unicode/utf8" |
|
||||
) |
|
||||
|
|
||||
var decodeTimeZone *time.Location |
|
||||
|
|
||||
const hexTable = "0123456789abcdef" |
|
||||
|
|
||||
const isFloat32 = 4 |
|
||||
const isFloat64 = 8 |
|
||||
|
|
||||
func readNBytes(src *bufio.Reader, n int) []byte { |
|
||||
ret := make([]byte, n) |
|
||||
for i := 0; i < n; i++ { |
|
||||
ch, e := src.ReadByte() |
|
||||
if e != nil { |
|
||||
panic(fmt.Errorf("Tried to Read %d Bytes.. But hit end of file", n)) |
|
||||
} |
|
||||
ret[i] = ch |
|
||||
} |
|
||||
return ret |
|
||||
} |
|
||||
|
|
||||
func readByte(src *bufio.Reader) byte { |
|
||||
b, e := src.ReadByte() |
|
||||
if e != nil { |
|
||||
panic(fmt.Errorf("Tried to Read 1 Byte.. But hit end of file")) |
|
||||
} |
|
||||
return b |
|
||||
} |
|
||||
|
|
||||
func decodeIntAdditonalType(src *bufio.Reader, minor byte) int64 { |
|
||||
val := int64(0) |
|
||||
if minor <= 23 { |
|
||||
val = int64(minor) |
|
||||
} else { |
|
||||
bytesToRead := 0 |
|
||||
switch minor { |
|
||||
case additionalTypeIntUint8: |
|
||||
bytesToRead = 1 |
|
||||
case additionalTypeIntUint16: |
|
||||
bytesToRead = 2 |
|
||||
case additionalTypeIntUint32: |
|
||||
bytesToRead = 4 |
|
||||
case additionalTypeIntUint64: |
|
||||
bytesToRead = 8 |
|
||||
default: |
|
||||
panic(fmt.Errorf("Invalid Additional Type: %d in decodeInteger (expected <28)", minor)) |
|
||||
} |
|
||||
pb := readNBytes(src, bytesToRead) |
|
||||
for i := 0; i < bytesToRead; i++ { |
|
||||
val = val * 256 |
|
||||
val += int64(pb[i]) |
|
||||
} |
|
||||
} |
|
||||
return val |
|
||||
} |
|
||||
|
|
||||
func decodeInteger(src *bufio.Reader) int64 { |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeUnsignedInt && major != majorTypeNegativeInt { |
|
||||
panic(fmt.Errorf("Major type is: %d in decodeInteger!! (expected 0 or 1)", major)) |
|
||||
} |
|
||||
val := decodeIntAdditonalType(src, minor) |
|
||||
if major == 0 { |
|
||||
return val |
|
||||
} |
|
||||
return (-1 - val) |
|
||||
} |
|
||||
|
|
||||
func decodeFloat(src *bufio.Reader) (float64, int) { |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeSimpleAndFloat { |
|
||||
panic(fmt.Errorf("Incorrect Major type is: %d in decodeFloat", major)) |
|
||||
} |
|
||||
|
|
||||
switch minor { |
|
||||
case additionalTypeFloat16: |
|
||||
panic(fmt.Errorf("float16 is not suppported in decodeFloat")) |
|
||||
|
|
||||
case additionalTypeFloat32: |
|
||||
pb := readNBytes(src, 4) |
|
||||
switch string(pb) { |
|
||||
case float32Nan: |
|
||||
return math.NaN(), isFloat32 |
|
||||
case float32PosInfinity: |
|
||||
return math.Inf(0), isFloat32 |
|
||||
case float32NegInfinity: |
|
||||
return math.Inf(-1), isFloat32 |
|
||||
} |
|
||||
n := uint32(0) |
|
||||
for i := 0; i < 4; i++ { |
|
||||
n = n * 256 |
|
||||
n += uint32(pb[i]) |
|
||||
} |
|
||||
val := math.Float32frombits(n) |
|
||||
return float64(val), isFloat32 |
|
||||
case additionalTypeFloat64: |
|
||||
pb := readNBytes(src, 8) |
|
||||
switch string(pb) { |
|
||||
case float64Nan: |
|
||||
return math.NaN(), isFloat64 |
|
||||
case float64PosInfinity: |
|
||||
return math.Inf(0), isFloat64 |
|
||||
case float64NegInfinity: |
|
||||
return math.Inf(-1), isFloat64 |
|
||||
} |
|
||||
n := uint64(0) |
|
||||
for i := 0; i < 8; i++ { |
|
||||
n = n * 256 |
|
||||
n += uint64(pb[i]) |
|
||||
} |
|
||||
val := math.Float64frombits(n) |
|
||||
return val, isFloat64 |
|
||||
} |
|
||||
panic(fmt.Errorf("Invalid Additional Type: %d in decodeFloat", minor)) |
|
||||
} |
|
||||
|
|
||||
func decodeStringComplex(dst []byte, s string, pos uint) []byte { |
|
||||
i := int(pos) |
|
||||
start := 0 |
|
||||
|
|
||||
for i < len(s) { |
|
||||
b := s[i] |
|
||||
if b >= utf8.RuneSelf { |
|
||||
r, size := utf8.DecodeRuneInString(s[i:]) |
|
||||
if r == utf8.RuneError && size == 1 { |
|
||||
// In case of error, first append previous simple characters to
|
|
||||
// the byte slice if any and append a replacement character code
|
|
||||
// in place of the invalid sequence.
|
|
||||
if start < i { |
|
||||
dst = append(dst, s[start:i]...) |
|
||||
} |
|
||||
dst = append(dst, `\ufffd`...) |
|
||||
i += size |
|
||||
start = i |
|
||||
continue |
|
||||
} |
|
||||
i += size |
|
||||
continue |
|
||||
} |
|
||||
if b >= 0x20 && b <= 0x7e && b != '\\' && b != '"' { |
|
||||
i++ |
|
||||
continue |
|
||||
} |
|
||||
// We encountered a character that needs to be encoded.
|
|
||||
// Let's append the previous simple characters to the byte slice
|
|
||||
// and switch our operation to read and encode the remainder
|
|
||||
// characters byte-by-byte.
|
|
||||
if start < i { |
|
||||
dst = append(dst, s[start:i]...) |
|
||||
} |
|
||||
switch b { |
|
||||
case '"', '\\': |
|
||||
dst = append(dst, '\\', b) |
|
||||
case '\b': |
|
||||
dst = append(dst, '\\', 'b') |
|
||||
case '\f': |
|
||||
dst = append(dst, '\\', 'f') |
|
||||
case '\n': |
|
||||
dst = append(dst, '\\', 'n') |
|
||||
case '\r': |
|
||||
dst = append(dst, '\\', 'r') |
|
||||
case '\t': |
|
||||
dst = append(dst, '\\', 't') |
|
||||
default: |
|
||||
dst = append(dst, '\\', 'u', '0', '0', hexTable[b>>4], hexTable[b&0xF]) |
|
||||
} |
|
||||
i++ |
|
||||
start = i |
|
||||
} |
|
||||
if start < len(s) { |
|
||||
dst = append(dst, s[start:]...) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
func decodeString(src *bufio.Reader, noQuotes bool) []byte { |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeByteString { |
|
||||
panic(fmt.Errorf("Major type is: %d in decodeString", major)) |
|
||||
} |
|
||||
result := []byte{} |
|
||||
if !noQuotes { |
|
||||
result = append(result, '"') |
|
||||
} |
|
||||
length := decodeIntAdditonalType(src, minor) |
|
||||
len := int(length) |
|
||||
pbs := readNBytes(src, len) |
|
||||
result = append(result, pbs...) |
|
||||
if noQuotes { |
|
||||
return result |
|
||||
} |
|
||||
return append(result, '"') |
|
||||
} |
|
||||
|
|
||||
func decodeUTF8String(src *bufio.Reader) []byte { |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeUtf8String { |
|
||||
panic(fmt.Errorf("Major type is: %d in decodeUTF8String", major)) |
|
||||
} |
|
||||
result := []byte{'"'} |
|
||||
length := decodeIntAdditonalType(src, minor) |
|
||||
len := int(length) |
|
||||
pbs := readNBytes(src, len) |
|
||||
|
|
||||
for i := 0; i < len; i++ { |
|
||||
// Check if the character needs encoding. Control characters, slashes,
|
|
||||
// and the double quote need json encoding. Bytes above the ascii
|
|
||||
// boundary needs utf8 encoding.
|
|
||||
if pbs[i] < 0x20 || pbs[i] > 0x7e || pbs[i] == '\\' || pbs[i] == '"' { |
|
||||
// We encountered a character that needs to be encoded. Switch
|
|
||||
// to complex version of the algorithm.
|
|
||||
dst := []byte{'"'} |
|
||||
dst = decodeStringComplex(dst, string(pbs), uint(i)) |
|
||||
return append(dst, '"') |
|
||||
} |
|
||||
} |
|
||||
// The string has no need for encoding an therefore is directly
|
|
||||
// appended to the byte slice.
|
|
||||
result = append(result, pbs...) |
|
||||
return append(result, '"') |
|
||||
} |
|
||||
|
|
||||
func array2Json(src *bufio.Reader, dst io.Writer) { |
|
||||
dst.Write([]byte{'['}) |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeArray { |
|
||||
panic(fmt.Errorf("Major type is: %d in array2Json", major)) |
|
||||
} |
|
||||
len := 0 |
|
||||
unSpecifiedCount := false |
|
||||
if minor == additionalTypeInfiniteCount { |
|
||||
unSpecifiedCount = true |
|
||||
} else { |
|
||||
length := decodeIntAdditonalType(src, minor) |
|
||||
len = int(length) |
|
||||
} |
|
||||
for i := 0; unSpecifiedCount || i < len; i++ { |
|
||||
if unSpecifiedCount { |
|
||||
pb, e := src.Peek(1) |
|
||||
if e != nil { |
|
||||
panic(e) |
|
||||
} |
|
||||
if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
|
||||
readByte(src) |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
cbor2JsonOneObject(src, dst) |
|
||||
if unSpecifiedCount { |
|
||||
pb, e := src.Peek(1) |
|
||||
if e != nil { |
|
||||
panic(e) |
|
||||
} |
|
||||
if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
|
||||
readByte(src) |
|
||||
break |
|
||||
} |
|
||||
dst.Write([]byte{','}) |
|
||||
} else if i+1 < len { |
|
||||
dst.Write([]byte{','}) |
|
||||
} |
|
||||
} |
|
||||
dst.Write([]byte{']'}) |
|
||||
} |
|
||||
|
|
||||
func map2Json(src *bufio.Reader, dst io.Writer) { |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeMap { |
|
||||
panic(fmt.Errorf("Major type is: %d in map2Json", major)) |
|
||||
} |
|
||||
len := 0 |
|
||||
unSpecifiedCount := false |
|
||||
if minor == additionalTypeInfiniteCount { |
|
||||
unSpecifiedCount = true |
|
||||
} else { |
|
||||
length := decodeIntAdditonalType(src, minor) |
|
||||
len = int(length) |
|
||||
} |
|
||||
dst.Write([]byte{'{'}) |
|
||||
for i := 0; unSpecifiedCount || i < len; i++ { |
|
||||
if unSpecifiedCount { |
|
||||
pb, e := src.Peek(1) |
|
||||
if e != nil { |
|
||||
panic(e) |
|
||||
} |
|
||||
if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
|
||||
readByte(src) |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
cbor2JsonOneObject(src, dst) |
|
||||
if i%2 == 0 { |
|
||||
// Even position values are keys.
|
|
||||
dst.Write([]byte{':'}) |
|
||||
} else { |
|
||||
if unSpecifiedCount { |
|
||||
pb, e := src.Peek(1) |
|
||||
if e != nil { |
|
||||
panic(e) |
|
||||
} |
|
||||
if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { |
|
||||
readByte(src) |
|
||||
break |
|
||||
} |
|
||||
dst.Write([]byte{','}) |
|
||||
} else if i+1 < len { |
|
||||
dst.Write([]byte{','}) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
dst.Write([]byte{'}'}) |
|
||||
} |
|
||||
|
|
||||
func decodeTagData(src *bufio.Reader) []byte { |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeTags { |
|
||||
panic(fmt.Errorf("Major type is: %d in decodeTagData", major)) |
|
||||
} |
|
||||
switch minor { |
|
||||
case additionalTypeTimestamp: |
|
||||
return decodeTimeStamp(src) |
|
||||
|
|
||||
// Tag value is larger than 256 (so uint16).
|
|
||||
case additionalTypeIntUint16: |
|
||||
val := decodeIntAdditonalType(src, minor) |
|
||||
|
|
||||
switch uint16(val) { |
|
||||
case additionalTypeEmbeddedJSON: |
|
||||
pb := readByte(src) |
|
||||
dataMajor := pb & maskOutAdditionalType |
|
||||
if dataMajor != majorTypeByteString { |
|
||||
panic(fmt.Errorf("Unsupported embedded Type: %d in decodeEmbeddedJSON", dataMajor)) |
|
||||
} |
|
||||
src.UnreadByte() |
|
||||
return decodeString(src, true) |
|
||||
|
|
||||
case additionalTypeTagNetworkAddr: |
|
||||
octets := decodeString(src, true) |
|
||||
ss := []byte{'"'} |
|
||||
switch len(octets) { |
|
||||
case 6: // MAC address.
|
|
||||
ha := net.HardwareAddr(octets) |
|
||||
ss = append(append(ss, ha.String()...), '"') |
|
||||
case 4: // IPv4 address.
|
|
||||
fallthrough |
|
||||
case 16: // IPv6 address.
|
|
||||
ip := net.IP(octets) |
|
||||
ss = append(append(ss, ip.String()...), '"') |
|
||||
default: |
|
||||
panic(fmt.Errorf("Unexpected Network Address length: %d (expected 4,6,16)", len(octets))) |
|
||||
} |
|
||||
return ss |
|
||||
|
|
||||
case additionalTypeTagNetworkPrefix: |
|
||||
pb := readByte(src) |
|
||||
if pb != byte(majorTypeMap|0x1) { |
|
||||
panic(fmt.Errorf("IP Prefix is NOT of MAP of 1 elements as expected")) |
|
||||
} |
|
||||
octets := decodeString(src, true) |
|
||||
val := decodeInteger(src) |
|
||||
ip := net.IP(octets) |
|
||||
var mask net.IPMask |
|
||||
pfxLen := int(val) |
|
||||
if len(octets) == 4 { |
|
||||
mask = net.CIDRMask(pfxLen, 32) |
|
||||
} else { |
|
||||
mask = net.CIDRMask(pfxLen, 128) |
|
||||
} |
|
||||
ipPfx := net.IPNet{IP: ip, Mask: mask} |
|
||||
ss := []byte{'"'} |
|
||||
ss = append(append(ss, ipPfx.String()...), '"') |
|
||||
return ss |
|
||||
|
|
||||
case additionalTypeTagHexString: |
|
||||
octets := decodeString(src, true) |
|
||||
ss := []byte{'"'} |
|
||||
for _, v := range octets { |
|
||||
ss = append(ss, hexTable[v>>4], hexTable[v&0x0f]) |
|
||||
} |
|
||||
return append(ss, '"') |
|
||||
|
|
||||
default: |
|
||||
panic(fmt.Errorf("Unsupported Additional Tag Type: %d in decodeTagData", val)) |
|
||||
} |
|
||||
} |
|
||||
panic(fmt.Errorf("Unsupported Additional Type: %d in decodeTagData", minor)) |
|
||||
} |
|
||||
|
|
||||
func decodeTimeStamp(src *bufio.Reader) []byte { |
|
||||
pb := readByte(src) |
|
||||
src.UnreadByte() |
|
||||
tsMajor := pb & maskOutAdditionalType |
|
||||
if tsMajor == majorTypeUnsignedInt || tsMajor == majorTypeNegativeInt { |
|
||||
n := decodeInteger(src) |
|
||||
t := time.Unix(n, 0) |
|
||||
if decodeTimeZone != nil { |
|
||||
t = t.In(decodeTimeZone) |
|
||||
} else { |
|
||||
t = t.In(time.UTC) |
|
||||
} |
|
||||
tsb := []byte{} |
|
||||
tsb = append(tsb, '"') |
|
||||
tsb = t.AppendFormat(tsb, IntegerTimeFieldFormat) |
|
||||
tsb = append(tsb, '"') |
|
||||
return tsb |
|
||||
} else if tsMajor == majorTypeSimpleAndFloat { |
|
||||
n, _ := decodeFloat(src) |
|
||||
secs := int64(n) |
|
||||
n -= float64(secs) |
|
||||
n *= float64(1e9) |
|
||||
t := time.Unix(secs, int64(n)) |
|
||||
if decodeTimeZone != nil { |
|
||||
t = t.In(decodeTimeZone) |
|
||||
} else { |
|
||||
t = t.In(time.UTC) |
|
||||
} |
|
||||
tsb := []byte{} |
|
||||
tsb = append(tsb, '"') |
|
||||
tsb = t.AppendFormat(tsb, NanoTimeFieldFormat) |
|
||||
tsb = append(tsb, '"') |
|
||||
return tsb |
|
||||
} |
|
||||
panic(fmt.Errorf("TS format is neigther int nor float: %d", tsMajor)) |
|
||||
} |
|
||||
|
|
||||
func decodeSimpleFloat(src *bufio.Reader) []byte { |
|
||||
pb := readByte(src) |
|
||||
major := pb & maskOutAdditionalType |
|
||||
minor := pb & maskOutMajorType |
|
||||
if major != majorTypeSimpleAndFloat { |
|
||||
panic(fmt.Errorf("Major type is: %d in decodeSimpleFloat", major)) |
|
||||
} |
|
||||
switch minor { |
|
||||
case additionalTypeBoolTrue: |
|
||||
return []byte("true") |
|
||||
case additionalTypeBoolFalse: |
|
||||
return []byte("false") |
|
||||
case additionalTypeNull: |
|
||||
return []byte("null") |
|
||||
case additionalTypeFloat16: |
|
||||
fallthrough |
|
||||
case additionalTypeFloat32: |
|
||||
fallthrough |
|
||||
case additionalTypeFloat64: |
|
||||
src.UnreadByte() |
|
||||
v, bc := decodeFloat(src) |
|
||||
ba := []byte{} |
|
||||
switch { |
|
||||
case math.IsNaN(v): |
|
||||
return []byte("\"NaN\"") |
|
||||
case math.IsInf(v, 1): |
|
||||
return []byte("\"+Inf\"") |
|
||||
case math.IsInf(v, -1): |
|
||||
return []byte("\"-Inf\"") |
|
||||
} |
|
||||
if bc == isFloat32 { |
|
||||
ba = strconv.AppendFloat(ba, v, 'f', -1, 32) |
|
||||
} else if bc == isFloat64 { |
|
||||
ba = strconv.AppendFloat(ba, v, 'f', -1, 64) |
|
||||
} else { |
|
||||
panic(fmt.Errorf("Invalid Float precision from decodeFloat: %d", bc)) |
|
||||
} |
|
||||
return ba |
|
||||
default: |
|
||||
panic(fmt.Errorf("Invalid Additional Type: %d in decodeSimpleFloat", minor)) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func cbor2JsonOneObject(src *bufio.Reader, dst io.Writer) { |
|
||||
pb, e := src.Peek(1) |
|
||||
if e != nil { |
|
||||
panic(e) |
|
||||
} |
|
||||
major := (pb[0] & maskOutAdditionalType) |
|
||||
|
|
||||
switch major { |
|
||||
case majorTypeUnsignedInt: |
|
||||
fallthrough |
|
||||
case majorTypeNegativeInt: |
|
||||
n := decodeInteger(src) |
|
||||
dst.Write([]byte(strconv.Itoa(int(n)))) |
|
||||
|
|
||||
case majorTypeByteString: |
|
||||
s := decodeString(src, false) |
|
||||
dst.Write(s) |
|
||||
|
|
||||
case majorTypeUtf8String: |
|
||||
s := decodeUTF8String(src) |
|
||||
dst.Write(s) |
|
||||
|
|
||||
case majorTypeArray: |
|
||||
array2Json(src, dst) |
|
||||
|
|
||||
case majorTypeMap: |
|
||||
map2Json(src, dst) |
|
||||
|
|
||||
case majorTypeTags: |
|
||||
s := decodeTagData(src) |
|
||||
dst.Write(s) |
|
||||
|
|
||||
case majorTypeSimpleAndFloat: |
|
||||
s := decodeSimpleFloat(src) |
|
||||
dst.Write(s) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func moreBytesToRead(src *bufio.Reader) bool { |
|
||||
_, e := src.ReadByte() |
|
||||
if e == nil { |
|
||||
src.UnreadByte() |
|
||||
return true |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
// Cbor2JsonManyObjects decodes all the CBOR Objects read from src
|
|
||||
// reader. It keeps on decoding until reader returns EOF (error when reading).
|
|
||||
// Decoded string is written to the dst. At the end of every CBOR Object
|
|
||||
// newline is written to the output stream.
|
|
||||
//
|
|
||||
// Returns error (if any) that was encountered during decode.
|
|
||||
// The child functions will generate a panic when error is encountered and
|
|
||||
// this function will recover non-runtime Errors and return the reason as error.
|
|
||||
func Cbor2JsonManyObjects(src io.Reader, dst io.Writer) (err error) { |
|
||||
defer func() { |
|
||||
if r := recover(); r != nil { |
|
||||
if _, ok := r.(runtime.Error); ok { |
|
||||
panic(r) |
|
||||
} |
|
||||
err = r.(error) |
|
||||
} |
|
||||
}() |
|
||||
bufRdr := bufio.NewReader(src) |
|
||||
for moreBytesToRead(bufRdr) { |
|
||||
cbor2JsonOneObject(bufRdr, dst) |
|
||||
dst.Write([]byte("\n")) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Detect if the bytes to be printed is Binary or not.
|
|
||||
func binaryFmt(p []byte) bool { |
|
||||
if len(p) > 0 && p[0] > 0x7F { |
|
||||
return true |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func getReader(str string) *bufio.Reader { |
|
||||
return bufio.NewReader(strings.NewReader(str)) |
|
||||
} |
|
||||
|
|
||||
// DecodeIfBinaryToString converts a binary formatted log msg to a
|
|
||||
// JSON formatted String Log message - suitable for printing to Console/Syslog.
|
|
||||
func DecodeIfBinaryToString(in []byte) string { |
|
||||
if binaryFmt(in) { |
|
||||
var b bytes.Buffer |
|
||||
Cbor2JsonManyObjects(strings.NewReader(string(in)), &b) |
|
||||
return b.String() |
|
||||
} |
|
||||
return string(in) |
|
||||
} |
|
||||
|
|
||||
// DecodeObjectToStr checks if the input is a binary format, if so,
|
|
||||
// it will decode a single Object and return the decoded string.
|
|
||||
func DecodeObjectToStr(in []byte) string { |
|
||||
if binaryFmt(in) { |
|
||||
var b bytes.Buffer |
|
||||
cbor2JsonOneObject(getReader(string(in)), &b) |
|
||||
return b.String() |
|
||||
} |
|
||||
return string(in) |
|
||||
} |
|
||||
|
|
||||
// DecodeIfBinaryToBytes checks if the input is a binary format, if so,
|
|
||||
// it will decode all Objects and return the decoded string as byte array.
|
|
||||
func DecodeIfBinaryToBytes(in []byte) []byte { |
|
||||
if binaryFmt(in) { |
|
||||
var b bytes.Buffer |
|
||||
Cbor2JsonManyObjects(bytes.NewReader(in), &b) |
|
||||
return b.Bytes() |
|
||||
} |
|
||||
return in |
|
||||
} |
|
@ -1,68 +0,0 @@ |
|||||
package cbor |
|
||||
|
|
||||
// AppendStrings encodes and adds an array of strings to the dst byte array.
|
|
||||
func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendString(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendString encodes and adds a string to the dst byte array.
|
|
||||
func (Encoder) AppendString(dst []byte, s string) []byte { |
|
||||
major := majorTypeUtf8String |
|
||||
|
|
||||
l := len(s) |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, majorTypeUtf8String, uint64(l)) |
|
||||
} |
|
||||
return append(dst, s...) |
|
||||
} |
|
||||
|
|
||||
// AppendBytes encodes and adds an array of bytes to the dst byte array.
|
|
||||
func (Encoder) AppendBytes(dst, s []byte) []byte { |
|
||||
major := majorTypeByteString |
|
||||
|
|
||||
l := len(s) |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
return append(dst, s...) |
|
||||
} |
|
||||
|
|
||||
// AppendEmbeddedJSON adds a tag and embeds input JSON as such.
|
|
||||
func AppendEmbeddedJSON(dst, s []byte) []byte { |
|
||||
major := majorTypeTags |
|
||||
minor := additionalTypeEmbeddedJSON |
|
||||
|
|
||||
// Append the TAG to indicate this is Embedded JSON.
|
|
||||
dst = append(dst, byte(major|additionalTypeIntUint16)) |
|
||||
dst = append(dst, byte(minor>>8)) |
|
||||
dst = append(dst, byte(minor&0xff)) |
|
||||
|
|
||||
// Append the JSON Object as Byte String.
|
|
||||
major = majorTypeByteString |
|
||||
|
|
||||
l := len(s) |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
return append(dst, s...) |
|
||||
} |
|
@ -1,93 +0,0 @@ |
|||||
package cbor |
|
||||
|
|
||||
import ( |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
func appendIntegerTimestamp(dst []byte, t time.Time) []byte { |
|
||||
major := majorTypeTags |
|
||||
minor := additionalTypeTimestamp |
|
||||
dst = append(dst, byte(major|minor)) |
|
||||
secs := t.Unix() |
|
||||
var val uint64 |
|
||||
if secs < 0 { |
|
||||
major = majorTypeNegativeInt |
|
||||
val = uint64(-secs - 1) |
|
||||
} else { |
|
||||
major = majorTypeUnsignedInt |
|
||||
val = uint64(secs) |
|
||||
} |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(val)) |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
func (e Encoder) appendFloatTimestamp(dst []byte, t time.Time) []byte { |
|
||||
major := majorTypeTags |
|
||||
minor := additionalTypeTimestamp |
|
||||
dst = append(dst, byte(major|minor)) |
|
||||
secs := t.Unix() |
|
||||
nanos := t.Nanosecond() |
|
||||
var val float64 |
|
||||
val = float64(secs)*1.0 + float64(nanos)*1E-9 |
|
||||
return e.AppendFloat64(dst, val) |
|
||||
} |
|
||||
|
|
||||
// AppendTime encodes and adds a timestamp to the dst byte array.
|
|
||||
func (e Encoder) AppendTime(dst []byte, t time.Time, unused string) []byte { |
|
||||
utc := t.UTC() |
|
||||
if utc.Nanosecond() == 0 { |
|
||||
return appendIntegerTimestamp(dst, utc) |
|
||||
} |
|
||||
return e.appendFloatTimestamp(dst, utc) |
|
||||
} |
|
||||
|
|
||||
// AppendTimes encodes and adds an array of timestamps to the dst byte array.
|
|
||||
func (e Encoder) AppendTimes(dst []byte, vals []time.Time, unused string) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
|
|
||||
for _, t := range vals { |
|
||||
dst = e.AppendTime(dst, t, unused) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendDuration encodes and adds a duration to the dst byte array.
|
|
||||
// useInt field indicates whether to store the duration as seconds (integer) or
|
|
||||
// as seconds+nanoseconds (float).
|
|
||||
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { |
|
||||
if useInt { |
|
||||
return e.AppendInt64(dst, int64(d/unit)) |
|
||||
} |
|
||||
return e.AppendFloat64(dst, float64(d)/float64(unit)) |
|
||||
} |
|
||||
|
|
||||
// AppendDurations encodes and adds an array of durations to the dst byte array.
|
|
||||
// useInt field indicates whether to store the duration as seconds (integer) or
|
|
||||
// as seconds+nanoseconds (float).
|
|
||||
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, d := range vals { |
|
||||
dst = e.AppendDuration(dst, d, unit, useInt) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
@ -1,478 +0,0 @@ |
|||||
package cbor |
|
||||
|
|
||||
import ( |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"math" |
|
||||
"net" |
|
||||
) |
|
||||
|
|
||||
// AppendNil inserts a 'Nil' object into the dst byte array.
|
|
||||
func (Encoder) AppendNil(dst []byte) []byte { |
|
||||
return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeNull)) |
|
||||
} |
|
||||
|
|
||||
// AppendBeginMarker inserts a map start into the dst byte array.
|
|
||||
func (Encoder) AppendBeginMarker(dst []byte) []byte { |
|
||||
return append(dst, byte(majorTypeMap|additionalTypeInfiniteCount)) |
|
||||
} |
|
||||
|
|
||||
// AppendEndMarker inserts a map end into the dst byte array.
|
|
||||
func (Encoder) AppendEndMarker(dst []byte) []byte { |
|
||||
return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak)) |
|
||||
} |
|
||||
|
|
||||
// AppendObjectData takes an object in form of a byte array and appends to dst.
|
|
||||
func (Encoder) AppendObjectData(dst []byte, o []byte) []byte { |
|
||||
// BeginMarker is present in the dst, which
|
|
||||
// should not be copied when appending to existing data.
|
|
||||
return append(dst, o[1:]...) |
|
||||
} |
|
||||
|
|
||||
// AppendArrayStart adds markers to indicate the start of an array.
|
|
||||
func (Encoder) AppendArrayStart(dst []byte) []byte { |
|
||||
return append(dst, byte(majorTypeArray|additionalTypeInfiniteCount)) |
|
||||
} |
|
||||
|
|
||||
// AppendArrayEnd adds markers to indicate the end of an array.
|
|
||||
func (Encoder) AppendArrayEnd(dst []byte) []byte { |
|
||||
return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak)) |
|
||||
} |
|
||||
|
|
||||
// AppendArrayDelim adds markers to indicate end of a particular array element.
|
|
||||
func (Encoder) AppendArrayDelim(dst []byte) []byte { |
|
||||
//No delimiters needed in cbor
|
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendLineBreak is a noop that keep API compat with json encoder.
|
|
||||
func (Encoder) AppendLineBreak(dst []byte) []byte { |
|
||||
// No line breaks needed in binary format.
|
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendBool encodes and inserts a boolean value into the dst byte array.
|
|
||||
func (Encoder) AppendBool(dst []byte, val bool) []byte { |
|
||||
b := additionalTypeBoolFalse |
|
||||
if val { |
|
||||
b = additionalTypeBoolTrue |
|
||||
} |
|
||||
return append(dst, byte(majorTypeSimpleAndFloat|b)) |
|
||||
} |
|
||||
|
|
||||
// AppendBools encodes and inserts an array of boolean values into the dst byte array.
|
|
||||
func (e Encoder) AppendBools(dst []byte, vals []bool) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendBool(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt encodes and inserts an integer value into the dst byte array.
|
|
||||
func (Encoder) AppendInt(dst []byte, val int) []byte { |
|
||||
major := majorTypeUnsignedInt |
|
||||
contentVal := val |
|
||||
if val < 0 { |
|
||||
major = majorTypeNegativeInt |
|
||||
contentVal = -val - 1 |
|
||||
} |
|
||||
if contentVal <= additionalMax { |
|
||||
lb := byte(contentVal) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(contentVal)) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInts encodes and inserts an array of integer values into the dst byte array.
|
|
||||
func (e Encoder) AppendInts(dst []byte, vals []int) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendInt(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt8 encodes and inserts an int8 value into the dst byte array.
|
|
||||
func (e Encoder) AppendInt8(dst []byte, val int8) []byte { |
|
||||
return e.AppendInt(dst, int(val)) |
|
||||
} |
|
||||
|
|
||||
// AppendInts8 encodes and inserts an array of integer values into the dst byte array.
|
|
||||
func (e Encoder) AppendInts8(dst []byte, vals []int8) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendInt(dst, int(v)) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt16 encodes and inserts a int16 value into the dst byte array.
|
|
||||
func (e Encoder) AppendInt16(dst []byte, val int16) []byte { |
|
||||
return e.AppendInt(dst, int(val)) |
|
||||
} |
|
||||
|
|
||||
// AppendInts16 encodes and inserts an array of int16 values into the dst byte array.
|
|
||||
func (e Encoder) AppendInts16(dst []byte, vals []int16) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendInt(dst, int(v)) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt32 encodes and inserts a int32 value into the dst byte array.
|
|
||||
func (e Encoder) AppendInt32(dst []byte, val int32) []byte { |
|
||||
return e.AppendInt(dst, int(val)) |
|
||||
} |
|
||||
|
|
||||
// AppendInts32 encodes and inserts an array of int32 values into the dst byte array.
|
|
||||
func (e Encoder) AppendInts32(dst []byte, vals []int32) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendInt(dst, int(v)) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt64 encodes and inserts a int64 value into the dst byte array.
|
|
||||
func (Encoder) AppendInt64(dst []byte, val int64) []byte { |
|
||||
major := majorTypeUnsignedInt |
|
||||
contentVal := val |
|
||||
if val < 0 { |
|
||||
major = majorTypeNegativeInt |
|
||||
contentVal = -val - 1 |
|
||||
} |
|
||||
if contentVal <= additionalMax { |
|
||||
lb := byte(contentVal) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(contentVal)) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInts64 encodes and inserts an array of int64 values into the dst byte array.
|
|
||||
func (e Encoder) AppendInts64(dst []byte, vals []int64) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendInt64(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint encodes and inserts an unsigned integer value into the dst byte array.
|
|
||||
func (e Encoder) AppendUint(dst []byte, val uint) []byte { |
|
||||
return e.AppendInt64(dst, int64(val)) |
|
||||
} |
|
||||
|
|
||||
// AppendUints encodes and inserts an array of unsigned integer values into the dst byte array.
|
|
||||
func (e Encoder) AppendUints(dst []byte, vals []uint) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendUint(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint8 encodes and inserts a unsigned int8 value into the dst byte array.
|
|
||||
func (e Encoder) AppendUint8(dst []byte, val uint8) []byte { |
|
||||
return e.AppendUint(dst, uint(val)) |
|
||||
} |
|
||||
|
|
||||
// AppendUints8 encodes and inserts an array of uint8 values into the dst byte array.
|
|
||||
func (e Encoder) AppendUints8(dst []byte, vals []uint8) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendUint8(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint16 encodes and inserts a uint16 value into the dst byte array.
|
|
||||
func (e Encoder) AppendUint16(dst []byte, val uint16) []byte { |
|
||||
return e.AppendUint(dst, uint(val)) |
|
||||
} |
|
||||
|
|
||||
// AppendUints16 encodes and inserts an array of uint16 values into the dst byte array.
|
|
||||
func (e Encoder) AppendUints16(dst []byte, vals []uint16) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendUint16(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint32 encodes and inserts a uint32 value into the dst byte array.
|
|
||||
func (e Encoder) AppendUint32(dst []byte, val uint32) []byte { |
|
||||
return e.AppendUint(dst, uint(val)) |
|
||||
} |
|
||||
|
|
||||
// AppendUints32 encodes and inserts an array of uint32 values into the dst byte array.
|
|
||||
func (e Encoder) AppendUints32(dst []byte, vals []uint32) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendUint32(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint64 encodes and inserts a uint64 value into the dst byte array.
|
|
||||
func (Encoder) AppendUint64(dst []byte, val uint64) []byte { |
|
||||
major := majorTypeUnsignedInt |
|
||||
contentVal := val |
|
||||
if contentVal <= additionalMax { |
|
||||
lb := byte(contentVal) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(contentVal)) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUints64 encodes and inserts an array of uint64 values into the dst byte array.
|
|
||||
func (e Encoder) AppendUints64(dst []byte, vals []uint64) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendUint64(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendFloat32 encodes and inserts a single precision float value into the dst byte array.
|
|
||||
func (Encoder) AppendFloat32(dst []byte, val float32) []byte { |
|
||||
switch { |
|
||||
case math.IsNaN(float64(val)): |
|
||||
return append(dst, "\xfa\x7f\xc0\x00\x00"...) |
|
||||
case math.IsInf(float64(val), 1): |
|
||||
return append(dst, "\xfa\x7f\x80\x00\x00"...) |
|
||||
case math.IsInf(float64(val), -1): |
|
||||
return append(dst, "\xfa\xff\x80\x00\x00"...) |
|
||||
} |
|
||||
major := majorTypeSimpleAndFloat |
|
||||
subType := additionalTypeFloat32 |
|
||||
n := math.Float32bits(val) |
|
||||
var buf [4]byte |
|
||||
for i := uint(0); i < 4; i++ { |
|
||||
buf[i] = byte(n >> ((3 - i) * 8)) |
|
||||
} |
|
||||
return append(append(dst, byte(major|subType)), buf[0], buf[1], buf[2], buf[3]) |
|
||||
} |
|
||||
|
|
||||
// AppendFloats32 encodes and inserts an array of single precision float value into the dst byte array.
|
|
||||
func (e Encoder) AppendFloats32(dst []byte, vals []float32) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendFloat32(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendFloat64 encodes and inserts a double precision float value into the dst byte array.
|
|
||||
func (Encoder) AppendFloat64(dst []byte, val float64) []byte { |
|
||||
switch { |
|
||||
case math.IsNaN(val): |
|
||||
return append(dst, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"...) |
|
||||
case math.IsInf(val, 1): |
|
||||
return append(dst, "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00"...) |
|
||||
case math.IsInf(val, -1): |
|
||||
return append(dst, "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00"...) |
|
||||
} |
|
||||
major := majorTypeSimpleAndFloat |
|
||||
subType := additionalTypeFloat64 |
|
||||
n := math.Float64bits(val) |
|
||||
dst = append(dst, byte(major|subType)) |
|
||||
for i := uint(1); i <= 8; i++ { |
|
||||
b := byte(n >> ((8 - i) * 8)) |
|
||||
dst = append(dst, b) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendFloats64 encodes and inserts an array of double precision float values into the dst byte array.
|
|
||||
func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte { |
|
||||
major := majorTypeArray |
|
||||
l := len(vals) |
|
||||
if l == 0 { |
|
||||
return e.AppendArrayEnd(e.AppendArrayStart(dst)) |
|
||||
} |
|
||||
if l <= additionalMax { |
|
||||
lb := byte(l) |
|
||||
dst = append(dst, byte(major|lb)) |
|
||||
} else { |
|
||||
dst = appendCborTypePrefix(dst, major, uint64(l)) |
|
||||
} |
|
||||
for _, v := range vals { |
|
||||
dst = e.AppendFloat64(dst, v) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInterface takes an arbitrary object and converts it to JSON and embeds it dst.
|
|
||||
func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { |
|
||||
marshaled, err := json.Marshal(i) |
|
||||
if err != nil { |
|
||||
return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) |
|
||||
} |
|
||||
return AppendEmbeddedJSON(dst, marshaled) |
|
||||
} |
|
||||
|
|
||||
// AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6).
|
|
||||
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte { |
|
||||
dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) |
|
||||
dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) |
|
||||
dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) |
|
||||
return e.AppendBytes(dst, ip) |
|
||||
} |
|
||||
|
|
||||
// AppendIPPrefix encodes and inserts an IP Address Prefix (Address + Mask Length).
|
|
||||
func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { |
|
||||
dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) |
|
||||
dst = append(dst, byte(additionalTypeTagNetworkPrefix>>8)) |
|
||||
dst = append(dst, byte(additionalTypeTagNetworkPrefix&0xff)) |
|
||||
|
|
||||
// Prefix is a tuple (aka MAP of 1 pair of elements) -
|
|
||||
// first element is prefix, second is mask length.
|
|
||||
dst = append(dst, byte(majorTypeMap|0x1)) |
|
||||
dst = e.AppendBytes(dst, pfx.IP) |
|
||||
maskLen, _ := pfx.Mask.Size() |
|
||||
return e.AppendUint8(dst, uint8(maskLen)) |
|
||||
} |
|
||||
|
|
||||
// AppendMACAddr encodes and inserts an Hardware (MAC) address.
|
|
||||
func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { |
|
||||
dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) |
|
||||
dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) |
|
||||
dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) |
|
||||
return e.AppendBytes(dst, ha) |
|
||||
} |
|
||||
|
|
||||
// AppendHex adds a TAG and inserts a hex bytes as a string.
|
|
||||
func (e Encoder) AppendHex(dst []byte, val []byte) []byte { |
|
||||
dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) |
|
||||
dst = append(dst, byte(additionalTypeTagHexString>>8)) |
|
||||
dst = append(dst, byte(additionalTypeTagHexString&0xff)) |
|
||||
return e.AppendBytes(dst, val) |
|
||||
} |
|
@ -1,12 +0,0 @@ |
|||||
package json |
|
||||
|
|
||||
type Encoder struct{} |
|
||||
|
|
||||
// AppendKey appends a new key to the output JSON.
|
|
||||
func (e Encoder) AppendKey(dst []byte, key string) []byte { |
|
||||
if len(dst) > 1 && dst[len(dst)-1] != '{' { |
|
||||
dst = append(dst, ',') |
|
||||
} |
|
||||
dst = e.AppendString(dst, key) |
|
||||
return append(dst, ':') |
|
||||
} |
|
@ -1,85 +0,0 @@ |
|||||
package json |
|
||||
|
|
||||
import "unicode/utf8" |
|
||||
|
|
||||
// AppendBytes is a mirror of appendString with []byte arg
|
|
||||
func (Encoder) AppendBytes(dst, s []byte) []byte { |
|
||||
dst = append(dst, '"') |
|
||||
for i := 0; i < len(s); i++ { |
|
||||
if !noEscapeTable[s[i]] { |
|
||||
dst = appendBytesComplex(dst, s, i) |
|
||||
return append(dst, '"') |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, s...) |
|
||||
return append(dst, '"') |
|
||||
} |
|
||||
|
|
||||
// AppendHex encodes the input bytes to a hex string and appends
|
|
||||
// the encoded string to the input byte slice.
|
|
||||
//
|
|
||||
// The operation loops though each byte and encodes it as hex using
|
|
||||
// the hex lookup table.
|
|
||||
func (Encoder) AppendHex(dst, s []byte) []byte { |
|
||||
dst = append(dst, '"') |
|
||||
for _, v := range s { |
|
||||
dst = append(dst, hex[v>>4], hex[v&0x0f]) |
|
||||
} |
|
||||
return append(dst, '"') |
|
||||
} |
|
||||
|
|
||||
// appendBytesComplex is a mirror of the appendStringComplex
|
|
||||
// with []byte arg
|
|
||||
func appendBytesComplex(dst, s []byte, i int) []byte { |
|
||||
start := 0 |
|
||||
for i < len(s) { |
|
||||
b := s[i] |
|
||||
if b >= utf8.RuneSelf { |
|
||||
r, size := utf8.DecodeRune(s[i:]) |
|
||||
if r == utf8.RuneError && size == 1 { |
|
||||
if start < i { |
|
||||
dst = append(dst, s[start:i]...) |
|
||||
} |
|
||||
dst = append(dst, `\ufffd`...) |
|
||||
i += size |
|
||||
start = i |
|
||||
continue |
|
||||
} |
|
||||
i += size |
|
||||
continue |
|
||||
} |
|
||||
if noEscapeTable[b] { |
|
||||
i++ |
|
||||
continue |
|
||||
} |
|
||||
// We encountered a character that needs to be encoded.
|
|
||||
// Let's append the previous simple characters to the byte slice
|
|
||||
// and switch our operation to read and encode the remainder
|
|
||||
// characters byte-by-byte.
|
|
||||
if start < i { |
|
||||
dst = append(dst, s[start:i]...) |
|
||||
} |
|
||||
switch b { |
|
||||
case '"', '\\': |
|
||||
dst = append(dst, '\\', b) |
|
||||
case '\b': |
|
||||
dst = append(dst, '\\', 'b') |
|
||||
case '\f': |
|
||||
dst = append(dst, '\\', 'f') |
|
||||
case '\n': |
|
||||
dst = append(dst, '\\', 'n') |
|
||||
case '\r': |
|
||||
dst = append(dst, '\\', 'r') |
|
||||
case '\t': |
|
||||
dst = append(dst, '\\', 't') |
|
||||
default: |
|
||||
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) |
|
||||
} |
|
||||
i++ |
|
||||
start = i |
|
||||
} |
|
||||
if start < len(s) { |
|
||||
dst = append(dst, s[start:]...) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
@ -1,121 +0,0 @@ |
|||||
package json |
|
||||
|
|
||||
import "unicode/utf8" |
|
||||
|
|
||||
const hex = "0123456789abcdef" |
|
||||
|
|
||||
var noEscapeTable = [256]bool{} |
|
||||
|
|
||||
func init() { |
|
||||
for i := 0; i <= 0x7e; i++ { |
|
||||
noEscapeTable[i] = i >= 0x20 && i != '\\' && i != '"' |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// AppendStrings encodes the input strings to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = e.AppendString(dst, vals[0]) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = e.AppendString(append(dst, ','), val) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendString encodes the input string to json and appends
|
|
||||
// the encoded string to the input byte slice.
|
|
||||
//
|
|
||||
// The operation loops though each byte in the string looking
|
|
||||
// for characters that need json or utf8 encoding. If the string
|
|
||||
// does not need encoding, then the string is appended in it's
|
|
||||
// entirety to the byte slice.
|
|
||||
// If we encounter a byte that does need encoding, switch up
|
|
||||
// the operation and perform a byte-by-byte read-encode-append.
|
|
||||
func (Encoder) AppendString(dst []byte, s string) []byte { |
|
||||
// Start with a double quote.
|
|
||||
dst = append(dst, '"') |
|
||||
// Loop through each character in the string.
|
|
||||
for i := 0; i < len(s); i++ { |
|
||||
// Check if the character needs encoding. Control characters, slashes,
|
|
||||
// and the double quote need json encoding. Bytes above the ascii
|
|
||||
// boundary needs utf8 encoding.
|
|
||||
if !noEscapeTable[s[i]] { |
|
||||
// We encountered a character that needs to be encoded. Switch
|
|
||||
// to complex version of the algorithm.
|
|
||||
dst = appendStringComplex(dst, s, i) |
|
||||
return append(dst, '"') |
|
||||
} |
|
||||
} |
|
||||
// The string has no need for encoding an therefore is directly
|
|
||||
// appended to the byte slice.
|
|
||||
dst = append(dst, s...) |
|
||||
// End with a double quote
|
|
||||
return append(dst, '"') |
|
||||
} |
|
||||
|
|
||||
// appendStringComplex is used by appendString to take over an in
|
|
||||
// progress JSON string encoding that encountered a character that needs
|
|
||||
// to be encoded.
|
|
||||
func appendStringComplex(dst []byte, s string, i int) []byte { |
|
||||
start := 0 |
|
||||
for i < len(s) { |
|
||||
b := s[i] |
|
||||
if b >= utf8.RuneSelf { |
|
||||
r, size := utf8.DecodeRuneInString(s[i:]) |
|
||||
if r == utf8.RuneError && size == 1 { |
|
||||
// In case of error, first append previous simple characters to
|
|
||||
// the byte slice if any and append a remplacement character code
|
|
||||
// in place of the invalid sequence.
|
|
||||
if start < i { |
|
||||
dst = append(dst, s[start:i]...) |
|
||||
} |
|
||||
dst = append(dst, `\ufffd`...) |
|
||||
i += size |
|
||||
start = i |
|
||||
continue |
|
||||
} |
|
||||
i += size |
|
||||
continue |
|
||||
} |
|
||||
if noEscapeTable[b] { |
|
||||
i++ |
|
||||
continue |
|
||||
} |
|
||||
// We encountered a character that needs to be encoded.
|
|
||||
// Let's append the previous simple characters to the byte slice
|
|
||||
// and switch our operation to read and encode the remainder
|
|
||||
// characters byte-by-byte.
|
|
||||
if start < i { |
|
||||
dst = append(dst, s[start:i]...) |
|
||||
} |
|
||||
switch b { |
|
||||
case '"', '\\': |
|
||||
dst = append(dst, '\\', b) |
|
||||
case '\b': |
|
||||
dst = append(dst, '\\', 'b') |
|
||||
case '\f': |
|
||||
dst = append(dst, '\\', 'f') |
|
||||
case '\n': |
|
||||
dst = append(dst, '\\', 'n') |
|
||||
case '\r': |
|
||||
dst = append(dst, '\\', 'r') |
|
||||
case '\t': |
|
||||
dst = append(dst, '\\', 't') |
|
||||
default: |
|
||||
dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) |
|
||||
} |
|
||||
i++ |
|
||||
start = i |
|
||||
} |
|
||||
if start < len(s) { |
|
||||
dst = append(dst, s[start:]...) |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
@ -1,76 +0,0 @@ |
|||||
package json |
|
||||
|
|
||||
import ( |
|
||||
"strconv" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
// AppendTime formats the input time with the given format
|
|
||||
// and appends the encoded string to the input byte slice.
|
|
||||
func (e Encoder) AppendTime(dst []byte, t time.Time, format string) []byte { |
|
||||
if format == "" { |
|
||||
return e.AppendInt64(dst, t.Unix()) |
|
||||
} |
|
||||
return append(t.AppendFormat(append(dst, '"'), format), '"') |
|
||||
} |
|
||||
|
|
||||
// AppendTimes converts the input times with the given format
|
|
||||
// and appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendTimes(dst []byte, vals []time.Time, format string) []byte { |
|
||||
if format == "" { |
|
||||
return appendUnixTimes(dst, vals) |
|
||||
} |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = append(vals[0].AppendFormat(append(dst, '"'), format), '"') |
|
||||
if len(vals) > 1 { |
|
||||
for _, t := range vals[1:] { |
|
||||
dst = append(t.AppendFormat(append(dst, ',', '"'), format), '"') |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
func appendUnixTimes(dst []byte, vals []time.Time) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendInt(dst, vals[0].Unix(), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, t := range vals[1:] { |
|
||||
dst = strconv.AppendInt(append(dst, ','), t.Unix(), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendDuration formats the input duration with the given unit & format
|
|
||||
// and appends the encoded string to the input byte slice.
|
|
||||
func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { |
|
||||
if useInt { |
|
||||
return strconv.AppendInt(dst, int64(d/unit), 10) |
|
||||
} |
|
||||
return e.AppendFloat64(dst, float64(d)/float64(unit)) |
|
||||
} |
|
||||
|
|
||||
// AppendDurations formats the input durations with the given unit & format
|
|
||||
// and appends the encoded string list to the input byte slice.
|
|
||||
func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = e.AppendDuration(dst, vals[0], unit, useInt) |
|
||||
if len(vals) > 1 { |
|
||||
for _, d := range vals[1:] { |
|
||||
dst = e.AppendDuration(append(dst, ','), d, unit, useInt) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
@ -1,402 +0,0 @@ |
|||||
package json |
|
||||
|
|
||||
import ( |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"math" |
|
||||
"net" |
|
||||
"strconv" |
|
||||
) |
|
||||
|
|
||||
// AppendNil inserts a 'Nil' object into the dst byte array.
|
|
||||
func (Encoder) AppendNil(dst []byte) []byte { |
|
||||
return append(dst, "null"...) |
|
||||
} |
|
||||
|
|
||||
// AppendBeginMarker inserts a map start into the dst byte array.
|
|
||||
func (Encoder) AppendBeginMarker(dst []byte) []byte { |
|
||||
return append(dst, '{') |
|
||||
} |
|
||||
|
|
||||
// AppendEndMarker inserts a map end into the dst byte array.
|
|
||||
func (Encoder) AppendEndMarker(dst []byte) []byte { |
|
||||
return append(dst, '}') |
|
||||
} |
|
||||
|
|
||||
// AppendLineBreak appends a line break.
|
|
||||
func (Encoder) AppendLineBreak(dst []byte) []byte { |
|
||||
return append(dst, '\n') |
|
||||
} |
|
||||
|
|
||||
// AppendArrayStart adds markers to indicate the start of an array.
|
|
||||
func (Encoder) AppendArrayStart(dst []byte) []byte { |
|
||||
return append(dst, '[') |
|
||||
} |
|
||||
|
|
||||
// AppendArrayEnd adds markers to indicate the end of an array.
|
|
||||
func (Encoder) AppendArrayEnd(dst []byte) []byte { |
|
||||
return append(dst, ']') |
|
||||
} |
|
||||
|
|
||||
// AppendArrayDelim adds markers to indicate end of a particular array element.
|
|
||||
func (Encoder) AppendArrayDelim(dst []byte) []byte { |
|
||||
if len(dst) > 0 { |
|
||||
return append(dst, ',') |
|
||||
} |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendBool converts the input bool to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendBool(dst []byte, val bool) []byte { |
|
||||
return strconv.AppendBool(dst, val) |
|
||||
} |
|
||||
|
|
||||
// AppendBools encodes the input bools to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendBools(dst []byte, vals []bool) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendBool(dst, vals[0]) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendBool(append(dst, ','), val) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt converts the input int to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendInt(dst []byte, val int) []byte { |
|
||||
return strconv.AppendInt(dst, int64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendInts encodes the input ints to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendInts(dst []byte, vals []int) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt8 converts the input []int8 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendInt8(dst []byte, val int8) []byte { |
|
||||
return strconv.AppendInt(dst, int64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendInts8 encodes the input int8s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendInts8(dst []byte, vals []int8) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt16 converts the input int16 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendInt16(dst []byte, val int16) []byte { |
|
||||
return strconv.AppendInt(dst, int64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendInts16 encodes the input int16s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendInts16(dst []byte, vals []int16) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt32 converts the input int32 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendInt32(dst []byte, val int32) []byte { |
|
||||
return strconv.AppendInt(dst, int64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendInts32 encodes the input int32s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendInts32(dst []byte, vals []int32) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendInt(dst, int64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendInt(append(dst, ','), int64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInt64 converts the input int64 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendInt64(dst []byte, val int64) []byte { |
|
||||
return strconv.AppendInt(dst, val, 10) |
|
||||
} |
|
||||
|
|
||||
// AppendInts64 encodes the input int64s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendInts64(dst []byte, vals []int64) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendInt(dst, vals[0], 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendInt(append(dst, ','), val, 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint converts the input uint to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendUint(dst []byte, val uint) []byte { |
|
||||
return strconv.AppendUint(dst, uint64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendUints encodes the input uints to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendUints(dst []byte, vals []uint) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint8 converts the input uint8 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendUint8(dst []byte, val uint8) []byte { |
|
||||
return strconv.AppendUint(dst, uint64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendUints8 encodes the input uint8s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendUints8(dst []byte, vals []uint8) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint16 converts the input uint16 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendUint16(dst []byte, val uint16) []byte { |
|
||||
return strconv.AppendUint(dst, uint64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendUints16 encodes the input uint16s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendUints16(dst []byte, vals []uint16) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint32 converts the input uint32 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendUint32(dst []byte, val uint32) []byte { |
|
||||
return strconv.AppendUint(dst, uint64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendUints32 encodes the input uint32s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendUints32(dst []byte, vals []uint32) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendUint(dst, uint64(vals[0]), 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendUint64 converts the input uint64 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendUint64(dst []byte, val uint64) []byte { |
|
||||
return strconv.AppendUint(dst, uint64(val), 10) |
|
||||
} |
|
||||
|
|
||||
// AppendUints64 encodes the input uint64s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendUints64(dst []byte, vals []uint64) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = strconv.AppendUint(dst, vals[0], 10) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = strconv.AppendUint(append(dst, ','), val, 10) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
func appendFloat(dst []byte, val float64, bitSize int) []byte { |
|
||||
// JSON does not permit NaN or Infinity. A typical JSON encoder would fail
|
|
||||
// with an error, but a logging library wants the data to get thru so we
|
|
||||
// make a tradeoff and store those types as string.
|
|
||||
switch { |
|
||||
case math.IsNaN(val): |
|
||||
return append(dst, `"NaN"`...) |
|
||||
case math.IsInf(val, 1): |
|
||||
return append(dst, `"+Inf"`...) |
|
||||
case math.IsInf(val, -1): |
|
||||
return append(dst, `"-Inf"`...) |
|
||||
} |
|
||||
return strconv.AppendFloat(dst, val, 'f', -1, bitSize) |
|
||||
} |
|
||||
|
|
||||
// AppendFloat32 converts the input float32 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendFloat32(dst []byte, val float32) []byte { |
|
||||
return appendFloat(dst, float64(val), 32) |
|
||||
} |
|
||||
|
|
||||
// AppendFloats32 encodes the input float32s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendFloats32(dst []byte, vals []float32) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = appendFloat(dst, float64(vals[0]), 32) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = appendFloat(append(dst, ','), float64(val), 32) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendFloat64 converts the input float64 to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (Encoder) AppendFloat64(dst []byte, val float64) []byte { |
|
||||
return appendFloat(dst, val, 64) |
|
||||
} |
|
||||
|
|
||||
// AppendFloats64 encodes the input float64s to json and
|
|
||||
// appends the encoded string list to the input byte slice.
|
|
||||
func (Encoder) AppendFloats64(dst []byte, vals []float64) []byte { |
|
||||
if len(vals) == 0 { |
|
||||
return append(dst, '[', ']') |
|
||||
} |
|
||||
dst = append(dst, '[') |
|
||||
dst = appendFloat(dst, vals[0], 32) |
|
||||
if len(vals) > 1 { |
|
||||
for _, val := range vals[1:] { |
|
||||
dst = appendFloat(append(dst, ','), val, 64) |
|
||||
} |
|
||||
} |
|
||||
dst = append(dst, ']') |
|
||||
return dst |
|
||||
} |
|
||||
|
|
||||
// AppendInterface marshals the input interface to a string and
|
|
||||
// appends the encoded string to the input byte slice.
|
|
||||
func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { |
|
||||
marshaled, err := json.Marshal(i) |
|
||||
if err != nil { |
|
||||
return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) |
|
||||
} |
|
||||
return append(dst, marshaled...) |
|
||||
} |
|
||||
|
|
||||
// AppendObjectData takes in an object that is already in a byte array
|
|
||||
// and adds it to the dst.
|
|
||||
func (Encoder) AppendObjectData(dst []byte, o []byte) []byte { |
|
||||
// Two conditions we want to put a ',' between existing content and
|
|
||||
// new content:
|
|
||||
// 1. new content starts with '{' - which shd be dropped OR
|
|
||||
// 2. existing content has already other fields
|
|
||||
if o[0] == '{' { |
|
||||
o[0] = ',' |
|
||||
} else if len(dst) > 1 { |
|
||||
dst = append(dst, ',') |
|
||||
} |
|
||||
return append(dst, o...) |
|
||||
} |
|
||||
|
|
||||
// AppendIPAddr adds IPv4 or IPv6 address to dst.
|
|
||||
func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte { |
|
||||
return e.AppendString(dst, ip.String()) |
|
||||
} |
|
||||
|
|
||||
// AppendIPPrefix adds IPv4 or IPv6 Prefix (address & mask) to dst.
|
|
||||
func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { |
|
||||
return e.AppendString(dst, pfx.String()) |
|
||||
|
|
||||
} |
|
||||
|
|
||||
// AppendMACAddr adds MAC address to dst.
|
|
||||
func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { |
|
||||
return e.AppendString(dst, ha.String()) |
|
||||
} |
|
@ -1,400 +0,0 @@ |
|||||
// Package zerolog provides a lightweight logging library dedicated to JSON logging.
|
|
||||
//
|
|
||||
// A global Logger can be use for simple logging:
|
|
||||
//
|
|
||||
// import "github.com/rs/zerolog/log"
|
|
||||
//
|
|
||||
// log.Info().Msg("hello world")
|
|
||||
// // Output: {"time":1494567715,"level":"info","message":"hello world"}
|
|
||||
//
|
|
||||
// NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log".
|
|
||||
//
|
|
||||
// Fields can be added to log messages:
|
|
||||
//
|
|
||||
// log.Info().Str("foo", "bar").Msg("hello world")
|
|
||||
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
|
||||
//
|
|
||||
// Create logger instance to manage different outputs:
|
|
||||
//
|
|
||||
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
|
||||
// logger.Info().
|
|
||||
// Str("foo", "bar").
|
|
||||
// Msg("hello world")
|
|
||||
// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"}
|
|
||||
//
|
|
||||
// Sub-loggers let you chain loggers with additional context:
|
|
||||
//
|
|
||||
// sublogger := log.With().Str("component": "foo").Logger()
|
|
||||
// sublogger.Info().Msg("hello world")
|
|
||||
// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"}
|
|
||||
//
|
|
||||
// Level logging
|
|
||||
//
|
|
||||
// zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
||||
//
|
|
||||
// log.Debug().Msg("filtered out message")
|
|
||||
// log.Info().Msg("routed message")
|
|
||||
//
|
|
||||
// if e := log.Debug(); e.Enabled() {
|
|
||||
// // Compute log output only if enabled.
|
|
||||
// value := compute()
|
|
||||
// e.Str("foo": value).Msg("some debug message")
|
|
||||
// }
|
|
||||
// // Output: {"level":"info","time":1494567715,"routed message"}
|
|
||||
//
|
|
||||
// Customize automatic field names:
|
|
||||
//
|
|
||||
// log.TimestampFieldName = "t"
|
|
||||
// log.LevelFieldName = "p"
|
|
||||
// log.MessageFieldName = "m"
|
|
||||
//
|
|
||||
// log.Info().Msg("hello world")
|
|
||||
// // Output: {"t":1494567715,"p":"info","m":"hello world"}
|
|
||||
//
|
|
||||
// Log with no level and message:
|
|
||||
//
|
|
||||
// log.Log().Str("foo","bar").Msg("")
|
|
||||
// // Output: {"time":1494567715,"foo":"bar"}
|
|
||||
//
|
|
||||
// Add contextual fields to global Logger:
|
|
||||
//
|
|
||||
// log.Logger = log.With().Str("foo", "bar").Logger()
|
|
||||
//
|
|
||||
// Sample logs:
|
|
||||
//
|
|
||||
// sampled := log.Sample(&zerolog.BasicSampler{N: 10})
|
|
||||
// sampled.Info().Msg("will be logged every 10 messages")
|
|
||||
//
|
|
||||
// Log with contextual hooks:
|
|
||||
//
|
|
||||
// // Create the hook:
|
|
||||
// type SeverityHook struct{}
|
|
||||
//
|
|
||||
// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) {
|
|
||||
// if level != zerolog.NoLevel {
|
|
||||
// e.Str("severity", level.String())
|
|
||||
// }
|
|
||||
// }
|
|
||||
//
|
|
||||
// // And use it:
|
|
||||
// var h SeverityHook
|
|
||||
// log := zerolog.New(os.Stdout).Hook(h)
|
|
||||
// log.Warn().Msg("")
|
|
||||
// // Output: {"level":"warn","severity":"warn"}
|
|
||||
//
|
|
||||
//
|
|
||||
// Caveats
|
|
||||
//
|
|
||||
// There is no fields deduplication out-of-the-box.
|
|
||||
// Using the same key multiple times creates new key in final JSON each time.
|
|
||||
//
|
|
||||
// logger := zerolog.New(os.Stderr).With().Timestamp().Logger()
|
|
||||
// logger.Info().
|
|
||||
// Timestamp().
|
|
||||
// Msg("dup")
|
|
||||
// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"}
|
|
||||
//
|
|
||||
// However, it’s not a big deal though as JSON accepts dup keys,
|
|
||||
// the last one prevails.
|
|
||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"io" |
|
||||
"io/ioutil" |
|
||||
"os" |
|
||||
"strconv" |
|
||||
) |
|
||||
|
|
||||
// Level defines log levels.
|
|
||||
type Level uint8 |
|
||||
|
|
||||
const ( |
|
||||
// DebugLevel defines debug log level.
|
|
||||
DebugLevel Level = iota |
|
||||
// InfoLevel defines info log level.
|
|
||||
InfoLevel |
|
||||
// WarnLevel defines warn log level.
|
|
||||
WarnLevel |
|
||||
// ErrorLevel defines error log level.
|
|
||||
ErrorLevel |
|
||||
// FatalLevel defines fatal log level.
|
|
||||
FatalLevel |
|
||||
// PanicLevel defines panic log level.
|
|
||||
PanicLevel |
|
||||
// NoLevel defines an absent log level.
|
|
||||
NoLevel |
|
||||
// Disabled disables the logger.
|
|
||||
Disabled |
|
||||
) |
|
||||
|
|
||||
func (l Level) String() string { |
|
||||
switch l { |
|
||||
case DebugLevel: |
|
||||
return "debug" |
|
||||
case InfoLevel: |
|
||||
return "info" |
|
||||
case WarnLevel: |
|
||||
return "warn" |
|
||||
case ErrorLevel: |
|
||||
return "error" |
|
||||
case FatalLevel: |
|
||||
return "fatal" |
|
||||
case PanicLevel: |
|
||||
return "panic" |
|
||||
case NoLevel: |
|
||||
return "" |
|
||||
} |
|
||||
return "" |
|
||||
} |
|
||||
|
|
||||
// ParseLevel converts a level string into a zerolog Level value.
|
|
||||
// returns an error if the input string does not match known values.
|
|
||||
func ParseLevel(levelStr string) (Level, error) { |
|
||||
switch levelStr { |
|
||||
case DebugLevel.String(): |
|
||||
return DebugLevel, nil |
|
||||
case InfoLevel.String(): |
|
||||
return InfoLevel, nil |
|
||||
case WarnLevel.String(): |
|
||||
return WarnLevel, nil |
|
||||
case ErrorLevel.String(): |
|
||||
return ErrorLevel, nil |
|
||||
case FatalLevel.String(): |
|
||||
return FatalLevel, nil |
|
||||
case PanicLevel.String(): |
|
||||
return PanicLevel, nil |
|
||||
case NoLevel.String(): |
|
||||
return NoLevel, nil |
|
||||
} |
|
||||
return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) |
|
||||
} |
|
||||
|
|
||||
// A Logger represents an active logging object that generates lines
|
|
||||
// of JSON output to an io.Writer. Each logging operation makes a single
|
|
||||
// call to the Writer's Write method. There is no guaranty on access
|
|
||||
// serialization to the Writer. If your Writer is not thread safe,
|
|
||||
// you may consider a sync wrapper.
|
|
||||
type Logger struct { |
|
||||
w LevelWriter |
|
||||
level Level |
|
||||
sampler Sampler |
|
||||
context []byte |
|
||||
hooks []Hook |
|
||||
} |
|
||||
|
|
||||
// New creates a root logger with given output writer. If the output writer implements
|
|
||||
// the LevelWriter interface, the WriteLevel method will be called instead of the Write
|
|
||||
// one.
|
|
||||
//
|
|
||||
// Each logging operation makes a single call to the Writer's Write method. There is no
|
|
||||
// guaranty on access serialization to the Writer. If your Writer is not thread safe,
|
|
||||
// you may consider using sync wrapper.
|
|
||||
func New(w io.Writer) Logger { |
|
||||
if w == nil { |
|
||||
w = ioutil.Discard |
|
||||
} |
|
||||
lw, ok := w.(LevelWriter) |
|
||||
if !ok { |
|
||||
lw = levelWriterAdapter{w} |
|
||||
} |
|
||||
return Logger{w: lw} |
|
||||
} |
|
||||
|
|
||||
// Nop returns a disabled logger for which all operation are no-op.
|
|
||||
func Nop() Logger { |
|
||||
return New(nil).Level(Disabled) |
|
||||
} |
|
||||
|
|
||||
// Output duplicates the current logger and sets w as its output.
|
|
||||
func (l Logger) Output(w io.Writer) Logger { |
|
||||
l2 := New(w) |
|
||||
l2.level = l.level |
|
||||
l2.sampler = l.sampler |
|
||||
if len(l.hooks) > 0 { |
|
||||
l2.hooks = append(l2.hooks, l.hooks...) |
|
||||
} |
|
||||
if l.context != nil { |
|
||||
l2.context = make([]byte, len(l.context), cap(l.context)) |
|
||||
copy(l2.context, l.context) |
|
||||
} |
|
||||
return l2 |
|
||||
} |
|
||||
|
|
||||
// With creates a child logger with the field added to its context.
|
|
||||
func (l Logger) With() Context { |
|
||||
context := l.context |
|
||||
l.context = make([]byte, 0, 500) |
|
||||
if context != nil { |
|
||||
l.context = append(l.context, context...) |
|
||||
} |
|
||||
return Context{l} |
|
||||
} |
|
||||
|
|
||||
// UpdateContext updates the internal logger's context.
|
|
||||
//
|
|
||||
// Use this method with caution. If unsure, prefer the With method.
|
|
||||
func (l *Logger) UpdateContext(update func(c Context) Context) { |
|
||||
if l == disabledLogger { |
|
||||
return |
|
||||
} |
|
||||
if cap(l.context) == 0 { |
|
||||
l.context = make([]byte, 0, 500) |
|
||||
} |
|
||||
c := update(Context{*l}) |
|
||||
l.context = c.l.context |
|
||||
} |
|
||||
|
|
||||
// Level creates a child logger with the minimum accepted level set to level.
|
|
||||
func (l Logger) Level(lvl Level) Logger { |
|
||||
l.level = lvl |
|
||||
return l |
|
||||
} |
|
||||
|
|
||||
// Sample returns a logger with the s sampler.
|
|
||||
func (l Logger) Sample(s Sampler) Logger { |
|
||||
l.sampler = s |
|
||||
return l |
|
||||
} |
|
||||
|
|
||||
// Hook returns a logger with the h Hook.
|
|
||||
func (l Logger) Hook(h Hook) Logger { |
|
||||
l.hooks = append(l.hooks, h) |
|
||||
return l |
|
||||
} |
|
||||
|
|
||||
// Debug starts a new message with debug level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) Debug() *Event { |
|
||||
return l.newEvent(DebugLevel, nil) |
|
||||
} |
|
||||
|
|
||||
// Info starts a new message with info level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) Info() *Event { |
|
||||
return l.newEvent(InfoLevel, nil) |
|
||||
} |
|
||||
|
|
||||
// Warn starts a new message with warn level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) Warn() *Event { |
|
||||
return l.newEvent(WarnLevel, nil) |
|
||||
} |
|
||||
|
|
||||
// Error starts a new message with error level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) Error() *Event { |
|
||||
return l.newEvent(ErrorLevel, nil) |
|
||||
} |
|
||||
|
|
||||
// Fatal starts a new message with fatal level. The os.Exit(1) function
|
|
||||
// is called by the Msg method, which terminates the program immediately.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) Fatal() *Event { |
|
||||
return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) }) |
|
||||
} |
|
||||
|
|
||||
// Panic starts a new message with panic level. The panic() function
|
|
||||
// is called by the Msg method, which stops the ordinary flow of a goroutine.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) Panic() *Event { |
|
||||
return l.newEvent(PanicLevel, func(msg string) { panic(msg) }) |
|
||||
} |
|
||||
|
|
||||
// WithLevel starts a new message with level. Unlike Fatal and Panic
|
|
||||
// methods, WithLevel does not terminate the program or stop the ordinary
|
|
||||
// flow of a gourotine when used with their respective levels.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) WithLevel(level Level) *Event { |
|
||||
switch level { |
|
||||
case DebugLevel: |
|
||||
return l.Debug() |
|
||||
case InfoLevel: |
|
||||
return l.Info() |
|
||||
case WarnLevel: |
|
||||
return l.Warn() |
|
||||
case ErrorLevel: |
|
||||
return l.Error() |
|
||||
case FatalLevel: |
|
||||
return l.newEvent(FatalLevel, nil) |
|
||||
case PanicLevel: |
|
||||
return l.newEvent(PanicLevel, nil) |
|
||||
case NoLevel: |
|
||||
return l.Log() |
|
||||
case Disabled: |
|
||||
return nil |
|
||||
default: |
|
||||
panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level))) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Log starts a new message with no level. Setting GlobalLevel to Disabled
|
|
||||
// will still disable events produced by this method.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func (l *Logger) Log() *Event { |
|
||||
return l.newEvent(NoLevel, nil) |
|
||||
} |
|
||||
|
|
||||
// Print sends a log event using debug level and no extra field.
|
|
||||
// Arguments are handled in the manner of fmt.Print.
|
|
||||
func (l *Logger) Print(v ...interface{}) { |
|
||||
if e := l.Debug(); e.Enabled() { |
|
||||
e.Msg(fmt.Sprint(v...)) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Printf sends a log event using debug level and no extra field.
|
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
|
||||
func (l *Logger) Printf(format string, v ...interface{}) { |
|
||||
if e := l.Debug(); e.Enabled() { |
|
||||
e.Msg(fmt.Sprintf(format, v...)) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Write implements the io.Writer interface. This is useful to set as a writer
|
|
||||
// for the standard library log.
|
|
||||
func (l Logger) Write(p []byte) (n int, err error) { |
|
||||
n = len(p) |
|
||||
if n > 0 && p[n-1] == '\n' { |
|
||||
// Trim CR added by stdlog.
|
|
||||
p = p[0 : n-1] |
|
||||
} |
|
||||
l.Log().Msg(string(p)) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
func (l *Logger) newEvent(level Level, done func(string)) *Event { |
|
||||
enabled := l.should(level) |
|
||||
if !enabled { |
|
||||
return nil |
|
||||
} |
|
||||
e := newEvent(l.w, level) |
|
||||
e.done = done |
|
||||
e.ch = l.hooks |
|
||||
if level != NoLevel { |
|
||||
e.Str(LevelFieldName, level.String()) |
|
||||
} |
|
||||
if l.context != nil && len(l.context) > 0 { |
|
||||
e.buf = enc.AppendObjectData(e.buf, l.context) |
|
||||
} |
|
||||
return e |
|
||||
} |
|
||||
|
|
||||
// should returns true if the log event should be logged.
|
|
||||
func (l *Logger) should(lvl Level) bool { |
|
||||
if lvl < l.level || lvl < GlobalLevel() { |
|
||||
return false |
|
||||
} |
|
||||
if l.sampler != nil && !samplingDisabled() { |
|
||||
return l.sampler.Sample(lvl) |
|
||||
} |
|
||||
return true |
|
||||
} |
|
@ -1,115 +0,0 @@ |
|||||
// Package log provides a global logger for zerolog.
|
|
||||
package log |
|
||||
|
|
||||
import ( |
|
||||
"context" |
|
||||
"io" |
|
||||
"os" |
|
||||
|
|
||||
"github.com/rs/zerolog" |
|
||||
) |
|
||||
|
|
||||
// Logger is the global logger.
|
|
||||
var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger() |
|
||||
|
|
||||
// Output duplicates the global logger and sets w as its output.
|
|
||||
func Output(w io.Writer) zerolog.Logger { |
|
||||
return Logger.Output(w) |
|
||||
} |
|
||||
|
|
||||
// With creates a child logger with the field added to its context.
|
|
||||
func With() zerolog.Context { |
|
||||
return Logger.With() |
|
||||
} |
|
||||
|
|
||||
// Level creates a child logger with the minimum accepted level set to level.
|
|
||||
func Level(level zerolog.Level) zerolog.Logger { |
|
||||
return Logger.Level(level) |
|
||||
} |
|
||||
|
|
||||
// Sample returns a logger with the s sampler.
|
|
||||
func Sample(s zerolog.Sampler) zerolog.Logger { |
|
||||
return Logger.Sample(s) |
|
||||
} |
|
||||
|
|
||||
// Hook returns a logger with the h Hook.
|
|
||||
func Hook(h zerolog.Hook) zerolog.Logger { |
|
||||
return Logger.Hook(h) |
|
||||
} |
|
||||
|
|
||||
// Debug starts a new message with debug level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func Debug() *zerolog.Event { |
|
||||
return Logger.Debug() |
|
||||
} |
|
||||
|
|
||||
// Info starts a new message with info level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func Info() *zerolog.Event { |
|
||||
return Logger.Info() |
|
||||
} |
|
||||
|
|
||||
// Warn starts a new message with warn level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func Warn() *zerolog.Event { |
|
||||
return Logger.Warn() |
|
||||
} |
|
||||
|
|
||||
// Error starts a new message with error level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func Error() *zerolog.Event { |
|
||||
return Logger.Error() |
|
||||
} |
|
||||
|
|
||||
// Fatal starts a new message with fatal level. The os.Exit(1) function
|
|
||||
// is called by the Msg method.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func Fatal() *zerolog.Event { |
|
||||
return Logger.Fatal() |
|
||||
} |
|
||||
|
|
||||
// Panic starts a new message with panic level. The message is also sent
|
|
||||
// to the panic function.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func Panic() *zerolog.Event { |
|
||||
return Logger.Panic() |
|
||||
} |
|
||||
|
|
||||
// WithLevel starts a new message with level.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func WithLevel(level zerolog.Level) *zerolog.Event { |
|
||||
return Logger.WithLevel(level) |
|
||||
} |
|
||||
|
|
||||
// Log starts a new message with no level. Setting zerolog.GlobalLevel to
|
|
||||
// zerolog.Disabled will still disable events produced by this method.
|
|
||||
//
|
|
||||
// You must call Msg on the returned event in order to send the event.
|
|
||||
func Log() *zerolog.Event { |
|
||||
return Logger.Log() |
|
||||
} |
|
||||
|
|
||||
// Print sends a log event using debug level and no extra field.
|
|
||||
// Arguments are handled in the manner of fmt.Print.
|
|
||||
func Print(v ...interface{}) { |
|
||||
Logger.Print(v...) |
|
||||
} |
|
||||
|
|
||||
// Printf sends a log event using debug level and no extra field.
|
|
||||
// Arguments are handled in the manner of fmt.Printf.
|
|
||||
func Printf(format string, v ...interface{}) { |
|
||||
Logger.Printf(format, v...) |
|
||||
} |
|
||||
|
|
||||
// Ctx returns the Logger associated with the ctx. If no logger
|
|
||||
// is associated, a disabled logger is returned.
|
|
||||
func Ctx(ctx context.Context) *zerolog.Logger { |
|
||||
return zerolog.Ctx(ctx) |
|
||||
} |
|
Before Width: | Height: | Size: 377 KiB |
@ -1,126 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"math/rand" |
|
||||
"sync/atomic" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
// Often samples log every ~ 10 events.
|
|
||||
Often = RandomSampler(10) |
|
||||
// Sometimes samples log every ~ 100 events.
|
|
||||
Sometimes = RandomSampler(100) |
|
||||
// Rarely samples log every ~ 1000 events.
|
|
||||
Rarely = RandomSampler(1000) |
|
||||
) |
|
||||
|
|
||||
// Sampler defines an interface to a log sampler.
|
|
||||
type Sampler interface { |
|
||||
// Sample returns true if the event should be part of the sample, false if
|
|
||||
// the event should be dropped.
|
|
||||
Sample(lvl Level) bool |
|
||||
} |
|
||||
|
|
||||
// RandomSampler use a PRNG to randomly sample an event out of N events,
|
|
||||
// regardless of their level.
|
|
||||
type RandomSampler uint32 |
|
||||
|
|
||||
// Sample implements the Sampler interface.
|
|
||||
func (s RandomSampler) Sample(lvl Level) bool { |
|
||||
if s <= 0 { |
|
||||
return false |
|
||||
} |
|
||||
if rand.Intn(int(s)) != 0 { |
|
||||
return false |
|
||||
} |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
// BasicSampler is a sampler that will send every Nth events, regardless of
|
|
||||
// there level.
|
|
||||
type BasicSampler struct { |
|
||||
N uint32 |
|
||||
counter uint32 |
|
||||
} |
|
||||
|
|
||||
// Sample implements the Sampler interface.
|
|
||||
func (s *BasicSampler) Sample(lvl Level) bool { |
|
||||
c := atomic.AddUint32(&s.counter, 1) |
|
||||
return c%s.N == s.N-1 |
|
||||
} |
|
||||
|
|
||||
// BurstSampler lets Burst events pass per Period then pass the decision to
|
|
||||
// NextSampler. If Sampler is not set, all subsequent events are rejected.
|
|
||||
type BurstSampler struct { |
|
||||
// Burst is the maximum number of event per period allowed before calling
|
|
||||
// NextSampler.
|
|
||||
Burst uint32 |
|
||||
// Period defines the burst period. If 0, NextSampler is always called.
|
|
||||
Period time.Duration |
|
||||
// NextSampler is the sampler used after the burst is reached. If nil,
|
|
||||
// events are always rejected after the burst.
|
|
||||
NextSampler Sampler |
|
||||
|
|
||||
counter uint32 |
|
||||
resetAt int64 |
|
||||
} |
|
||||
|
|
||||
// Sample implements the Sampler interface.
|
|
||||
func (s *BurstSampler) Sample(lvl Level) bool { |
|
||||
if s.Burst > 0 && s.Period > 0 { |
|
||||
if s.inc() <= s.Burst { |
|
||||
return true |
|
||||
} |
|
||||
} |
|
||||
if s.NextSampler == nil { |
|
||||
return false |
|
||||
} |
|
||||
return s.NextSampler.Sample(lvl) |
|
||||
} |
|
||||
|
|
||||
func (s *BurstSampler) inc() uint32 { |
|
||||
now := time.Now().UnixNano() |
|
||||
resetAt := atomic.LoadInt64(&s.resetAt) |
|
||||
var c uint32 |
|
||||
if now > resetAt { |
|
||||
c = 1 |
|
||||
atomic.StoreUint32(&s.counter, c) |
|
||||
newResetAt := now + s.Period.Nanoseconds() |
|
||||
reset := atomic.CompareAndSwapInt64(&s.resetAt, resetAt, newResetAt) |
|
||||
if !reset { |
|
||||
// Lost the race with another goroutine trying to reset.
|
|
||||
c = atomic.AddUint32(&s.counter, 1) |
|
||||
} |
|
||||
} else { |
|
||||
c = atomic.AddUint32(&s.counter, 1) |
|
||||
} |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// LevelSampler applies a different sampler for each level.
|
|
||||
type LevelSampler struct { |
|
||||
DebugSampler, InfoSampler, WarnSampler, ErrorSampler Sampler |
|
||||
} |
|
||||
|
|
||||
func (s LevelSampler) Sample(lvl Level) bool { |
|
||||
switch lvl { |
|
||||
case DebugLevel: |
|
||||
if s.DebugSampler != nil { |
|
||||
return s.DebugSampler.Sample(lvl) |
|
||||
} |
|
||||
case InfoLevel: |
|
||||
if s.InfoSampler != nil { |
|
||||
return s.InfoSampler.Sample(lvl) |
|
||||
} |
|
||||
case WarnLevel: |
|
||||
if s.WarnSampler != nil { |
|
||||
return s.WarnSampler.Sample(lvl) |
|
||||
} |
|
||||
case ErrorLevel: |
|
||||
if s.ErrorSampler != nil { |
|
||||
return s.ErrorSampler.Sample(lvl) |
|
||||
} |
|
||||
} |
|
||||
return true |
|
||||
} |
|
@ -1,57 +0,0 @@ |
|||||
// +build !windows
|
|
||||
// +build !binary_log
|
|
||||
|
|
||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"io" |
|
||||
) |
|
||||
|
|
||||
// SyslogWriter is an interface matching a syslog.Writer struct.
|
|
||||
type SyslogWriter interface { |
|
||||
io.Writer |
|
||||
Debug(m string) error |
|
||||
Info(m string) error |
|
||||
Warning(m string) error |
|
||||
Err(m string) error |
|
||||
Emerg(m string) error |
|
||||
Crit(m string) error |
|
||||
} |
|
||||
|
|
||||
type syslogWriter struct { |
|
||||
w SyslogWriter |
|
||||
} |
|
||||
|
|
||||
// SyslogLevelWriter wraps a SyslogWriter and call the right syslog level
|
|
||||
// method matching the zerolog level.
|
|
||||
func SyslogLevelWriter(w SyslogWriter) LevelWriter { |
|
||||
return syslogWriter{w} |
|
||||
} |
|
||||
|
|
||||
func (sw syslogWriter) Write(p []byte) (n int, err error) { |
|
||||
return sw.w.Write(p) |
|
||||
} |
|
||||
|
|
||||
// WriteLevel implements LevelWriter interface.
|
|
||||
func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) { |
|
||||
switch level { |
|
||||
case DebugLevel: |
|
||||
err = sw.w.Debug(string(p)) |
|
||||
case InfoLevel: |
|
||||
err = sw.w.Info(string(p)) |
|
||||
case WarnLevel: |
|
||||
err = sw.w.Warning(string(p)) |
|
||||
case ErrorLevel: |
|
||||
err = sw.w.Err(string(p)) |
|
||||
case FatalLevel: |
|
||||
err = sw.w.Emerg(string(p)) |
|
||||
case PanicLevel: |
|
||||
err = sw.w.Crit(string(p)) |
|
||||
case NoLevel: |
|
||||
err = sw.w.Info(string(p)) |
|
||||
default: |
|
||||
panic("invalid level") |
|
||||
} |
|
||||
n = len(p) |
|
||||
return |
|
||||
} |
|
@ -1,100 +0,0 @@ |
|||||
package zerolog |
|
||||
|
|
||||
import ( |
|
||||
"io" |
|
||||
"sync" |
|
||||
) |
|
||||
|
|
||||
// LevelWriter defines as interface a writer may implement in order
|
|
||||
// to receive level information with payload.
|
|
||||
type LevelWriter interface { |
|
||||
io.Writer |
|
||||
WriteLevel(level Level, p []byte) (n int, err error) |
|
||||
} |
|
||||
|
|
||||
type levelWriterAdapter struct { |
|
||||
io.Writer |
|
||||
} |
|
||||
|
|
||||
func (lw levelWriterAdapter) WriteLevel(l Level, p []byte) (n int, err error) { |
|
||||
return lw.Write(p) |
|
||||
} |
|
||||
|
|
||||
type syncWriter struct { |
|
||||
mu sync.Mutex |
|
||||
lw LevelWriter |
|
||||
} |
|
||||
|
|
||||
// SyncWriter wraps w so that each call to Write is synchronized with a mutex.
|
|
||||
// This syncer can be the call to writer's Write method is not thread safe.
|
|
||||
// Note that os.File Write operation is using write() syscall which is supposed
|
|
||||
// to be thread-safe on POSIX systems. So there is no need to use this with
|
|
||||
// os.File on such systems as zerolog guaranties to issue a single Write call
|
|
||||
// per log event.
|
|
||||
func SyncWriter(w io.Writer) io.Writer { |
|
||||
if lw, ok := w.(LevelWriter); ok { |
|
||||
return &syncWriter{lw: lw} |
|
||||
} |
|
||||
return &syncWriter{lw: levelWriterAdapter{w}} |
|
||||
} |
|
||||
|
|
||||
// Write implements the io.Writer interface.
|
|
||||
func (s *syncWriter) Write(p []byte) (n int, err error) { |
|
||||
s.mu.Lock() |
|
||||
defer s.mu.Unlock() |
|
||||
return s.lw.Write(p) |
|
||||
} |
|
||||
|
|
||||
// WriteLevel implements the LevelWriter interface.
|
|
||||
func (s *syncWriter) WriteLevel(l Level, p []byte) (n int, err error) { |
|
||||
s.mu.Lock() |
|
||||
defer s.mu.Unlock() |
|
||||
return s.lw.WriteLevel(l, p) |
|
||||
} |
|
||||
|
|
||||
type multiLevelWriter struct { |
|
||||
writers []LevelWriter |
|
||||
} |
|
||||
|
|
||||
func (t multiLevelWriter) Write(p []byte) (n int, err error) { |
|
||||
for _, w := range t.writers { |
|
||||
n, err = w.Write(p) |
|
||||
if err != nil { |
|
||||
return |
|
||||
} |
|
||||
if n != len(p) { |
|
||||
err = io.ErrShortWrite |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
return len(p), nil |
|
||||
} |
|
||||
|
|
||||
func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) { |
|
||||
for _, w := range t.writers { |
|
||||
n, err = w.WriteLevel(l, p) |
|
||||
if err != nil { |
|
||||
return |
|
||||
} |
|
||||
if n != len(p) { |
|
||||
err = io.ErrShortWrite |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
return len(p), nil |
|
||||
} |
|
||||
|
|
||||
// MultiLevelWriter creates a writer that duplicates its writes to all the
|
|
||||
// provided writers, similar to the Unix tee(1) command. If some writers
|
|
||||
// implement LevelWriter, their WriteLevel method will be used instead of Write.
|
|
||||
func MultiLevelWriter(writers ...io.Writer) LevelWriter { |
|
||||
lwriters := make([]LevelWriter, 0, len(writers)) |
|
||||
for _, w := range writers { |
|
||||
if lw, ok := w.(LevelWriter); ok { |
|
||||
lwriters = append(lwriters, lw) |
|
||||
} else { |
|
||||
lwriters = append(lwriters, levelWriterAdapter{w}) |
|
||||
} |
|
||||
} |
|
||||
return multiLevelWriter{lwriters} |
|
||||
} |
|
@ -1,435 +0,0 @@ |
|||||
# Change Log |
|
||||
|
|
||||
**ATTN**: This project uses [semantic versioning](http://semver.org/). |
|
||||
|
|
||||
## [Unreleased] |
|
||||
|
|
||||
## 1.20.0 - 2017-08-10 |
|
||||
|
|
||||
### Fixed |
|
||||
|
|
||||
* `HandleExitCoder` is now correctly iterates over all errors in |
|
||||
a `MultiError`. The exit code is the exit code of the last error or `1` if |
|
||||
there are no `ExitCoder`s in the `MultiError`. |
|
||||
* Fixed YAML file loading on Windows (previously would fail validate the file path) |
|
||||
* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly |
|
||||
propogated |
|
||||
* `ErrWriter` is now passed downwards through command structure to avoid the |
|
||||
need to redefine it |
|
||||
* Pass `Command` context into `OnUsageError` rather than parent context so that |
|
||||
all fields are avaiable |
|
||||
* Errors occuring in `Before` funcs are no longer double printed |
|
||||
* Use `UsageText` in the help templates for commands and subcommands if |
|
||||
defined; otherwise build the usage as before (was previously ignoring this |
|
||||
field) |
|
||||
* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if |
|
||||
a program calls `Set` or `GlobalSet` directly after flag parsing (would |
|
||||
previously only return `true` if the flag was set during parsing) |
|
||||
|
|
||||
### Changed |
|
||||
|
|
||||
* No longer exit the program on command/subcommand error if the error raised is |
|
||||
not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was |
|
||||
determined to be a regression in functionality. See [the |
|
||||
PR](https://github.com/urfave/cli/pull/595) for discussion. |
|
||||
|
|
||||
### Added |
|
||||
|
|
||||
* `CommandsByName` type was added to make it easy to sort `Command`s by name, |
|
||||
alphabetically |
|
||||
* `altsrc` now handles loading of string and int arrays from TOML |
|
||||
* Support for definition of custom help templates for `App` via |
|
||||
`CustomAppHelpTemplate` |
|
||||
* Support for arbitrary key/value fields on `App` to be used with |
|
||||
`CustomAppHelpTemplate` via `ExtraInfo` |
|
||||
* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be |
|
||||
`cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag` |
|
||||
interface to be used. |
|
||||
|
|
||||
|
|
||||
## [1.19.1] - 2016-11-21 |
|
||||
|
|
||||
### Fixed |
|
||||
|
|
||||
- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as |
|
||||
the `Action` for a command would cause it to error rather than calling the |
|
||||
function. Should not have a affected declarative cases using `func(c |
|
||||
*cli.Context) err)`. |
|
||||
- Shell completion now handles the case where the user specifies |
|
||||
`--generate-bash-completion` immediately after a flag that takes an argument. |
|
||||
Previously it call the application with `--generate-bash-completion` as the |
|
||||
flag value. |
|
||||
|
|
||||
## [1.19.0] - 2016-11-19 |
|
||||
### Added |
|
||||
- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`) |
|
||||
- A `Description` field was added to `App` for a more detailed description of |
|
||||
the application (similar to the existing `Description` field on `Command`) |
|
||||
- Flag type code generation via `go generate` |
|
||||
- Write to stderr and exit 1 if action returns non-nil error |
|
||||
- Added support for TOML to the `altsrc` loader |
|
||||
- `SkipArgReorder` was added to allow users to skip the argument reordering. |
|
||||
This is useful if you want to consider all "flags" after an argument as |
|
||||
arguments rather than flags (the default behavior of the stdlib `flag` |
|
||||
library). This is backported functionality from the [removal of the flag |
|
||||
reordering](https://github.com/urfave/cli/pull/398) in the unreleased version |
|
||||
2 |
|
||||
- For formatted errors (those implementing `ErrorFormatter`), the errors will |
|
||||
be formatted during output. Compatible with `pkg/errors`. |
|
||||
|
|
||||
### Changed |
|
||||
- Raise minimum tested/supported Go version to 1.2+ |
|
||||
|
|
||||
### Fixed |
|
||||
- Consider empty environment variables as set (previously environment variables |
|
||||
with the equivalent of `""` would be skipped rather than their value used). |
|
||||
- Return an error if the value in a given environment variable cannot be parsed |
|
||||
as the flag type. Previously these errors were silently swallowed. |
|
||||
- Print full error when an invalid flag is specified (which includes the invalid flag) |
|
||||
- `App.Writer` defaults to `stdout` when `nil` |
|
||||
- If no action is specified on a command or app, the help is now printed instead of `panic`ing |
|
||||
- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized) |
|
||||
- Correctly show help message if `-h` is provided to a subcommand |
|
||||
- `context.(Global)IsSet` now respects environment variables. Previously it |
|
||||
would return `false` if a flag was specified in the environment rather than |
|
||||
as an argument |
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user |
|
||||
- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This |
|
||||
fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well |
|
||||
as `altsrc` where Go would complain that the types didn't match |
|
||||
|
|
||||
## [1.18.1] - 2016-08-28 |
|
||||
### Fixed |
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported) |
|
||||
|
|
||||
## [1.18.0] - 2016-06-27 |
|
||||
### Added |
|
||||
- `./runtests` test runner with coverage tracking by default |
|
||||
- testing on OS X |
|
||||
- testing on Windows |
|
||||
- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code |
|
||||
|
|
||||
### Changed |
|
||||
- Use spaces for alignment in help/usage output instead of tabs, making the |
|
||||
output alignment consistent regardless of tab width |
|
||||
|
|
||||
### Fixed |
|
||||
- Printing of command aliases in help text |
|
||||
- Printing of visible flags for both struct and struct pointer flags |
|
||||
- Display the `help` subcommand when using `CommandCategories` |
|
||||
- No longer swallows `panic`s that occur within the `Action`s themselves when |
|
||||
detecting the signature of the `Action` field |
|
||||
|
|
||||
## [1.17.1] - 2016-08-28 |
|
||||
### Fixed |
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user |
|
||||
|
|
||||
## [1.17.0] - 2016-05-09 |
|
||||
### Added |
|
||||
- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` |
|
||||
- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` |
|
||||
- Support for hiding commands by setting `Hidden: true` -- this will hide the |
|
||||
commands in help output |
|
||||
|
|
||||
### Changed |
|
||||
- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer |
|
||||
quoted in help text output. |
|
||||
- All flag types now include `(default: {value})` strings following usage when a |
|
||||
default value can be (reasonably) detected. |
|
||||
- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent |
|
||||
with non-slice flag types |
|
||||
- Apps now exit with a code of 3 if an unknown subcommand is specified |
|
||||
(previously they printed "No help topic for...", but still exited 0. This |
|
||||
makes it easier to script around apps built using `cli` since they can trust |
|
||||
that a 0 exit code indicated a successful execution. |
|
||||
- cleanups based on [Go Report Card |
|
||||
feedback](https://goreportcard.com/report/github.com/urfave/cli) |
|
||||
|
|
||||
## [1.16.1] - 2016-08-28 |
|
||||
### Fixed |
|
||||
- Removed deprecation warnings to STDERR to avoid them leaking to the end-user |
|
||||
|
|
||||
## [1.16.0] - 2016-05-02 |
|
||||
### Added |
|
||||
- `Hidden` field on all flag struct types to omit from generated help text |
|
||||
|
|
||||
### Changed |
|
||||
- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from |
|
||||
generated help text via the `Hidden` field |
|
||||
|
|
||||
### Fixed |
|
||||
- handling of error values in `HandleAction` and `HandleExitCoder` |
|
||||
|
|
||||
## [1.15.0] - 2016-04-30 |
|
||||
### Added |
|
||||
- This file! |
|
||||
- Support for placeholders in flag usage strings |
|
||||
- `App.Metadata` map for arbitrary data/state management |
|
||||
- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after |
|
||||
parsing. |
|
||||
- Support for nested lookup of dot-delimited keys in structures loaded from |
|
||||
YAML. |
|
||||
|
|
||||
### Changed |
|
||||
- The `App.Action` and `Command.Action` now prefer a return signature of |
|
||||
`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil |
|
||||
`error` is returned, there may be two outcomes: |
|
||||
- If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called |
|
||||
automatically |
|
||||
- Else the error is bubbled up and returned from `App.Run` |
|
||||
- Specifying an `Action` with the legacy return signature of |
|
||||
`func(*cli.Context)` will produce a deprecation message to stderr |
|
||||
- Specifying an `Action` that is not a `func` type will produce a non-zero exit |
|
||||
from `App.Run` |
|
||||
- Specifying an `Action` func that has an invalid (input) signature will |
|
||||
produce a non-zero exit from `App.Run` |
|
||||
|
|
||||
### Deprecated |
|
||||
- <a name="deprecated-cli-app-runandexitonerror"></a> |
|
||||
`cli.App.RunAndExitOnError`, which should now be done by returning an error |
|
||||
that fulfills `cli.ExitCoder` to `cli.App.Run`. |
|
||||
- <a name="deprecated-cli-app-action-signature"></a> the legacy signature for |
|
||||
`cli.App.Action` of `func(*cli.Context)`, which should now have a return |
|
||||
signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. |
|
||||
|
|
||||
### Fixed |
|
||||
- Added missing `*cli.Context.GlobalFloat64` method |
|
||||
|
|
||||
## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Codebeat badge |
|
||||
- Support for categorization via `CategorizedHelp` and `Categories` on app. |
|
||||
|
|
||||
### Changed |
|
||||
- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`. |
|
||||
|
|
||||
### Fixed |
|
||||
- Ensure version is not shown in help text when `HideVersion` set. |
|
||||
|
|
||||
## [1.13.0] - 2016-03-06 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- YAML file input support. |
|
||||
- `NArg` method on context. |
|
||||
|
|
||||
## [1.12.0] - 2016-02-17 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Custom usage error handling. |
|
||||
- Custom text support in `USAGE` section of help output. |
|
||||
- Improved help messages for empty strings. |
|
||||
- AppVeyor CI configuration. |
|
||||
|
|
||||
### Changed |
|
||||
- Removed `panic` from default help printer func. |
|
||||
- De-duping and optimizations. |
|
||||
|
|
||||
### Fixed |
|
||||
- Correctly handle `Before`/`After` at command level when no subcommands. |
|
||||
- Case of literal `-` argument causing flag reordering. |
|
||||
- Environment variable hints on Windows. |
|
||||
- Docs updates. |
|
||||
|
|
||||
## [1.11.1] - 2015-12-21 (backfilled 2016-04-25) |
|
||||
### Changed |
|
||||
- Use `path.Base` in `Name` and `HelpName` |
|
||||
- Export `GetName` on flag types. |
|
||||
|
|
||||
### Fixed |
|
||||
- Flag parsing when skipping is enabled. |
|
||||
- Test output cleanup. |
|
||||
- Move completion check to account for empty input case. |
|
||||
|
|
||||
## [1.11.0] - 2015-11-15 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Destination scan support for flags. |
|
||||
- Testing against `tip` in Travis CI config. |
|
||||
|
|
||||
### Changed |
|
||||
- Go version in Travis CI config. |
|
||||
|
|
||||
### Fixed |
|
||||
- Removed redundant tests. |
|
||||
- Use correct example naming in tests. |
|
||||
|
|
||||
## [1.10.2] - 2015-10-29 (backfilled 2016-04-25) |
|
||||
### Fixed |
|
||||
- Remove unused var in bash completion. |
|
||||
|
|
||||
## [1.10.1] - 2015-10-21 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Coverage and reference logos in README. |
|
||||
|
|
||||
### Fixed |
|
||||
- Use specified values in help and version parsing. |
|
||||
- Only display app version and help message once. |
|
||||
|
|
||||
## [1.10.0] - 2015-10-06 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- More tests for existing functionality. |
|
||||
- `ArgsUsage` at app and command level for help text flexibility. |
|
||||
|
|
||||
### Fixed |
|
||||
- Honor `HideHelp` and `HideVersion` in `App.Run`. |
|
||||
- Remove juvenile word from README. |
|
||||
|
|
||||
## [1.9.0] - 2015-09-08 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- `FullName` on command with accompanying help output update. |
|
||||
- Set default `$PROG` in bash completion. |
|
||||
|
|
||||
### Changed |
|
||||
- Docs formatting. |
|
||||
|
|
||||
### Fixed |
|
||||
- Removed self-referential imports in tests. |
|
||||
|
|
||||
## [1.8.0] - 2015-06-30 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Support for `Copyright` at app level. |
|
||||
- `Parent` func at context level to walk up context lineage. |
|
||||
|
|
||||
### Fixed |
|
||||
- Global flag processing at top level. |
|
||||
|
|
||||
## [1.7.1] - 2015-06-11 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Aggregate errors from `Before`/`After` funcs. |
|
||||
- Doc comments on flag structs. |
|
||||
- Include non-global flags when checking version and help. |
|
||||
- Travis CI config updates. |
|
||||
|
|
||||
### Fixed |
|
||||
- Ensure slice type flags have non-nil values. |
|
||||
- Collect global flags from the full command hierarchy. |
|
||||
- Docs prose. |
|
||||
|
|
||||
## [1.7.0] - 2015-05-03 (backfilled 2016-04-25) |
|
||||
### Changed |
|
||||
- `HelpPrinter` signature includes output writer. |
|
||||
|
|
||||
### Fixed |
|
||||
- Specify go 1.1+ in docs. |
|
||||
- Set `Writer` when running command as app. |
|
||||
|
|
||||
## [1.6.0] - 2015-03-23 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Multiple author support. |
|
||||
- `NumFlags` at context level. |
|
||||
- `Aliases` at command level. |
|
||||
|
|
||||
### Deprecated |
|
||||
- `ShortName` at command level. |
|
||||
|
|
||||
### Fixed |
|
||||
- Subcommand help output. |
|
||||
- Backward compatible support for deprecated `Author` and `Email` fields. |
|
||||
- Docs regarding `Names`/`Aliases`. |
|
||||
|
|
||||
## [1.5.0] - 2015-02-20 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- `After` hook func support at app and command level. |
|
||||
|
|
||||
### Fixed |
|
||||
- Use parsed context when running command as subcommand. |
|
||||
- Docs prose. |
|
||||
|
|
||||
## [1.4.1] - 2015-01-09 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Support for hiding `-h / --help` flags, but not `help` subcommand. |
|
||||
- Stop flag parsing after `--`. |
|
||||
|
|
||||
### Fixed |
|
||||
- Help text for generic flags to specify single value. |
|
||||
- Use double quotes in output for defaults. |
|
||||
- Use `ParseInt` instead of `ParseUint` for int environment var values. |
|
||||
- Use `0` as base when parsing int environment var values. |
|
||||
|
|
||||
## [1.4.0] - 2014-12-12 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Support for environment variable lookup "cascade". |
|
||||
- Support for `Stdout` on app for output redirection. |
|
||||
|
|
||||
### Fixed |
|
||||
- Print command help instead of app help in `ShowCommandHelp`. |
|
||||
|
|
||||
## [1.3.1] - 2014-11-13 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- Docs and example code updates. |
|
||||
|
|
||||
### Changed |
|
||||
- Default `-v / --version` flag made optional. |
|
||||
|
|
||||
## [1.3.0] - 2014-08-10 (backfilled 2016-04-25) |
|
||||
### Added |
|
||||
- `FlagNames` at context level. |
|
||||
- Exposed `VersionPrinter` var for more control over version output. |
|
||||
- Zsh completion hook. |
|
||||
- `AUTHOR` section in default app help template. |
|
||||
- Contribution guidelines. |
|
||||
- `DurationFlag` type. |
|
||||
|
|
||||
## [1.2.0] - 2014-08-02 |
|
||||
### Added |
|
||||
- Support for environment variable defaults on flags plus tests. |
|
||||
|
|
||||
## [1.1.0] - 2014-07-15 |
|
||||
### Added |
|
||||
- Bash completion. |
|
||||
- Optional hiding of built-in help command. |
|
||||
- Optional skipping of flag parsing at command level. |
|
||||
- `Author`, `Email`, and `Compiled` metadata on app. |
|
||||
- `Before` hook func support at app and command level. |
|
||||
- `CommandNotFound` func support at app level. |
|
||||
- Command reference available on context. |
|
||||
- `GenericFlag` type. |
|
||||
- `Float64Flag` type. |
|
||||
- `BoolTFlag` type. |
|
||||
- `IsSet` flag helper on context. |
|
||||
- More flag lookup funcs at context level. |
|
||||
- More tests & docs. |
|
||||
|
|
||||
### Changed |
|
||||
- Help template updates to account for presence/absence of flags. |
|
||||
- Separated subcommand help template. |
|
||||
- Exposed `HelpPrinter` var for more control over help output. |
|
||||
|
|
||||
## [1.0.0] - 2013-11-01 |
|
||||
### Added |
|
||||
- `help` flag in default app flag set and each command flag set. |
|
||||
- Custom handling of argument parsing errors. |
|
||||
- Command lookup by name at app level. |
|
||||
- `StringSliceFlag` type and supporting `StringSlice` type. |
|
||||
- `IntSliceFlag` type and supporting `IntSlice` type. |
|
||||
- Slice type flag lookups by name at context level. |
|
||||
- Export of app and command help functions. |
|
||||
- More tests & docs. |
|
||||
|
|
||||
## 0.1.0 - 2013-07-22 |
|
||||
### Added |
|
||||
- Initial implementation. |
|
||||
|
|
||||
[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD |
|
||||
[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0 |
|
||||
[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 |
|
||||
[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 |
|
||||
[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 |
|
||||
[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 |
|
||||
[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 |
|
||||
[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 |
|
||||
[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 |
|
||||
[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 |
|
||||
[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 |
|
||||
[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 |
|
||||
[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 |
|
||||
[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 |
|
||||
[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 |
|
||||
[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 |
|
||||
[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 |
|
||||
[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 |
|
||||
[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 |
|
||||
[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 |
|
||||
[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 |
|
||||
[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 |
|
||||
[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 |
|
||||
[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 |
|
||||
[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 |
|
||||
[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 |
|
@ -1,74 +0,0 @@ |
|||||
# Contributor Covenant Code of Conduct |
|
||||
|
|
||||
## Our Pledge |
|
||||
|
|
||||
In the interest of fostering an open and welcoming environment, we as |
|
||||
contributors and maintainers pledge to making participation in our project and |
|
||||
our community a harassment-free experience for everyone, regardless of age, body |
|
||||
size, disability, ethnicity, gender identity and expression, level of experience, |
|
||||
education, socio-economic status, nationality, personal appearance, race, |
|
||||
religion, or sexual identity and orientation. |
|
||||
|
|
||||
## Our Standards |
|
||||
|
|
||||
Examples of behavior that contributes to creating a positive environment |
|
||||
include: |
|
||||
|
|
||||
* Using welcoming and inclusive language |
|
||||
* Being respectful of differing viewpoints and experiences |
|
||||
* Gracefully accepting constructive criticism |
|
||||
* Focusing on what is best for the community |
|
||||
* Showing empathy towards other community members |
|
||||
|
|
||||
Examples of unacceptable behavior by participants include: |
|
||||
|
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or |
|
||||
advances |
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks |
|
||||
* Public or private harassment |
|
||||
* Publishing others' private information, such as a physical or electronic |
|
||||
address, without explicit permission |
|
||||
* Other conduct which could reasonably be considered inappropriate in a |
|
||||
professional setting |
|
||||
|
|
||||
## Our Responsibilities |
|
||||
|
|
||||
Project maintainers are responsible for clarifying the standards of acceptable |
|
||||
behavior and are expected to take appropriate and fair corrective action in |
|
||||
response to any instances of unacceptable behavior. |
|
||||
|
|
||||
Project maintainers have the right and responsibility to remove, edit, or |
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions |
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or |
|
||||
permanently any contributor for other behaviors that they deem inappropriate, |
|
||||
threatening, offensive, or harmful. |
|
||||
|
|
||||
## Scope |
|
||||
|
|
||||
This Code of Conduct applies both within project spaces and in public spaces |
|
||||
when an individual is representing the project or its community. Examples of |
|
||||
representing a project or community include using an official project e-mail |
|
||||
address, posting via an official social media account, or acting as an appointed |
|
||||
representative at an online or offline event. Representation of a project may be |
|
||||
further defined and clarified by project maintainers. |
|
||||
|
|
||||
## Enforcement |
|
||||
|
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be |
|
||||
reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be |
|
||||
reviewed and investigated and will result in a response that is deemed necessary |
|
||||
and appropriate to the circumstances. The project team is obligated to maintain |
|
||||
confidentiality with regard to the reporter of an incident. Further details of |
|
||||
specific enforcement policies may be posted separately. |
|
||||
|
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good |
|
||||
faith may face temporary or permanent repercussions as determined by other |
|
||||
members of the project's leadership. |
|
||||
|
|
||||
## Attribution |
|
||||
|
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, |
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html |
|
||||
|
|
||||
[homepage]: https://www.contributor-covenant.org |
|
||||
|
|
@ -1,19 +0,0 @@ |
|||||
## Contributing |
|
||||
|
|
||||
**NOTE**: the primary maintainer(s) may be found in |
|
||||
[./MAINTAINERS.md](./MAINTAINERS.md). |
|
||||
|
|
||||
Feel free to put up a pull request to fix a bug or maybe add a feature. I will |
|
||||
give it a code review and make sure that it does not break backwards |
|
||||
compatibility. If I or any other collaborators agree that it is in line with |
|
||||
the vision of the project, we will work with you to get the code into |
|
||||
a mergeable state and merge it into the master branch. |
|
||||
|
|
||||
If you have contributed something significant to the project, we will most |
|
||||
likely add you as a collaborator. As a collaborator you are given the ability |
|
||||
to merge others pull requests. It is very important that new code does not |
|
||||
break existing code, so be careful about what code you do choose to merge. |
|
||||
|
|
||||
If you feel like you have contributed to the project but have not yet been added |
|
||||
as a collaborator, we probably forgot to add you :sweat_smile:. Please open an |
|
||||
issue! |
|
@ -1,21 +0,0 @@ |
|||||
MIT License |
|
||||
|
|
||||
Copyright (c) 2016 Jeremy Saenz & Contributors |
|
||||
|
|
||||
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. |
|
@ -1 +0,0 @@ |
|||||
- @meatballhat |
|
File diff suppressed because it is too large
@ -1,508 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"io" |
|
||||
"io/ioutil" |
|
||||
"os" |
|
||||
"path/filepath" |
|
||||
"sort" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" |
|
||||
appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) |
|
||||
runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) |
|
||||
|
|
||||
contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." |
|
||||
|
|
||||
errInvalidActionType = NewExitError("ERROR invalid Action type. "+ |
|
||||
fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ |
|
||||
fmt.Sprintf("See %s", appActionDeprecationURL), 2) |
|
||||
) |
|
||||
|
|
||||
// App is the main structure of a cli application. It is recommended that
|
|
||||
// an app be created with the cli.NewApp() function
|
|
||||
type App struct { |
|
||||
// The name of the program. Defaults to path.Base(os.Args[0])
|
|
||||
Name string |
|
||||
// Full name of command for help, defaults to Name
|
|
||||
HelpName string |
|
||||
// Description of the program.
|
|
||||
Usage string |
|
||||
// Text to override the USAGE section of help
|
|
||||
UsageText string |
|
||||
// Description of the program argument format.
|
|
||||
ArgsUsage string |
|
||||
// Version of the program
|
|
||||
Version string |
|
||||
// Description of the program
|
|
||||
Description string |
|
||||
// List of commands to execute
|
|
||||
Commands []Command |
|
||||
// List of flags to parse
|
|
||||
Flags []Flag |
|
||||
// Boolean to enable bash completion commands
|
|
||||
EnableBashCompletion bool |
|
||||
// Boolean to hide built-in help command
|
|
||||
HideHelp bool |
|
||||
// Boolean to hide built-in version flag and the VERSION section of help
|
|
||||
HideVersion bool |
|
||||
// Populate on app startup, only gettable through method Categories()
|
|
||||
categories CommandCategories |
|
||||
// An action to execute when the bash-completion flag is set
|
|
||||
BashComplete BashCompleteFunc |
|
||||
// An action to execute before any subcommands are run, but after the context is ready
|
|
||||
// If a non-nil error is returned, no subcommands are run
|
|
||||
Before BeforeFunc |
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||
// It is run even if Action() panics
|
|
||||
After AfterFunc |
|
||||
|
|
||||
// The action to execute when no subcommands are specified
|
|
||||
// Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}`
|
|
||||
// *Note*: support for the deprecated `Action` signature will be removed in a future version
|
|
||||
Action interface{} |
|
||||
|
|
||||
// Execute this function if the proper command cannot be found
|
|
||||
CommandNotFound CommandNotFoundFunc |
|
||||
// Execute this function if an usage error occurs
|
|
||||
OnUsageError OnUsageErrorFunc |
|
||||
// Compilation date
|
|
||||
Compiled time.Time |
|
||||
// List of all authors who contributed
|
|
||||
Authors []Author |
|
||||
// Copyright of the binary if any
|
|
||||
Copyright string |
|
||||
// Name of Author (Note: Use App.Authors, this is deprecated)
|
|
||||
Author string |
|
||||
// Email of Author (Note: Use App.Authors, this is deprecated)
|
|
||||
Email string |
|
||||
// Writer writer to write output to
|
|
||||
Writer io.Writer |
|
||||
// ErrWriter writes error output
|
|
||||
ErrWriter io.Writer |
|
||||
// Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to
|
|
||||
// function as a default, so this is optional.
|
|
||||
ExitErrHandler ExitErrHandlerFunc |
|
||||
// Other custom info
|
|
||||
Metadata map[string]interface{} |
|
||||
// Carries a function which returns app specific info.
|
|
||||
ExtraInfo func() map[string]string |
|
||||
// CustomAppHelpTemplate the text template for app help topic.
|
|
||||
// cli.go uses text/template to render templates. You can
|
|
||||
// render custom help text by setting this variable.
|
|
||||
CustomAppHelpTemplate string |
|
||||
|
|
||||
didSetup bool |
|
||||
} |
|
||||
|
|
||||
// Tries to find out when this binary was compiled.
|
|
||||
// Returns the current time if it fails to find it.
|
|
||||
func compileTime() time.Time { |
|
||||
info, err := os.Stat(os.Args[0]) |
|
||||
if err != nil { |
|
||||
return time.Now() |
|
||||
} |
|
||||
return info.ModTime() |
|
||||
} |
|
||||
|
|
||||
// NewApp creates a new cli Application with some reasonable defaults for Name,
|
|
||||
// Usage, Version and Action.
|
|
||||
func NewApp() *App { |
|
||||
return &App{ |
|
||||
Name: filepath.Base(os.Args[0]), |
|
||||
HelpName: filepath.Base(os.Args[0]), |
|
||||
Usage: "A new cli application", |
|
||||
UsageText: "", |
|
||||
Version: "0.0.0", |
|
||||
BashComplete: DefaultAppComplete, |
|
||||
Action: helpCommand.Action, |
|
||||
Compiled: compileTime(), |
|
||||
Writer: os.Stdout, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Setup runs initialization code to ensure all data structures are ready for
|
|
||||
// `Run` or inspection prior to `Run`. It is internally called by `Run`, but
|
|
||||
// will return early if setup has already happened.
|
|
||||
func (a *App) Setup() { |
|
||||
if a.didSetup { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
a.didSetup = true |
|
||||
|
|
||||
if a.Author != "" || a.Email != "" { |
|
||||
a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) |
|
||||
} |
|
||||
|
|
||||
newCmds := []Command{} |
|
||||
for _, c := range a.Commands { |
|
||||
if c.HelpName == "" { |
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) |
|
||||
} |
|
||||
newCmds = append(newCmds, c) |
|
||||
} |
|
||||
a.Commands = newCmds |
|
||||
|
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp { |
|
||||
a.Commands = append(a.Commands, helpCommand) |
|
||||
if (HelpFlag != BoolFlag{}) { |
|
||||
a.appendFlag(HelpFlag) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if !a.HideVersion { |
|
||||
a.appendFlag(VersionFlag) |
|
||||
} |
|
||||
|
|
||||
a.categories = CommandCategories{} |
|
||||
for _, command := range a.Commands { |
|
||||
a.categories = a.categories.AddCommand(command.Category, command) |
|
||||
} |
|
||||
sort.Sort(a.categories) |
|
||||
|
|
||||
if a.Metadata == nil { |
|
||||
a.Metadata = make(map[string]interface{}) |
|
||||
} |
|
||||
|
|
||||
if a.Writer == nil { |
|
||||
a.Writer = os.Stdout |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Run is the entry point to the cli app. Parses the arguments slice and routes
|
|
||||
// to the proper flag/args combination
|
|
||||
func (a *App) Run(arguments []string) (err error) { |
|
||||
a.Setup() |
|
||||
|
|
||||
// handle the completion flag separately from the flagset since
|
|
||||
// completion could be attempted after a flag, but before its value was put
|
|
||||
// on the command line. this causes the flagset to interpret the completion
|
|
||||
// flag name as the value of the flag before it which is undesirable
|
|
||||
// note that we can only do this because the shell autocomplete function
|
|
||||
// always appends the completion flag at the end of the command
|
|
||||
shellComplete, arguments := checkShellCompleteFlag(a, arguments) |
|
||||
|
|
||||
// parse flags
|
|
||||
set, err := flagSet(a.Name, a.Flags) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
set.SetOutput(ioutil.Discard) |
|
||||
err = set.Parse(arguments[1:]) |
|
||||
nerr := normalizeFlags(a.Flags, set) |
|
||||
context := NewContext(a, set, nil) |
|
||||
if nerr != nil { |
|
||||
fmt.Fprintln(a.Writer, nerr) |
|
||||
ShowAppHelp(context) |
|
||||
return nerr |
|
||||
} |
|
||||
context.shellComplete = shellComplete |
|
||||
|
|
||||
if checkCompletions(context) { |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
if err != nil { |
|
||||
if a.OnUsageError != nil { |
|
||||
err := a.OnUsageError(context, err, false) |
|
||||
a.handleExitCoder(context, err) |
|
||||
return err |
|
||||
} |
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) |
|
||||
ShowAppHelp(context) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
if !a.HideHelp && checkHelp(context) { |
|
||||
ShowAppHelp(context) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
if !a.HideVersion && checkVersion(context) { |
|
||||
ShowVersion(context) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
if a.After != nil { |
|
||||
defer func() { |
|
||||
if afterErr := a.After(context); afterErr != nil { |
|
||||
if err != nil { |
|
||||
err = NewMultiError(err, afterErr) |
|
||||
} else { |
|
||||
err = afterErr |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
} |
|
||||
|
|
||||
if a.Before != nil { |
|
||||
beforeErr := a.Before(context) |
|
||||
if beforeErr != nil { |
|
||||
fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) |
|
||||
ShowAppHelp(context) |
|
||||
a.handleExitCoder(context, beforeErr) |
|
||||
err = beforeErr |
|
||||
return err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
args := context.Args() |
|
||||
if args.Present() { |
|
||||
name := args.First() |
|
||||
c := a.Command(name) |
|
||||
if c != nil { |
|
||||
return c.Run(context) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if a.Action == nil { |
|
||||
a.Action = helpCommand.Action |
|
||||
} |
|
||||
|
|
||||
// Run default Action
|
|
||||
err = HandleAction(a.Action, context) |
|
||||
|
|
||||
a.handleExitCoder(context, err) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
// RunAndExitOnError calls .Run() and exits non-zero if an error was returned
|
|
||||
//
|
|
||||
// Deprecated: instead you should return an error that fulfills cli.ExitCoder
|
|
||||
// to cli.App.Run. This will cause the application to exit with the given eror
|
|
||||
// code in the cli.ExitCoder
|
|
||||
func (a *App) RunAndExitOnError() { |
|
||||
if err := a.Run(os.Args); err != nil { |
|
||||
fmt.Fprintln(a.errWriter(), err) |
|
||||
OsExiter(1) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to
|
|
||||
// generate command-specific flags
|
|
||||
func (a *App) RunAsSubcommand(ctx *Context) (err error) { |
|
||||
// append help to commands
|
|
||||
if len(a.Commands) > 0 { |
|
||||
if a.Command(helpCommand.Name) == nil && !a.HideHelp { |
|
||||
a.Commands = append(a.Commands, helpCommand) |
|
||||
if (HelpFlag != BoolFlag{}) { |
|
||||
a.appendFlag(HelpFlag) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
newCmds := []Command{} |
|
||||
for _, c := range a.Commands { |
|
||||
if c.HelpName == "" { |
|
||||
c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) |
|
||||
} |
|
||||
newCmds = append(newCmds, c) |
|
||||
} |
|
||||
a.Commands = newCmds |
|
||||
|
|
||||
// parse flags
|
|
||||
set, err := flagSet(a.Name, a.Flags) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
set.SetOutput(ioutil.Discard) |
|
||||
err = set.Parse(ctx.Args().Tail()) |
|
||||
nerr := normalizeFlags(a.Flags, set) |
|
||||
context := NewContext(a, set, ctx) |
|
||||
|
|
||||
if nerr != nil { |
|
||||
fmt.Fprintln(a.Writer, nerr) |
|
||||
fmt.Fprintln(a.Writer) |
|
||||
if len(a.Commands) > 0 { |
|
||||
ShowSubcommandHelp(context) |
|
||||
} else { |
|
||||
ShowCommandHelp(ctx, context.Args().First()) |
|
||||
} |
|
||||
return nerr |
|
||||
} |
|
||||
|
|
||||
if checkCompletions(context) { |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
if err != nil { |
|
||||
if a.OnUsageError != nil { |
|
||||
err = a.OnUsageError(context, err, true) |
|
||||
a.handleExitCoder(context, err) |
|
||||
return err |
|
||||
} |
|
||||
fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) |
|
||||
ShowSubcommandHelp(context) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
if len(a.Commands) > 0 { |
|
||||
if checkSubcommandHelp(context) { |
|
||||
return nil |
|
||||
} |
|
||||
} else { |
|
||||
if checkCommandHelp(ctx, context.Args().First()) { |
|
||||
return nil |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if a.After != nil { |
|
||||
defer func() { |
|
||||
afterErr := a.After(context) |
|
||||
if afterErr != nil { |
|
||||
a.handleExitCoder(context, err) |
|
||||
if err != nil { |
|
||||
err = NewMultiError(err, afterErr) |
|
||||
} else { |
|
||||
err = afterErr |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
} |
|
||||
|
|
||||
if a.Before != nil { |
|
||||
beforeErr := a.Before(context) |
|
||||
if beforeErr != nil { |
|
||||
a.handleExitCoder(context, beforeErr) |
|
||||
err = beforeErr |
|
||||
return err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
args := context.Args() |
|
||||
if args.Present() { |
|
||||
name := args.First() |
|
||||
c := a.Command(name) |
|
||||
if c != nil { |
|
||||
return c.Run(context) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Run default Action
|
|
||||
err = HandleAction(a.Action, context) |
|
||||
|
|
||||
a.handleExitCoder(context, err) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
// Command returns the named command on App. Returns nil if the command does not exist
|
|
||||
func (a *App) Command(name string) *Command { |
|
||||
for _, c := range a.Commands { |
|
||||
if c.HasName(name) { |
|
||||
return &c |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Categories returns a slice containing all the categories with the commands they contain
|
|
||||
func (a *App) Categories() CommandCategories { |
|
||||
return a.categories |
|
||||
} |
|
||||
|
|
||||
// VisibleCategories returns a slice of categories and commands that are
|
|
||||
// Hidden=false
|
|
||||
func (a *App) VisibleCategories() []*CommandCategory { |
|
||||
ret := []*CommandCategory{} |
|
||||
for _, category := range a.categories { |
|
||||
if visible := func() *CommandCategory { |
|
||||
for _, command := range category.Commands { |
|
||||
if !command.Hidden { |
|
||||
return category |
|
||||
} |
|
||||
} |
|
||||
return nil |
|
||||
}(); visible != nil { |
|
||||
ret = append(ret, visible) |
|
||||
} |
|
||||
} |
|
||||
return ret |
|
||||
} |
|
||||
|
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||
func (a *App) VisibleCommands() []Command { |
|
||||
ret := []Command{} |
|
||||
for _, command := range a.Commands { |
|
||||
if !command.Hidden { |
|
||||
ret = append(ret, command) |
|
||||
} |
|
||||
} |
|
||||
return ret |
|
||||
} |
|
||||
|
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||
func (a *App) VisibleFlags() []Flag { |
|
||||
return visibleFlags(a.Flags) |
|
||||
} |
|
||||
|
|
||||
func (a *App) hasFlag(flag Flag) bool { |
|
||||
for _, f := range a.Flags { |
|
||||
if flag == f { |
|
||||
return true |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func (a *App) errWriter() io.Writer { |
|
||||
// When the app ErrWriter is nil use the package level one.
|
|
||||
if a.ErrWriter == nil { |
|
||||
return ErrWriter |
|
||||
} |
|
||||
|
|
||||
return a.ErrWriter |
|
||||
} |
|
||||
|
|
||||
func (a *App) appendFlag(flag Flag) { |
|
||||
if !a.hasFlag(flag) { |
|
||||
a.Flags = append(a.Flags, flag) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (a *App) handleExitCoder(context *Context, err error) { |
|
||||
if a.ExitErrHandler != nil { |
|
||||
a.ExitErrHandler(context, err) |
|
||||
} else { |
|
||||
HandleExitCoder(err) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Author represents someone who has contributed to a cli project.
|
|
||||
type Author struct { |
|
||||
Name string // The Authors name
|
|
||||
Email string // The Authors email
|
|
||||
} |
|
||||
|
|
||||
// String makes Author comply to the Stringer interface, to allow an easy print in the templating process
|
|
||||
func (a Author) String() string { |
|
||||
e := "" |
|
||||
if a.Email != "" { |
|
||||
e = " <" + a.Email + ">" |
|
||||
} |
|
||||
|
|
||||
return fmt.Sprintf("%v%v", a.Name, e) |
|
||||
} |
|
||||
|
|
||||
// HandleAction attempts to figure out which Action signature was used. If
|
|
||||
// it's an ActionFunc or a func with the legacy signature for Action, the func
|
|
||||
// is run!
|
|
||||
func HandleAction(action interface{}, context *Context) (err error) { |
|
||||
if a, ok := action.(ActionFunc); ok { |
|
||||
return a(context) |
|
||||
} else if a, ok := action.(func(*Context) error); ok { |
|
||||
return a(context) |
|
||||
} else if a, ok := action.(func(*Context)); ok { // deprecated function signature
|
|
||||
a(context) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
return errInvalidActionType |
|
||||
} |
|
@ -1,26 +0,0 @@ |
|||||
version: "{build}" |
|
||||
|
|
||||
os: Windows Server 2016 |
|
||||
|
|
||||
image: Visual Studio 2017 |
|
||||
|
|
||||
clone_folder: c:\gopath\src\github.com\urfave\cli |
|
||||
|
|
||||
environment: |
|
||||
GOPATH: C:\gopath |
|
||||
GOVERSION: 1.8.x |
|
||||
PYTHON: C:\Python36-x64 |
|
||||
PYTHON_VERSION: 3.6.x |
|
||||
PYTHON_ARCH: 64 |
|
||||
|
|
||||
install: |
|
||||
- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% |
|
||||
- go version |
|
||||
- go env |
|
||||
- go get github.com/urfave/gfmrun/... |
|
||||
- go get -v -t ./... |
|
||||
|
|
||||
build_script: |
|
||||
- python runtests vet |
|
||||
- python runtests test |
|
||||
- python runtests gfmrun |
|
@ -1,44 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
// CommandCategories is a slice of *CommandCategory.
|
|
||||
type CommandCategories []*CommandCategory |
|
||||
|
|
||||
// CommandCategory is a category containing commands.
|
|
||||
type CommandCategory struct { |
|
||||
Name string |
|
||||
Commands Commands |
|
||||
} |
|
||||
|
|
||||
func (c CommandCategories) Less(i, j int) bool { |
|
||||
return lexicographicLess(c[i].Name, c[j].Name) |
|
||||
} |
|
||||
|
|
||||
func (c CommandCategories) Len() int { |
|
||||
return len(c) |
|
||||
} |
|
||||
|
|
||||
func (c CommandCategories) Swap(i, j int) { |
|
||||
c[i], c[j] = c[j], c[i] |
|
||||
} |
|
||||
|
|
||||
// AddCommand adds a command to a category.
|
|
||||
func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { |
|
||||
for _, commandCategory := range c { |
|
||||
if commandCategory.Name == category { |
|
||||
commandCategory.Commands = append(commandCategory.Commands, command) |
|
||||
return c |
|
||||
} |
|
||||
} |
|
||||
return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) |
|
||||
} |
|
||||
|
|
||||
// VisibleCommands returns a slice of the Commands with Hidden=false
|
|
||||
func (c *CommandCategory) VisibleCommands() []Command { |
|
||||
ret := []Command{} |
|
||||
for _, command := range c.Commands { |
|
||||
if !command.Hidden { |
|
||||
ret = append(ret, command) |
|
||||
} |
|
||||
} |
|
||||
return ret |
|
||||
} |
|
@ -1,22 +0,0 @@ |
|||||
// Package cli provides a minimal framework for creating and organizing command line
|
|
||||
// Go applications. cli is designed to be easy to understand and write, the most simple
|
|
||||
// cli application can be written as follows:
|
|
||||
// func main() {
|
|
||||
// cli.NewApp().Run(os.Args)
|
|
||||
// }
|
|
||||
//
|
|
||||
// Of course this application does not do much, so let's make this an actual application:
|
|
||||
// func main() {
|
|
||||
// app := cli.NewApp()
|
|
||||
// app.Name = "greet"
|
|
||||
// app.Usage = "say a greeting"
|
|
||||
// app.Action = func(c *cli.Context) error {
|
|
||||
// println("Greetings")
|
|
||||
// return nil
|
|
||||
// }
|
|
||||
//
|
|
||||
// app.Run(os.Args)
|
|
||||
// }
|
|
||||
package cli |
|
||||
|
|
||||
//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go
|
|
@ -1,336 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import ( |
|
||||
"flag" |
|
||||
"fmt" |
|
||||
"io/ioutil" |
|
||||
"sort" |
|
||||
"strings" |
|
||||
) |
|
||||
|
|
||||
// Command is a subcommand for a cli.App.
|
|
||||
type Command struct { |
|
||||
// The name of the command
|
|
||||
Name string |
|
||||
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
|
||||
ShortName string |
|
||||
// A list of aliases for the command
|
|
||||
Aliases []string |
|
||||
// A short description of the usage of this command
|
|
||||
Usage string |
|
||||
// Custom text to show on USAGE section of help
|
|
||||
UsageText string |
|
||||
// A longer explanation of how the command works
|
|
||||
Description string |
|
||||
// A short description of the arguments of this command
|
|
||||
ArgsUsage string |
|
||||
// The category the command is part of
|
|
||||
Category string |
|
||||
// The function to call when checking for bash command completions
|
|
||||
BashComplete BashCompleteFunc |
|
||||
// An action to execute before any sub-subcommands are run, but after the context is ready
|
|
||||
// If a non-nil error is returned, no sub-subcommands are run
|
|
||||
Before BeforeFunc |
|
||||
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
||||
// It is run even if Action() panics
|
|
||||
After AfterFunc |
|
||||
// The function to call when this command is invoked
|
|
||||
Action interface{} |
|
||||
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
|
||||
// of deprecation period has passed, maybe?
|
|
||||
|
|
||||
// Execute this function if a usage error occurs.
|
|
||||
OnUsageError OnUsageErrorFunc |
|
||||
// List of child commands
|
|
||||
Subcommands Commands |
|
||||
// List of flags to parse
|
|
||||
Flags []Flag |
|
||||
// Treat all flags as normal arguments if true
|
|
||||
SkipFlagParsing bool |
|
||||
// Skip argument reordering which attempts to move flags before arguments,
|
|
||||
// but only works if all flags appear after all arguments. This behavior was
|
|
||||
// removed n version 2 since it only works under specific conditions so we
|
|
||||
// backport here by exposing it as an option for compatibility.
|
|
||||
SkipArgReorder bool |
|
||||
// Boolean to hide built-in help command
|
|
||||
HideHelp bool |
|
||||
// Boolean to hide this command from help or completion
|
|
||||
Hidden bool |
|
||||
// Boolean to enable short-option handling so user can combine several
|
|
||||
// single-character bool arguements into one
|
|
||||
// i.e. foobar -o -v -> foobar -ov
|
|
||||
UseShortOptionHandling bool |
|
||||
|
|
||||
// Full name of command for help, defaults to full command name, including parent commands.
|
|
||||
HelpName string |
|
||||
commandNamePath []string |
|
||||
|
|
||||
// CustomHelpTemplate the text template for the command help topic.
|
|
||||
// cli.go uses text/template to render templates. You can
|
|
||||
// render custom help text by setting this variable.
|
|
||||
CustomHelpTemplate string |
|
||||
} |
|
||||
|
|
||||
type CommandsByName []Command |
|
||||
|
|
||||
func (c CommandsByName) Len() int { |
|
||||
return len(c) |
|
||||
} |
|
||||
|
|
||||
func (c CommandsByName) Less(i, j int) bool { |
|
||||
return lexicographicLess(c[i].Name, c[j].Name) |
|
||||
} |
|
||||
|
|
||||
func (c CommandsByName) Swap(i, j int) { |
|
||||
c[i], c[j] = c[j], c[i] |
|
||||
} |
|
||||
|
|
||||
// FullName returns the full name of the command.
|
|
||||
// For subcommands this ensures that parent commands are part of the command path
|
|
||||
func (c Command) FullName() string { |
|
||||
if c.commandNamePath == nil { |
|
||||
return c.Name |
|
||||
} |
|
||||
return strings.Join(c.commandNamePath, " ") |
|
||||
} |
|
||||
|
|
||||
// Commands is a slice of Command
|
|
||||
type Commands []Command |
|
||||
|
|
||||
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
|
||||
func (c Command) Run(ctx *Context) (err error) { |
|
||||
if len(c.Subcommands) > 0 { |
|
||||
return c.startApp(ctx) |
|
||||
} |
|
||||
|
|
||||
if !c.HideHelp && (HelpFlag != BoolFlag{}) { |
|
||||
// append help to flags
|
|
||||
c.Flags = append( |
|
||||
c.Flags, |
|
||||
HelpFlag, |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
set, err := c.parseFlags(ctx.Args().Tail()) |
|
||||
|
|
||||
context := NewContext(ctx.App, set, ctx) |
|
||||
context.Command = c |
|
||||
if checkCommandCompletions(context, c.Name) { |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
if err != nil { |
|
||||
if c.OnUsageError != nil { |
|
||||
err := c.OnUsageError(context, err, false) |
|
||||
context.App.handleExitCoder(context, err) |
|
||||
return err |
|
||||
} |
|
||||
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) |
|
||||
fmt.Fprintln(context.App.Writer) |
|
||||
ShowCommandHelp(context, c.Name) |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
if checkCommandHelp(context, c.Name) { |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
if c.After != nil { |
|
||||
defer func() { |
|
||||
afterErr := c.After(context) |
|
||||
if afterErr != nil { |
|
||||
context.App.handleExitCoder(context, err) |
|
||||
if err != nil { |
|
||||
err = NewMultiError(err, afterErr) |
|
||||
} else { |
|
||||
err = afterErr |
|
||||
} |
|
||||
} |
|
||||
}() |
|
||||
} |
|
||||
|
|
||||
if c.Before != nil { |
|
||||
err = c.Before(context) |
|
||||
if err != nil { |
|
||||
ShowCommandHelp(context, c.Name) |
|
||||
context.App.handleExitCoder(context, err) |
|
||||
return err |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if c.Action == nil { |
|
||||
c.Action = helpSubcommand.Action |
|
||||
} |
|
||||
|
|
||||
err = HandleAction(c.Action, context) |
|
||||
|
|
||||
if err != nil { |
|
||||
context.App.handleExitCoder(context, err) |
|
||||
} |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
func (c *Command) parseFlags(args Args) (*flag.FlagSet, error) { |
|
||||
set, err := flagSet(c.Name, c.Flags) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
set.SetOutput(ioutil.Discard) |
|
||||
|
|
||||
if c.SkipFlagParsing { |
|
||||
return set, set.Parse(append([]string{"--"}, args...)) |
|
||||
} |
|
||||
|
|
||||
if c.UseShortOptionHandling { |
|
||||
args = translateShortOptions(args) |
|
||||
} |
|
||||
|
|
||||
if !c.SkipArgReorder { |
|
||||
args = reorderArgs(args) |
|
||||
} |
|
||||
|
|
||||
err = set.Parse(args) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
err = normalizeFlags(c.Flags, set) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return set, nil |
|
||||
} |
|
||||
|
|
||||
// reorderArgs moves all flags before arguments as this is what flag expects
|
|
||||
func reorderArgs(args []string) []string { |
|
||||
var nonflags, flags []string |
|
||||
|
|
||||
readFlagValue := false |
|
||||
for i, arg := range args { |
|
||||
if arg == "--" { |
|
||||
nonflags = append(nonflags, args[i:]...) |
|
||||
break |
|
||||
} |
|
||||
|
|
||||
if readFlagValue && !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") { |
|
||||
readFlagValue = false |
|
||||
flags = append(flags, arg) |
|
||||
continue |
|
||||
} |
|
||||
readFlagValue = false |
|
||||
|
|
||||
if arg != "-" && strings.HasPrefix(arg, "-") { |
|
||||
flags = append(flags, arg) |
|
||||
|
|
||||
readFlagValue = !strings.Contains(arg, "=") |
|
||||
} else { |
|
||||
nonflags = append(nonflags, arg) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return append(flags, nonflags...) |
|
||||
} |
|
||||
|
|
||||
func translateShortOptions(flagArgs Args) []string { |
|
||||
// separate combined flags
|
|
||||
var flagArgsSeparated []string |
|
||||
for _, flagArg := range flagArgs { |
|
||||
if strings.HasPrefix(flagArg, "-") && strings.HasPrefix(flagArg, "--") == false && len(flagArg) > 2 { |
|
||||
for _, flagChar := range flagArg[1:] { |
|
||||
flagArgsSeparated = append(flagArgsSeparated, "-"+string(flagChar)) |
|
||||
} |
|
||||
} else { |
|
||||
flagArgsSeparated = append(flagArgsSeparated, flagArg) |
|
||||
} |
|
||||
} |
|
||||
return flagArgsSeparated |
|
||||
} |
|
||||
|
|
||||
// Names returns the names including short names and aliases.
|
|
||||
func (c Command) Names() []string { |
|
||||
names := []string{c.Name} |
|
||||
|
|
||||
if c.ShortName != "" { |
|
||||
names = append(names, c.ShortName) |
|
||||
} |
|
||||
|
|
||||
return append(names, c.Aliases...) |
|
||||
} |
|
||||
|
|
||||
// HasName returns true if Command.Name or Command.ShortName matches given name
|
|
||||
func (c Command) HasName(name string) bool { |
|
||||
for _, n := range c.Names() { |
|
||||
if n == name { |
|
||||
return true |
|
||||
} |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func (c Command) startApp(ctx *Context) error { |
|
||||
app := NewApp() |
|
||||
app.Metadata = ctx.App.Metadata |
|
||||
// set the name and usage
|
|
||||
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) |
|
||||
if c.HelpName == "" { |
|
||||
app.HelpName = c.HelpName |
|
||||
} else { |
|
||||
app.HelpName = app.Name |
|
||||
} |
|
||||
|
|
||||
app.Usage = c.Usage |
|
||||
app.Description = c.Description |
|
||||
app.ArgsUsage = c.ArgsUsage |
|
||||
|
|
||||
// set CommandNotFound
|
|
||||
app.CommandNotFound = ctx.App.CommandNotFound |
|
||||
app.CustomAppHelpTemplate = c.CustomHelpTemplate |
|
||||
|
|
||||
// set the flags and commands
|
|
||||
app.Commands = c.Subcommands |
|
||||
app.Flags = c.Flags |
|
||||
app.HideHelp = c.HideHelp |
|
||||
|
|
||||
app.Version = ctx.App.Version |
|
||||
app.HideVersion = ctx.App.HideVersion |
|
||||
app.Compiled = ctx.App.Compiled |
|
||||
app.Author = ctx.App.Author |
|
||||
app.Email = ctx.App.Email |
|
||||
app.Writer = ctx.App.Writer |
|
||||
app.ErrWriter = ctx.App.ErrWriter |
|
||||
|
|
||||
app.categories = CommandCategories{} |
|
||||
for _, command := range c.Subcommands { |
|
||||
app.categories = app.categories.AddCommand(command.Category, command) |
|
||||
} |
|
||||
|
|
||||
sort.Sort(app.categories) |
|
||||
|
|
||||
// bash completion
|
|
||||
app.EnableBashCompletion = ctx.App.EnableBashCompletion |
|
||||
if c.BashComplete != nil { |
|
||||
app.BashComplete = c.BashComplete |
|
||||
} |
|
||||
|
|
||||
// set the actions
|
|
||||
app.Before = c.Before |
|
||||
app.After = c.After |
|
||||
if c.Action != nil { |
|
||||
app.Action = c.Action |
|
||||
} else { |
|
||||
app.Action = helpSubcommand.Action |
|
||||
} |
|
||||
app.OnUsageError = c.OnUsageError |
|
||||
|
|
||||
for index, cc := range app.Commands { |
|
||||
app.Commands[index].commandNamePath = []string{c.Name, cc.Name} |
|
||||
} |
|
||||
|
|
||||
return app.RunAsSubcommand(ctx) |
|
||||
} |
|
||||
|
|
||||
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
||||
func (c Command) VisibleFlags() []Flag { |
|
||||
return visibleFlags(c.Flags) |
|
||||
} |
|
@ -1,287 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
"flag" |
|
||||
"os" |
|
||||
"reflect" |
|
||||
"strings" |
|
||||
"syscall" |
|
||||
) |
|
||||
|
|
||||
// Context is a type that is passed through to
|
|
||||
// each Handler action in a cli application. Context
|
|
||||
// can be used to retrieve context-specific Args and
|
|
||||
// parsed command-line options.
|
|
||||
type Context struct { |
|
||||
App *App |
|
||||
Command Command |
|
||||
shellComplete bool |
|
||||
flagSet *flag.FlagSet |
|
||||
setFlags map[string]bool |
|
||||
parentContext *Context |
|
||||
} |
|
||||
|
|
||||
// NewContext creates a new context. For use in when invoking an App or Command action.
|
|
||||
func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { |
|
||||
c := &Context{App: app, flagSet: set, parentContext: parentCtx} |
|
||||
|
|
||||
if parentCtx != nil { |
|
||||
c.shellComplete = parentCtx.shellComplete |
|
||||
} |
|
||||
|
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// NumFlags returns the number of flags set
|
|
||||
func (c *Context) NumFlags() int { |
|
||||
return c.flagSet.NFlag() |
|
||||
} |
|
||||
|
|
||||
// Set sets a context flag to a value.
|
|
||||
func (c *Context) Set(name, value string) error { |
|
||||
c.setFlags = nil |
|
||||
return c.flagSet.Set(name, value) |
|
||||
} |
|
||||
|
|
||||
// GlobalSet sets a context flag to a value on the global flagset
|
|
||||
func (c *Context) GlobalSet(name, value string) error { |
|
||||
globalContext(c).setFlags = nil |
|
||||
return globalContext(c).flagSet.Set(name, value) |
|
||||
} |
|
||||
|
|
||||
// IsSet determines if the flag was actually set
|
|
||||
func (c *Context) IsSet(name string) bool { |
|
||||
if c.setFlags == nil { |
|
||||
c.setFlags = make(map[string]bool) |
|
||||
|
|
||||
c.flagSet.Visit(func(f *flag.Flag) { |
|
||||
c.setFlags[f.Name] = true |
|
||||
}) |
|
||||
|
|
||||
c.flagSet.VisitAll(func(f *flag.Flag) { |
|
||||
if _, ok := c.setFlags[f.Name]; ok { |
|
||||
return |
|
||||
} |
|
||||
c.setFlags[f.Name] = false |
|
||||
}) |
|
||||
|
|
||||
// XXX hack to support IsSet for flags with EnvVar
|
|
||||
//
|
|
||||
// There isn't an easy way to do this with the current implementation since
|
|
||||
// whether a flag was set via an environment variable is very difficult to
|
|
||||
// determine here. Instead, we intend to introduce a backwards incompatible
|
|
||||
// change in version 2 to add `IsSet` to the Flag interface to push the
|
|
||||
// responsibility closer to where the information required to determine
|
|
||||
// whether a flag is set by non-standard means such as environment
|
|
||||
// variables is available.
|
|
||||
//
|
|
||||
// See https://github.com/urfave/cli/issues/294 for additional discussion
|
|
||||
flags := c.Command.Flags |
|
||||
if c.Command.Name == "" { // cannot == Command{} since it contains slice types
|
|
||||
if c.App != nil { |
|
||||
flags = c.App.Flags |
|
||||
} |
|
||||
} |
|
||||
for _, f := range flags { |
|
||||
eachName(f.GetName(), func(name string) { |
|
||||
if isSet, ok := c.setFlags[name]; isSet || !ok { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
val := reflect.ValueOf(f) |
|
||||
if val.Kind() == reflect.Ptr { |
|
||||
val = val.Elem() |
|
||||
} |
|
||||
|
|
||||
filePathValue := val.FieldByName("FilePath") |
|
||||
if filePathValue.IsValid() { |
|
||||
eachName(filePathValue.String(), func(filePath string) { |
|
||||
if _, err := os.Stat(filePath); err == nil { |
|
||||
c.setFlags[name] = true |
|
||||
return |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
envVarValue := val.FieldByName("EnvVar") |
|
||||
if envVarValue.IsValid() { |
|
||||
eachName(envVarValue.String(), func(envVar string) { |
|
||||
envVar = strings.TrimSpace(envVar) |
|
||||
if _, ok := syscall.Getenv(envVar); ok { |
|
||||
c.setFlags[name] = true |
|
||||
return |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return c.setFlags[name] |
|
||||
} |
|
||||
|
|
||||
// GlobalIsSet determines if the global flag was actually set
|
|
||||
func (c *Context) GlobalIsSet(name string) bool { |
|
||||
ctx := c |
|
||||
if ctx.parentContext != nil { |
|
||||
ctx = ctx.parentContext |
|
||||
} |
|
||||
|
|
||||
for ; ctx != nil; ctx = ctx.parentContext { |
|
||||
if ctx.IsSet(name) { |
|
||||
return true |
|
||||
} |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
// FlagNames returns a slice of flag names used in this context.
|
|
||||
func (c *Context) FlagNames() (names []string) { |
|
||||
for _, flag := range c.Command.Flags { |
|
||||
name := strings.Split(flag.GetName(), ",")[0] |
|
||||
if name == "help" { |
|
||||
continue |
|
||||
} |
|
||||
names = append(names, name) |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// GlobalFlagNames returns a slice of global flag names used by the app.
|
|
||||
func (c *Context) GlobalFlagNames() (names []string) { |
|
||||
for _, flag := range c.App.Flags { |
|
||||
name := strings.Split(flag.GetName(), ",")[0] |
|
||||
if name == "help" || name == "version" { |
|
||||
continue |
|
||||
} |
|
||||
names = append(names, name) |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Parent returns the parent context, if any
|
|
||||
func (c *Context) Parent() *Context { |
|
||||
return c.parentContext |
|
||||
} |
|
||||
|
|
||||
// value returns the value of the flag coressponding to `name`
|
|
||||
func (c *Context) value(name string) interface{} { |
|
||||
return c.flagSet.Lookup(name).Value.(flag.Getter).Get() |
|
||||
} |
|
||||
|
|
||||
// Args contains apps console arguments
|
|
||||
type Args []string |
|
||||
|
|
||||
// Args returns the command line arguments associated with the context.
|
|
||||
func (c *Context) Args() Args { |
|
||||
args := Args(c.flagSet.Args()) |
|
||||
return args |
|
||||
} |
|
||||
|
|
||||
// NArg returns the number of the command line arguments.
|
|
||||
func (c *Context) NArg() int { |
|
||||
return len(c.Args()) |
|
||||
} |
|
||||
|
|
||||
// Get returns the nth argument, or else a blank string
|
|
||||
func (a Args) Get(n int) string { |
|
||||
if len(a) > n { |
|
||||
return a[n] |
|
||||
} |
|
||||
return "" |
|
||||
} |
|
||||
|
|
||||
// First returns the first argument, or else a blank string
|
|
||||
func (a Args) First() string { |
|
||||
return a.Get(0) |
|
||||
} |
|
||||
|
|
||||
// Tail returns the rest of the arguments (not the first one)
|
|
||||
// or else an empty string slice
|
|
||||
func (a Args) Tail() []string { |
|
||||
if len(a) >= 2 { |
|
||||
return []string(a)[1:] |
|
||||
} |
|
||||
return []string{} |
|
||||
} |
|
||||
|
|
||||
// Present checks if there are any arguments present
|
|
||||
func (a Args) Present() bool { |
|
||||
return len(a) != 0 |
|
||||
} |
|
||||
|
|
||||
// Swap swaps arguments at the given indexes
|
|
||||
func (a Args) Swap(from, to int) error { |
|
||||
if from >= len(a) || to >= len(a) { |
|
||||
return errors.New("index out of range") |
|
||||
} |
|
||||
a[from], a[to] = a[to], a[from] |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func globalContext(ctx *Context) *Context { |
|
||||
if ctx == nil { |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
for { |
|
||||
if ctx.parentContext == nil { |
|
||||
return ctx |
|
||||
} |
|
||||
ctx = ctx.parentContext |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { |
|
||||
if ctx.parentContext != nil { |
|
||||
ctx = ctx.parentContext |
|
||||
} |
|
||||
for ; ctx != nil; ctx = ctx.parentContext { |
|
||||
if f := ctx.flagSet.Lookup(name); f != nil { |
|
||||
return ctx.flagSet |
|
||||
} |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { |
|
||||
switch ff.Value.(type) { |
|
||||
case *StringSlice: |
|
||||
default: |
|
||||
set.Set(name, ff.Value.String()) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func normalizeFlags(flags []Flag, set *flag.FlagSet) error { |
|
||||
visited := make(map[string]bool) |
|
||||
set.Visit(func(f *flag.Flag) { |
|
||||
visited[f.Name] = true |
|
||||
}) |
|
||||
for _, f := range flags { |
|
||||
parts := strings.Split(f.GetName(), ",") |
|
||||
if len(parts) == 1 { |
|
||||
continue |
|
||||
} |
|
||||
var ff *flag.Flag |
|
||||
for _, name := range parts { |
|
||||
name = strings.Trim(name, " ") |
|
||||
if visited[name] { |
|
||||
if ff != nil { |
|
||||
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) |
|
||||
} |
|
||||
ff = set.Lookup(name) |
|
||||
} |
|
||||
} |
|
||||
if ff == nil { |
|
||||
continue |
|
||||
} |
|
||||
for _, name := range parts { |
|
||||
name = strings.Trim(name, " ") |
|
||||
if !visited[name] { |
|
||||
copyFlag(name, ff, set) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
@ -1,115 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"io" |
|
||||
"os" |
|
||||
"strings" |
|
||||
) |
|
||||
|
|
||||
// OsExiter is the function used when the app exits. If not set defaults to os.Exit.
|
|
||||
var OsExiter = os.Exit |
|
||||
|
|
||||
// ErrWriter is used to write errors to the user. This can be anything
|
|
||||
// implementing the io.Writer interface and defaults to os.Stderr.
|
|
||||
var ErrWriter io.Writer = os.Stderr |
|
||||
|
|
||||
// MultiError is an error that wraps multiple errors.
|
|
||||
type MultiError struct { |
|
||||
Errors []error |
|
||||
} |
|
||||
|
|
||||
// NewMultiError creates a new MultiError. Pass in one or more errors.
|
|
||||
func NewMultiError(err ...error) MultiError { |
|
||||
return MultiError{Errors: err} |
|
||||
} |
|
||||
|
|
||||
// Error implements the error interface.
|
|
||||
func (m MultiError) Error() string { |
|
||||
errs := make([]string, len(m.Errors)) |
|
||||
for i, err := range m.Errors { |
|
||||
errs[i] = err.Error() |
|
||||
} |
|
||||
|
|
||||
return strings.Join(errs, "\n") |
|
||||
} |
|
||||
|
|
||||
type ErrorFormatter interface { |
|
||||
Format(s fmt.State, verb rune) |
|
||||
} |
|
||||
|
|
||||
// ExitCoder is the interface checked by `App` and `Command` for a custom exit
|
|
||||
// code
|
|
||||
type ExitCoder interface { |
|
||||
error |
|
||||
ExitCode() int |
|
||||
} |
|
||||
|
|
||||
// ExitError fulfills both the builtin `error` interface and `ExitCoder`
|
|
||||
type ExitError struct { |
|
||||
exitCode int |
|
||||
message interface{} |
|
||||
} |
|
||||
|
|
||||
// NewExitError makes a new *ExitError
|
|
||||
func NewExitError(message interface{}, exitCode int) *ExitError { |
|
||||
return &ExitError{ |
|
||||
exitCode: exitCode, |
|
||||
message: message, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Error returns the string message, fulfilling the interface required by
|
|
||||
// `error`
|
|
||||
func (ee *ExitError) Error() string { |
|
||||
return fmt.Sprintf("%v", ee.message) |
|
||||
} |
|
||||
|
|
||||
// ExitCode returns the exit code, fulfilling the interface required by
|
|
||||
// `ExitCoder`
|
|
||||
func (ee *ExitError) ExitCode() int { |
|
||||
return ee.exitCode |
|
||||
} |
|
||||
|
|
||||
// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if
|
|
||||
// so prints the error to stderr (if it is non-empty) and calls OsExiter with the
|
|
||||
// given exit code. If the given error is a MultiError, then this func is
|
|
||||
// called on all members of the Errors slice and calls OsExiter with the last exit code.
|
|
||||
func HandleExitCoder(err error) { |
|
||||
if err == nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
if exitErr, ok := err.(ExitCoder); ok { |
|
||||
if err.Error() != "" { |
|
||||
if _, ok := exitErr.(ErrorFormatter); ok { |
|
||||
fmt.Fprintf(ErrWriter, "%+v\n", err) |
|
||||
} else { |
|
||||
fmt.Fprintln(ErrWriter, err) |
|
||||
} |
|
||||
} |
|
||||
OsExiter(exitErr.ExitCode()) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
if multiErr, ok := err.(MultiError); ok { |
|
||||
code := handleMultiError(multiErr) |
|
||||
OsExiter(code) |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func handleMultiError(multiErr MultiError) int { |
|
||||
code := 1 |
|
||||
for _, merr := range multiErr.Errors { |
|
||||
if multiErr2, ok := merr.(MultiError); ok { |
|
||||
code = handleMultiError(multiErr2) |
|
||||
} else { |
|
||||
fmt.Fprintln(ErrWriter, merr) |
|
||||
if exitErr, ok := merr.(ExitCoder); ok { |
|
||||
code = exitErr.ExitCode() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return code |
|
||||
} |
|
@ -1,93 +0,0 @@ |
|||||
[ |
|
||||
{ |
|
||||
"name": "Bool", |
|
||||
"type": "bool", |
|
||||
"value": false, |
|
||||
"context_default": "false", |
|
||||
"parser": "strconv.ParseBool(f.Value.String())" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "BoolT", |
|
||||
"type": "bool", |
|
||||
"value": false, |
|
||||
"doctail": " that is true by default", |
|
||||
"context_default": "false", |
|
||||
"parser": "strconv.ParseBool(f.Value.String())" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Duration", |
|
||||
"type": "time.Duration", |
|
||||
"doctail": " (see https://golang.org/pkg/time/#ParseDuration)", |
|
||||
"context_default": "0", |
|
||||
"parser": "time.ParseDuration(f.Value.String())" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Float64", |
|
||||
"type": "float64", |
|
||||
"context_default": "0", |
|
||||
"parser": "strconv.ParseFloat(f.Value.String(), 64)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Generic", |
|
||||
"type": "Generic", |
|
||||
"dest": false, |
|
||||
"context_default": "nil", |
|
||||
"context_type": "interface{}" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Int64", |
|
||||
"type": "int64", |
|
||||
"context_default": "0", |
|
||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Int", |
|
||||
"type": "int", |
|
||||
"context_default": "0", |
|
||||
"parser": "strconv.ParseInt(f.Value.String(), 0, 64)", |
|
||||
"parser_cast": "int(parsed)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "IntSlice", |
|
||||
"type": "*IntSlice", |
|
||||
"dest": false, |
|
||||
"context_default": "nil", |
|
||||
"context_type": "[]int", |
|
||||
"parser": "(f.Value.(*IntSlice)).Value(), error(nil)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Int64Slice", |
|
||||
"type": "*Int64Slice", |
|
||||
"dest": false, |
|
||||
"context_default": "nil", |
|
||||
"context_type": "[]int64", |
|
||||
"parser": "(f.Value.(*Int64Slice)).Value(), error(nil)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "String", |
|
||||
"type": "string", |
|
||||
"context_default": "\"\"", |
|
||||
"parser": "f.Value.String(), error(nil)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "StringSlice", |
|
||||
"type": "*StringSlice", |
|
||||
"dest": false, |
|
||||
"context_default": "nil", |
|
||||
"context_type": "[]string", |
|
||||
"parser": "(f.Value.(*StringSlice)).Value(), error(nil)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Uint64", |
|
||||
"type": "uint64", |
|
||||
"context_default": "0", |
|
||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "Uint", |
|
||||
"type": "uint", |
|
||||
"context_default": "0", |
|
||||
"parser": "strconv.ParseUint(f.Value.String(), 0, 64)", |
|
||||
"parser_cast": "uint(parsed)" |
|
||||
} |
|
||||
] |
|
@ -1,786 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import ( |
|
||||
"flag" |
|
||||
"fmt" |
|
||||
"io/ioutil" |
|
||||
"reflect" |
|
||||
"runtime" |
|
||||
"strconv" |
|
||||
"strings" |
|
||||
"syscall" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
const defaultPlaceholder = "value" |
|
||||
|
|
||||
// BashCompletionFlag enables bash-completion for all commands and subcommands
|
|
||||
var BashCompletionFlag Flag = BoolFlag{ |
|
||||
Name: "generate-bash-completion", |
|
||||
Hidden: true, |
|
||||
} |
|
||||
|
|
||||
// VersionFlag prints the version for the application
|
|
||||
var VersionFlag Flag = BoolFlag{ |
|
||||
Name: "version, v", |
|
||||
Usage: "print the version", |
|
||||
} |
|
||||
|
|
||||
// HelpFlag prints the help for all commands and subcommands
|
|
||||
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
|
||||
// unless HideHelp is set to true)
|
|
||||
var HelpFlag Flag = BoolFlag{ |
|
||||
Name: "help, h", |
|
||||
Usage: "show help", |
|
||||
} |
|
||||
|
|
||||
// FlagStringer converts a flag definition to a string. This is used by help
|
|
||||
// to display a flag.
|
|
||||
var FlagStringer FlagStringFunc = stringifyFlag |
|
||||
|
|
||||
// FlagNamePrefixer converts a full flag name and its placeholder into the help
|
|
||||
// message flag prefix. This is used by the default FlagStringer.
|
|
||||
var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames |
|
||||
|
|
||||
// FlagEnvHinter annotates flag help message with the environment variable
|
|
||||
// details. This is used by the default FlagStringer.
|
|
||||
var FlagEnvHinter FlagEnvHintFunc = withEnvHint |
|
||||
|
|
||||
// FlagFileHinter annotates flag help message with the environment variable
|
|
||||
// details. This is used by the default FlagStringer.
|
|
||||
var FlagFileHinter FlagFileHintFunc = withFileHint |
|
||||
|
|
||||
// FlagsByName is a slice of Flag.
|
|
||||
type FlagsByName []Flag |
|
||||
|
|
||||
func (f FlagsByName) Len() int { |
|
||||
return len(f) |
|
||||
} |
|
||||
|
|
||||
func (f FlagsByName) Less(i, j int) bool { |
|
||||
return lexicographicLess(f[i].GetName(), f[j].GetName()) |
|
||||
} |
|
||||
|
|
||||
func (f FlagsByName) Swap(i, j int) { |
|
||||
f[i], f[j] = f[j], f[i] |
|
||||
} |
|
||||
|
|
||||
// Flag is a common interface related to parsing flags in cli.
|
|
||||
// For more advanced flag parsing techniques, it is recommended that
|
|
||||
// this interface be implemented.
|
|
||||
type Flag interface { |
|
||||
fmt.Stringer |
|
||||
// Apply Flag settings to the given flag set
|
|
||||
Apply(*flag.FlagSet) |
|
||||
GetName() string |
|
||||
} |
|
||||
|
|
||||
// errorableFlag is an interface that allows us to return errors during apply
|
|
||||
// it allows flags defined in this library to return errors in a fashion backwards compatible
|
|
||||
// TODO remove in v2 and modify the existing Flag interface to return errors
|
|
||||
type errorableFlag interface { |
|
||||
Flag |
|
||||
|
|
||||
ApplyWithError(*flag.FlagSet) error |
|
||||
} |
|
||||
|
|
||||
func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { |
|
||||
set := flag.NewFlagSet(name, flag.ContinueOnError) |
|
||||
|
|
||||
for _, f := range flags { |
|
||||
//TODO remove in v2 when errorableFlag is removed
|
|
||||
if ef, ok := f.(errorableFlag); ok { |
|
||||
if err := ef.ApplyWithError(set); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
} else { |
|
||||
f.Apply(set) |
|
||||
} |
|
||||
} |
|
||||
return set, nil |
|
||||
} |
|
||||
|
|
||||
func eachName(longName string, fn func(string)) { |
|
||||
parts := strings.Split(longName, ",") |
|
||||
for _, name := range parts { |
|
||||
name = strings.Trim(name, " ") |
|
||||
fn(name) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Generic is a generic parseable type identified by a specific flag
|
|
||||
type Generic interface { |
|
||||
Set(value string) error |
|
||||
String() string |
|
||||
} |
|
||||
|
|
||||
// Apply takes the flagset and calls Set on the generic flag with the value
|
|
||||
// provided by the user for parsing by the flag
|
|
||||
// Ignores parsing errors
|
|
||||
func (f GenericFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError takes the flagset and calls Set on the generic flag with the value
|
|
||||
// provided by the user for parsing by the flag
|
|
||||
func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
val := f.Value |
|
||||
if fileEnvVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
if err := val.Set(fileEnvVal); err != nil { |
|
||||
return fmt.Errorf("could not parse %s as value for flag %s: %s", fileEnvVal, f.Name, err) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
set.Var(f.Value, name, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter
|
|
||||
type StringSlice []string |
|
||||
|
|
||||
// Set appends the string value to the list of values
|
|
||||
func (f *StringSlice) Set(value string) error { |
|
||||
*f = append(*f, value) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||
func (f *StringSlice) String() string { |
|
||||
return fmt.Sprintf("%s", *f) |
|
||||
} |
|
||||
|
|
||||
// Value returns the slice of strings set by this flag
|
|
||||
func (f *StringSlice) Value() []string { |
|
||||
return *f |
|
||||
} |
|
||||
|
|
||||
// Get returns the slice of strings set by this flag
|
|
||||
func (f *StringSlice) Get() interface{} { |
|
||||
return *f |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f StringSliceFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
newVal := &StringSlice{} |
|
||||
for _, s := range strings.Split(envVal, ",") { |
|
||||
s = strings.TrimSpace(s) |
|
||||
if err := newVal.Set(s); err != nil { |
|
||||
return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
} |
|
||||
if f.Value == nil { |
|
||||
f.Value = newVal |
|
||||
} else { |
|
||||
*f.Value = *newVal |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Value == nil { |
|
||||
f.Value = &StringSlice{} |
|
||||
} |
|
||||
set.Var(f.Value, name, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||
type IntSlice []int |
|
||||
|
|
||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||
func (f *IntSlice) Set(value string) error { |
|
||||
tmp, err := strconv.Atoi(value) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
*f = append(*f, tmp) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||
func (f *IntSlice) String() string { |
|
||||
return fmt.Sprintf("%#v", *f) |
|
||||
} |
|
||||
|
|
||||
// Value returns the slice of ints set by this flag
|
|
||||
func (f *IntSlice) Value() []int { |
|
||||
return *f |
|
||||
} |
|
||||
|
|
||||
// Get returns the slice of ints set by this flag
|
|
||||
func (f *IntSlice) Get() interface{} { |
|
||||
return *f |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f IntSliceFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
newVal := &IntSlice{} |
|
||||
for _, s := range strings.Split(envVal, ",") { |
|
||||
s = strings.TrimSpace(s) |
|
||||
if err := newVal.Set(s); err != nil { |
|
||||
return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
} |
|
||||
if f.Value == nil { |
|
||||
f.Value = newVal |
|
||||
} else { |
|
||||
*f.Value = *newVal |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Value == nil { |
|
||||
f.Value = &IntSlice{} |
|
||||
} |
|
||||
set.Var(f.Value, name, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter
|
|
||||
type Int64Slice []int64 |
|
||||
|
|
||||
// Set parses the value into an integer and appends it to the list of values
|
|
||||
func (f *Int64Slice) Set(value string) error { |
|
||||
tmp, err := strconv.ParseInt(value, 10, 64) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
*f = append(*f, tmp) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value (for usage defaults)
|
|
||||
func (f *Int64Slice) String() string { |
|
||||
return fmt.Sprintf("%#v", *f) |
|
||||
} |
|
||||
|
|
||||
// Value returns the slice of ints set by this flag
|
|
||||
func (f *Int64Slice) Value() []int64 { |
|
||||
return *f |
|
||||
} |
|
||||
|
|
||||
// Get returns the slice of ints set by this flag
|
|
||||
func (f *Int64Slice) Get() interface{} { |
|
||||
return *f |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f Int64SliceFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
newVal := &Int64Slice{} |
|
||||
for _, s := range strings.Split(envVal, ",") { |
|
||||
s = strings.TrimSpace(s) |
|
||||
if err := newVal.Set(s); err != nil { |
|
||||
return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
} |
|
||||
if f.Value == nil { |
|
||||
f.Value = newVal |
|
||||
} else { |
|
||||
*f.Value = *newVal |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Value == nil { |
|
||||
f.Value = &Int64Slice{} |
|
||||
} |
|
||||
set.Var(f.Value, name, f.Usage) |
|
||||
}) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f BoolFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
val := false |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
if envVal == "" { |
|
||||
val = false |
|
||||
} else { |
|
||||
envValBool, err := strconv.ParseBool(envVal) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
val = envValBool |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.BoolVar(f.Destination, name, val, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Bool(name, val, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f BoolTFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
val := true |
|
||||
|
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
if envVal == "" { |
|
||||
val = false |
|
||||
} else { |
|
||||
envValBool, err := strconv.ParseBool(envVal) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
val = envValBool |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.BoolVar(f.Destination, name, val, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Bool(name, val, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f StringFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
f.Value = envVal |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.StringVar(f.Destination, name, f.Value, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.String(name, f.Value, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f IntFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
f.Value = int(envValInt) |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.IntVar(f.Destination, name, f.Value, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Int(name, f.Value, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f Int64Flag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
envValInt, err := strconv.ParseInt(envVal, 0, 64) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
|
|
||||
f.Value = envValInt |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.Int64Var(f.Destination, name, f.Value, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Int64(name, f.Value, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f UintFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
|
|
||||
f.Value = uint(envValInt) |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.UintVar(f.Destination, name, f.Value, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Uint(name, f.Value, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f Uint64Flag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
envValInt, err := strconv.ParseUint(envVal, 0, 64) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
|
|
||||
f.Value = uint64(envValInt) |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.Uint64Var(f.Destination, name, f.Value, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Uint64(name, f.Value, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f DurationFlag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
envValDuration, err := time.ParseDuration(envVal) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
|
|
||||
f.Value = envValDuration |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.DurationVar(f.Destination, name, f.Value, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Duration(name, f.Value, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Apply populates the flag given the flag set and environment
|
|
||||
// Ignores errors
|
|
||||
func (f Float64Flag) Apply(set *flag.FlagSet) { |
|
||||
f.ApplyWithError(set) |
|
||||
} |
|
||||
|
|
||||
// ApplyWithError populates the flag given the flag set and environment
|
|
||||
func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { |
|
||||
if envVal, ok := flagFromFileEnv(f.FilePath, f.EnvVar); ok { |
|
||||
envValFloat, err := strconv.ParseFloat(envVal, 10) |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) |
|
||||
} |
|
||||
|
|
||||
f.Value = float64(envValFloat) |
|
||||
} |
|
||||
|
|
||||
eachName(f.Name, func(name string) { |
|
||||
if f.Destination != nil { |
|
||||
set.Float64Var(f.Destination, name, f.Value, f.Usage) |
|
||||
return |
|
||||
} |
|
||||
set.Float64(name, f.Value, f.Usage) |
|
||||
}) |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func visibleFlags(fl []Flag) []Flag { |
|
||||
visible := []Flag{} |
|
||||
for _, flag := range fl { |
|
||||
field := flagValue(flag).FieldByName("Hidden") |
|
||||
if !field.IsValid() || !field.Bool() { |
|
||||
visible = append(visible, flag) |
|
||||
} |
|
||||
} |
|
||||
return visible |
|
||||
} |
|
||||
|
|
||||
func prefixFor(name string) (prefix string) { |
|
||||
if len(name) == 1 { |
|
||||
prefix = "-" |
|
||||
} else { |
|
||||
prefix = "--" |
|
||||
} |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Returns the placeholder, if any, and the unquoted usage string.
|
|
||||
func unquoteUsage(usage string) (string, string) { |
|
||||
for i := 0; i < len(usage); i++ { |
|
||||
if usage[i] == '`' { |
|
||||
for j := i + 1; j < len(usage); j++ { |
|
||||
if usage[j] == '`' { |
|
||||
name := usage[i+1 : j] |
|
||||
usage = usage[:i] + name + usage[j+1:] |
|
||||
return name, usage |
|
||||
} |
|
||||
} |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
return "", usage |
|
||||
} |
|
||||
|
|
||||
func prefixedNames(fullName, placeholder string) string { |
|
||||
var prefixed string |
|
||||
parts := strings.Split(fullName, ",") |
|
||||
for i, name := range parts { |
|
||||
name = strings.Trim(name, " ") |
|
||||
prefixed += prefixFor(name) + name |
|
||||
if placeholder != "" { |
|
||||
prefixed += " " + placeholder |
|
||||
} |
|
||||
if i < len(parts)-1 { |
|
||||
prefixed += ", " |
|
||||
} |
|
||||
} |
|
||||
return prefixed |
|
||||
} |
|
||||
|
|
||||
func withEnvHint(envVar, str string) string { |
|
||||
envText := "" |
|
||||
if envVar != "" { |
|
||||
prefix := "$" |
|
||||
suffix := "" |
|
||||
sep := ", $" |
|
||||
if runtime.GOOS == "windows" { |
|
||||
prefix = "%" |
|
||||
suffix = "%" |
|
||||
sep = "%, %" |
|
||||
} |
|
||||
envText = " [" + prefix + strings.Join(strings.Split(envVar, ","), sep) + suffix + "]" |
|
||||
} |
|
||||
return str + envText |
|
||||
} |
|
||||
|
|
||||
func withFileHint(filePath, str string) string { |
|
||||
fileText := "" |
|
||||
if filePath != "" { |
|
||||
fileText = fmt.Sprintf(" [%s]", filePath) |
|
||||
} |
|
||||
return str + fileText |
|
||||
} |
|
||||
|
|
||||
func flagValue(f Flag) reflect.Value { |
|
||||
fv := reflect.ValueOf(f) |
|
||||
for fv.Kind() == reflect.Ptr { |
|
||||
fv = reflect.Indirect(fv) |
|
||||
} |
|
||||
return fv |
|
||||
} |
|
||||
|
|
||||
func stringifyFlag(f Flag) string { |
|
||||
fv := flagValue(f) |
|
||||
|
|
||||
switch f.(type) { |
|
||||
case IntSliceFlag: |
|
||||
return FlagFileHinter( |
|
||||
fv.FieldByName("FilePath").String(), |
|
||||
FlagEnvHinter( |
|
||||
fv.FieldByName("EnvVar").String(), |
|
||||
stringifyIntSliceFlag(f.(IntSliceFlag)), |
|
||||
), |
|
||||
) |
|
||||
case Int64SliceFlag: |
|
||||
return FlagFileHinter( |
|
||||
fv.FieldByName("FilePath").String(), |
|
||||
FlagEnvHinter( |
|
||||
fv.FieldByName("EnvVar").String(), |
|
||||
stringifyInt64SliceFlag(f.(Int64SliceFlag)), |
|
||||
), |
|
||||
) |
|
||||
case StringSliceFlag: |
|
||||
return FlagFileHinter( |
|
||||
fv.FieldByName("FilePath").String(), |
|
||||
FlagEnvHinter( |
|
||||
fv.FieldByName("EnvVar").String(), |
|
||||
stringifyStringSliceFlag(f.(StringSliceFlag)), |
|
||||
), |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) |
|
||||
|
|
||||
needsPlaceholder := false |
|
||||
defaultValueString := "" |
|
||||
|
|
||||
if val := fv.FieldByName("Value"); val.IsValid() { |
|
||||
needsPlaceholder = true |
|
||||
defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) |
|
||||
|
|
||||
if val.Kind() == reflect.String && val.String() != "" { |
|
||||
defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if defaultValueString == " (default: )" { |
|
||||
defaultValueString = "" |
|
||||
} |
|
||||
|
|
||||
if needsPlaceholder && placeholder == "" { |
|
||||
placeholder = defaultPlaceholder |
|
||||
} |
|
||||
|
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultValueString) |
|
||||
|
|
||||
return FlagFileHinter( |
|
||||
fv.FieldByName("FilePath").String(), |
|
||||
FlagEnvHinter( |
|
||||
fv.FieldByName("EnvVar").String(), |
|
||||
FlagNamePrefixer(fv.FieldByName("Name").String(), placeholder)+"\t"+usageWithDefault, |
|
||||
), |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
func stringifyIntSliceFlag(f IntSliceFlag) string { |
|
||||
defaultVals := []string{} |
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 { |
|
||||
for _, i := range f.Value.Value() { |
|
||||
defaultVals = append(defaultVals, strconv.Itoa(i)) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals) |
|
||||
} |
|
||||
|
|
||||
func stringifyInt64SliceFlag(f Int64SliceFlag) string { |
|
||||
defaultVals := []string{} |
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 { |
|
||||
for _, i := range f.Value.Value() { |
|
||||
defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals) |
|
||||
} |
|
||||
|
|
||||
func stringifyStringSliceFlag(f StringSliceFlag) string { |
|
||||
defaultVals := []string{} |
|
||||
if f.Value != nil && len(f.Value.Value()) > 0 { |
|
||||
for _, s := range f.Value.Value() { |
|
||||
if len(s) > 0 { |
|
||||
defaultVals = append(defaultVals, strconv.Quote(s)) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return stringifySliceFlag(f.Usage, f.Name, defaultVals) |
|
||||
} |
|
||||
|
|
||||
func stringifySliceFlag(usage, name string, defaultVals []string) string { |
|
||||
placeholder, usage := unquoteUsage(usage) |
|
||||
if placeholder == "" { |
|
||||
placeholder = defaultPlaceholder |
|
||||
} |
|
||||
|
|
||||
defaultVal := "" |
|
||||
if len(defaultVals) > 0 { |
|
||||
defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) |
|
||||
} |
|
||||
|
|
||||
usageWithDefault := strings.TrimSpace(usage + defaultVal) |
|
||||
return FlagNamePrefixer(name, placeholder) + "\t" + usageWithDefault |
|
||||
} |
|
||||
|
|
||||
func flagFromFileEnv(filePath, envName string) (val string, ok bool) { |
|
||||
for _, envVar := range strings.Split(envName, ",") { |
|
||||
envVar = strings.TrimSpace(envVar) |
|
||||
if envVal, ok := syscall.Getenv(envVar); ok { |
|
||||
return envVal, true |
|
||||
} |
|
||||
} |
|
||||
for _, fileVar := range strings.Split(filePath, ",") { |
|
||||
if data, err := ioutil.ReadFile(fileVar); err == nil { |
|
||||
return string(data), true |
|
||||
} |
|
||||
} |
|
||||
return "", false |
|
||||
} |
|
@ -1,640 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import ( |
|
||||
"flag" |
|
||||
"strconv" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
// WARNING: This file is generated!
|
|
||||
|
|
||||
// BoolFlag is a flag with type bool
|
|
||||
type BoolFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Destination *bool |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f BoolFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f BoolFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Bool looks up the value of a local BoolFlag, returns
|
|
||||
// false if not found
|
|
||||
func (c *Context) Bool(name string) bool { |
|
||||
return lookupBool(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalBool looks up the value of a global BoolFlag, returns
|
|
||||
// false if not found
|
|
||||
func (c *Context) GlobalBool(name string) bool { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupBool(name, fs) |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func lookupBool(name string, set *flag.FlagSet) bool { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := strconv.ParseBool(f.Value.String()) |
|
||||
if err != nil { |
|
||||
return false |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
// BoolTFlag is a flag with type bool that is true by default
|
|
||||
type BoolTFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Destination *bool |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f BoolTFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f BoolTFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// BoolT looks up the value of a local BoolTFlag, returns
|
|
||||
// false if not found
|
|
||||
func (c *Context) BoolT(name string) bool { |
|
||||
return lookupBoolT(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalBoolT looks up the value of a global BoolTFlag, returns
|
|
||||
// false if not found
|
|
||||
func (c *Context) GlobalBoolT(name string) bool { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupBoolT(name, fs) |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func lookupBoolT(name string, set *flag.FlagSet) bool { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := strconv.ParseBool(f.Value.String()) |
|
||||
if err != nil { |
|
||||
return false |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration)
|
|
||||
type DurationFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value time.Duration |
|
||||
Destination *time.Duration |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f DurationFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f DurationFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Duration looks up the value of a local DurationFlag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) Duration(name string) time.Duration { |
|
||||
return lookupDuration(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalDuration looks up the value of a global DurationFlag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) GlobalDuration(name string) time.Duration { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupDuration(name, fs) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
func lookupDuration(name string, set *flag.FlagSet) time.Duration { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := time.ParseDuration(f.Value.String()) |
|
||||
if err != nil { |
|
||||
return 0 |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
// Float64Flag is a flag with type float64
|
|
||||
type Float64Flag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value float64 |
|
||||
Destination *float64 |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f Float64Flag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f Float64Flag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Float64 looks up the value of a local Float64Flag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) Float64(name string) float64 { |
|
||||
return lookupFloat64(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalFloat64 looks up the value of a global Float64Flag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) GlobalFloat64(name string) float64 { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupFloat64(name, fs) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
func lookupFloat64(name string, set *flag.FlagSet) float64 { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := strconv.ParseFloat(f.Value.String(), 64) |
|
||||
if err != nil { |
|
||||
return 0 |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
// GenericFlag is a flag with type Generic
|
|
||||
type GenericFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value Generic |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f GenericFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f GenericFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Generic looks up the value of a local GenericFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) Generic(name string) interface{} { |
|
||||
return lookupGeneric(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalGeneric looks up the value of a global GenericFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) GlobalGeneric(name string) interface{} { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupGeneric(name, fs) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func lookupGeneric(name string, set *flag.FlagSet) interface{} { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := f.Value, error(nil) |
|
||||
if err != nil { |
|
||||
return nil |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Int64Flag is a flag with type int64
|
|
||||
type Int64Flag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value int64 |
|
||||
Destination *int64 |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f Int64Flag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f Int64Flag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Int64 looks up the value of a local Int64Flag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) Int64(name string) int64 { |
|
||||
return lookupInt64(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalInt64 looks up the value of a global Int64Flag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) GlobalInt64(name string) int64 { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupInt64(name, fs) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
func lookupInt64(name string, set *flag.FlagSet) int64 { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) |
|
||||
if err != nil { |
|
||||
return 0 |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
// IntFlag is a flag with type int
|
|
||||
type IntFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value int |
|
||||
Destination *int |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f IntFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f IntFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Int looks up the value of a local IntFlag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) Int(name string) int { |
|
||||
return lookupInt(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalInt looks up the value of a global IntFlag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) GlobalInt(name string) int { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupInt(name, fs) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
func lookupInt(name string, set *flag.FlagSet) int { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) |
|
||||
if err != nil { |
|
||||
return 0 |
|
||||
} |
|
||||
return int(parsed) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
// IntSliceFlag is a flag with type *IntSlice
|
|
||||
type IntSliceFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value *IntSlice |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f IntSliceFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f IntSliceFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// IntSlice looks up the value of a local IntSliceFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) IntSlice(name string) []int { |
|
||||
return lookupIntSlice(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalIntSlice looks up the value of a global IntSliceFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) GlobalIntSlice(name string) []int { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupIntSlice(name, fs) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func lookupIntSlice(name string, set *flag.FlagSet) []int { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) |
|
||||
if err != nil { |
|
||||
return nil |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Int64SliceFlag is a flag with type *Int64Slice
|
|
||||
type Int64SliceFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value *Int64Slice |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f Int64SliceFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f Int64SliceFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Int64Slice looks up the value of a local Int64SliceFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) Int64Slice(name string) []int64 { |
|
||||
return lookupInt64Slice(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) GlobalInt64Slice(name string) []int64 { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupInt64Slice(name, fs) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) |
|
||||
if err != nil { |
|
||||
return nil |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// StringFlag is a flag with type string
|
|
||||
type StringFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value string |
|
||||
Destination *string |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f StringFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f StringFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// String looks up the value of a local StringFlag, returns
|
|
||||
// "" if not found
|
|
||||
func (c *Context) String(name string) string { |
|
||||
return lookupString(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalString looks up the value of a global StringFlag, returns
|
|
||||
// "" if not found
|
|
||||
func (c *Context) GlobalString(name string) string { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupString(name, fs) |
|
||||
} |
|
||||
return "" |
|
||||
} |
|
||||
|
|
||||
func lookupString(name string, set *flag.FlagSet) string { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := f.Value.String(), error(nil) |
|
||||
if err != nil { |
|
||||
return "" |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return "" |
|
||||
} |
|
||||
|
|
||||
// StringSliceFlag is a flag with type *StringSlice
|
|
||||
type StringSliceFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value *StringSlice |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f StringSliceFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f StringSliceFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// StringSlice looks up the value of a local StringSliceFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) StringSlice(name string) []string { |
|
||||
return lookupStringSlice(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalStringSlice looks up the value of a global StringSliceFlag, returns
|
|
||||
// nil if not found
|
|
||||
func (c *Context) GlobalStringSlice(name string) []string { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupStringSlice(name, fs) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func lookupStringSlice(name string, set *flag.FlagSet) []string { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) |
|
||||
if err != nil { |
|
||||
return nil |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Uint64Flag is a flag with type uint64
|
|
||||
type Uint64Flag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value uint64 |
|
||||
Destination *uint64 |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f Uint64Flag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f Uint64Flag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Uint64 looks up the value of a local Uint64Flag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) Uint64(name string) uint64 { |
|
||||
return lookupUint64(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalUint64 looks up the value of a global Uint64Flag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) GlobalUint64(name string) uint64 { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupUint64(name, fs) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
func lookupUint64(name string, set *flag.FlagSet) uint64 { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) |
|
||||
if err != nil { |
|
||||
return 0 |
|
||||
} |
|
||||
return parsed |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
// UintFlag is a flag with type uint
|
|
||||
type UintFlag struct { |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
Value uint |
|
||||
Destination *uint |
|
||||
} |
|
||||
|
|
||||
// String returns a readable representation of this value
|
|
||||
// (for usage defaults)
|
|
||||
func (f UintFlag) String() string { |
|
||||
return FlagStringer(f) |
|
||||
} |
|
||||
|
|
||||
// GetName returns the name of the flag
|
|
||||
func (f UintFlag) GetName() string { |
|
||||
return f.Name |
|
||||
} |
|
||||
|
|
||||
// Uint looks up the value of a local UintFlag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) Uint(name string) uint { |
|
||||
return lookupUint(name, c.flagSet) |
|
||||
} |
|
||||
|
|
||||
// GlobalUint looks up the value of a global UintFlag, returns
|
|
||||
// 0 if not found
|
|
||||
func (c *Context) GlobalUint(name string) uint { |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil { |
|
||||
return lookupUint(name, fs) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
func lookupUint(name string, set *flag.FlagSet) uint { |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil { |
|
||||
parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) |
|
||||
if err != nil { |
|
||||
return 0 |
|
||||
} |
|
||||
return uint(parsed) |
|
||||
} |
|
||||
return 0 |
|
||||
} |
|
@ -1,44 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
// BashCompleteFunc is an action to execute when the bash-completion flag is set
|
|
||||
type BashCompleteFunc func(*Context) |
|
||||
|
|
||||
// BeforeFunc is an action to execute before any subcommands are run, but after
|
|
||||
// the context is ready if a non-nil error is returned, no subcommands are run
|
|
||||
type BeforeFunc func(*Context) error |
|
||||
|
|
||||
// AfterFunc is an action to execute after any subcommands are run, but after the
|
|
||||
// subcommand has finished it is run even if Action() panics
|
|
||||
type AfterFunc func(*Context) error |
|
||||
|
|
||||
// ActionFunc is the action to execute when no subcommands are specified
|
|
||||
type ActionFunc func(*Context) error |
|
||||
|
|
||||
// CommandNotFoundFunc is executed if the proper command cannot be found
|
|
||||
type CommandNotFoundFunc func(*Context, string) |
|
||||
|
|
||||
// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying
|
|
||||
// customized usage error messages. This function is able to replace the
|
|
||||
// original error messages. If this function is not set, the "Incorrect usage"
|
|
||||
// is displayed and the execution is interrupted.
|
|
||||
type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error |
|
||||
|
|
||||
// ExitErrHandlerFunc is executed if provided in order to handle ExitError values
|
|
||||
// returned by Actions and Before/After functions.
|
|
||||
type ExitErrHandlerFunc func(context *Context, err error) |
|
||||
|
|
||||
// FlagStringFunc is used by the help generation to display a flag, which is
|
|
||||
// expected to be a single line.
|
|
||||
type FlagStringFunc func(Flag) string |
|
||||
|
|
||||
// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix
|
|
||||
// text for a flag's full name.
|
|
||||
type FlagNamePrefixFunc func(fullName, placeholder string) string |
|
||||
|
|
||||
// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help
|
|
||||
// with the environment variable details.
|
|
||||
type FlagEnvHintFunc func(envVar, str string) string |
|
||||
|
|
||||
// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help
|
|
||||
// with the file path details.
|
|
||||
type FlagFileHintFunc func(filePath, str string) string |
|
@ -1,256 +0,0 @@ |
|||||
#!/usr/bin/env python |
|
||||
""" |
|
||||
The flag types that ship with the cli library have many things in common, and |
|
||||
so we can take advantage of the `go generate` command to create much of the |
|
||||
source code from a list of definitions. These definitions attempt to cover |
|
||||
the parts that vary between flag types, and should evolve as needed. |
|
||||
|
|
||||
An example of the minimum definition needed is: |
|
||||
|
|
||||
{ |
|
||||
"name": "SomeType", |
|
||||
"type": "sometype", |
|
||||
"context_default": "nil" |
|
||||
} |
|
||||
|
|
||||
In this example, the code generated for the `cli` package will include a type |
|
||||
named `SomeTypeFlag` that is expected to wrap a value of type `sometype`. |
|
||||
Fetching values by name via `*cli.Context` will default to a value of `nil`. |
|
||||
|
|
||||
A more complete, albeit somewhat redundant, example showing all available |
|
||||
definition keys is: |
|
||||
|
|
||||
{ |
|
||||
"name": "VeryMuchType", |
|
||||
"type": "*VeryMuchType", |
|
||||
"value": true, |
|
||||
"dest": false, |
|
||||
"doctail": " which really only wraps a []float64, oh well!", |
|
||||
"context_type": "[]float64", |
|
||||
"context_default": "nil", |
|
||||
"parser": "parseVeryMuchType(f.Value.String())", |
|
||||
"parser_cast": "[]float64(parsed)" |
|
||||
} |
|
||||
|
|
||||
The meaning of each field is as follows: |
|
||||
|
|
||||
name (string) - The type "name", which will be suffixed with |
|
||||
`Flag` when generating the type definition |
|
||||
for `cli` and the wrapper type for `altsrc` |
|
||||
type (string) - The type that the generated `Flag` type for `cli` |
|
||||
is expected to "contain" as its `.Value` member |
|
||||
value (bool) - Should the generated `cli` type have a `Value` |
|
||||
member? |
|
||||
dest (bool) - Should the generated `cli` type support a |
|
||||
destination pointer? |
|
||||
doctail (string) - Additional docs for the `cli` flag type comment |
|
||||
context_type (string) - The literal type used in the `*cli.Context` |
|
||||
reader func signature |
|
||||
context_default (string) - The literal value used as the default by the |
|
||||
`*cli.Context` reader funcs when no value is |
|
||||
present |
|
||||
parser (string) - Literal code used to parse the flag `f`, |
|
||||
expected to have a return signature of |
|
||||
(value, error) |
|
||||
parser_cast (string) - Literal code used to cast the `parsed` value |
|
||||
returned from the `parser` code |
|
||||
""" |
|
||||
|
|
||||
from __future__ import print_function, unicode_literals |
|
||||
|
|
||||
import argparse |
|
||||
import json |
|
||||
import os |
|
||||
import subprocess |
|
||||
import sys |
|
||||
import tempfile |
|
||||
import textwrap |
|
||||
|
|
||||
|
|
||||
class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter, |
|
||||
argparse.RawDescriptionHelpFormatter): |
|
||||
pass |
|
||||
|
|
||||
|
|
||||
def main(sysargs=sys.argv[:]): |
|
||||
parser = argparse.ArgumentParser( |
|
||||
description='Generate flag type code!', |
|
||||
formatter_class=_FancyFormatter) |
|
||||
parser.add_argument( |
|
||||
'package', |
|
||||
type=str, default='cli', choices=_WRITEFUNCS.keys(), |
|
||||
help='Package for which flag types will be generated' |
|
||||
) |
|
||||
parser.add_argument( |
|
||||
'-i', '--in-json', |
|
||||
type=argparse.FileType('r'), |
|
||||
default=sys.stdin, |
|
||||
help='Input JSON file which defines each type to be generated' |
|
||||
) |
|
||||
parser.add_argument( |
|
||||
'-o', '--out-go', |
|
||||
type=argparse.FileType('w'), |
|
||||
default=sys.stdout, |
|
||||
help='Output file/stream to which generated source will be written' |
|
||||
) |
|
||||
parser.epilog = __doc__ |
|
||||
|
|
||||
args = parser.parse_args(sysargs[1:]) |
|
||||
_generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json) |
|
||||
return 0 |
|
||||
|
|
||||
|
|
||||
def _generate_flag_types(writefunc, output_go, input_json): |
|
||||
types = json.load(input_json) |
|
||||
|
|
||||
tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False) |
|
||||
writefunc(tmp, types) |
|
||||
tmp.close() |
|
||||
|
|
||||
new_content = subprocess.check_output( |
|
||||
['goimports', tmp.name] |
|
||||
).decode('utf-8') |
|
||||
|
|
||||
print(new_content, file=output_go, end='') |
|
||||
output_go.flush() |
|
||||
os.remove(tmp.name) |
|
||||
|
|
||||
|
|
||||
def _set_typedef_defaults(typedef): |
|
||||
typedef.setdefault('doctail', '') |
|
||||
typedef.setdefault('context_type', typedef['type']) |
|
||||
typedef.setdefault('dest', True) |
|
||||
typedef.setdefault('value', True) |
|
||||
typedef.setdefault('parser', 'f.Value, error(nil)') |
|
||||
typedef.setdefault('parser_cast', 'parsed') |
|
||||
|
|
||||
|
|
||||
def _write_cli_flag_types(outfile, types): |
|
||||
_fwrite(outfile, """\ |
|
||||
package cli |
|
||||
|
|
||||
// WARNING: This file is generated! |
|
||||
|
|
||||
""") |
|
||||
|
|
||||
for typedef in types: |
|
||||
_set_typedef_defaults(typedef) |
|
||||
|
|
||||
_fwrite(outfile, """\ |
|
||||
// {name}Flag is a flag with type {type}{doctail} |
|
||||
type {name}Flag struct {{ |
|
||||
Name string |
|
||||
Usage string |
|
||||
EnvVar string |
|
||||
FilePath string |
|
||||
Hidden bool |
|
||||
""".format(**typedef)) |
|
||||
|
|
||||
if typedef['value']: |
|
||||
_fwrite(outfile, """\ |
|
||||
Value {type} |
|
||||
""".format(**typedef)) |
|
||||
|
|
||||
if typedef['dest']: |
|
||||
_fwrite(outfile, """\ |
|
||||
Destination *{type} |
|
||||
""".format(**typedef)) |
|
||||
|
|
||||
_fwrite(outfile, "\n}\n\n") |
|
||||
|
|
||||
_fwrite(outfile, """\ |
|
||||
// String returns a readable representation of this value |
|
||||
// (for usage defaults) |
|
||||
func (f {name}Flag) String() string {{ |
|
||||
return FlagStringer(f) |
|
||||
}} |
|
||||
|
|
||||
// GetName returns the name of the flag |
|
||||
func (f {name}Flag) GetName() string {{ |
|
||||
return f.Name |
|
||||
}} |
|
||||
|
|
||||
// {name} looks up the value of a local {name}Flag, returns |
|
||||
// {context_default} if not found |
|
||||
func (c *Context) {name}(name string) {context_type} {{ |
|
||||
return lookup{name}(name, c.flagSet) |
|
||||
}} |
|
||||
|
|
||||
// Global{name} looks up the value of a global {name}Flag, returns |
|
||||
// {context_default} if not found |
|
||||
func (c *Context) Global{name}(name string) {context_type} {{ |
|
||||
if fs := lookupGlobalFlagSet(name, c); fs != nil {{ |
|
||||
return lookup{name}(name, fs) |
|
||||
}} |
|
||||
return {context_default} |
|
||||
}} |
|
||||
|
|
||||
func lookup{name}(name string, set *flag.FlagSet) {context_type} {{ |
|
||||
f := set.Lookup(name) |
|
||||
if f != nil {{ |
|
||||
parsed, err := {parser} |
|
||||
if err != nil {{ |
|
||||
return {context_default} |
|
||||
}} |
|
||||
return {parser_cast} |
|
||||
}} |
|
||||
return {context_default} |
|
||||
}} |
|
||||
""".format(**typedef)) |
|
||||
|
|
||||
|
|
||||
def _write_altsrc_flag_types(outfile, types): |
|
||||
_fwrite(outfile, """\ |
|
||||
package altsrc |
|
||||
|
|
||||
import ( |
|
||||
"gopkg.in/urfave/cli.v1" |
|
||||
) |
|
||||
|
|
||||
// WARNING: This file is generated! |
|
||||
|
|
||||
""") |
|
||||
|
|
||||
for typedef in types: |
|
||||
_set_typedef_defaults(typedef) |
|
||||
|
|
||||
_fwrite(outfile, """\ |
|
||||
// {name}Flag is the flag type that wraps cli.{name}Flag to allow |
|
||||
// for other values to be specified |
|
||||
type {name}Flag struct {{ |
|
||||
cli.{name}Flag |
|
||||
set *flag.FlagSet |
|
||||
}} |
|
||||
|
|
||||
// New{name}Flag creates a new {name}Flag |
|
||||
func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{ |
|
||||
return &{name}Flag{{{name}Flag: fl, set: nil}} |
|
||||
}} |
|
||||
|
|
||||
// Apply saves the flagSet for later usage calls, then calls the |
|
||||
// wrapped {name}Flag.Apply |
|
||||
func (f *{name}Flag) Apply(set *flag.FlagSet) {{ |
|
||||
f.set = set |
|
||||
f.{name}Flag.Apply(set) |
|
||||
}} |
|
||||
|
|
||||
// ApplyWithError saves the flagSet for later usage calls, then calls the |
|
||||
// wrapped {name}Flag.ApplyWithError |
|
||||
func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{ |
|
||||
f.set = set |
|
||||
return f.{name}Flag.ApplyWithError(set) |
|
||||
}} |
|
||||
""".format(**typedef)) |
|
||||
|
|
||||
|
|
||||
def _fwrite(outfile, text): |
|
||||
print(textwrap.dedent(text), end='', file=outfile) |
|
||||
|
|
||||
|
|
||||
_WRITEFUNCS = { |
|
||||
'cli': _write_cli_flag_types, |
|
||||
'altsrc': _write_altsrc_flag_types |
|
||||
} |
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
sys.exit(main()) |
|
@ -1,345 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"io" |
|
||||
"os" |
|
||||
"strings" |
|
||||
"text/tabwriter" |
|
||||
"text/template" |
|
||||
) |
|
||||
|
|
||||
// AppHelpTemplate is the text template for the Default help topic.
|
|
||||
// cli.go uses text/template to render templates. You can
|
|
||||
// render custom help text by setting this variable.
|
|
||||
var AppHelpTemplate = `NAME: |
|
||||
{{.Name}}{{if .Usage}} - {{.Usage}}{{end}} |
|
||||
|
|
||||
USAGE: |
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} |
|
||||
|
|
||||
VERSION: |
|
||||
{{.Version}}{{end}}{{end}}{{if .Description}} |
|
||||
|
|
||||
DESCRIPTION: |
|
||||
{{.Description}}{{end}}{{if len .Authors}} |
|
||||
|
|
||||
AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: |
|
||||
{{range $index, $author := .Authors}}{{if $index}} |
|
||||
{{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} |
|
||||
|
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}} |
|
||||
|
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}} |
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} |
|
||||
|
|
||||
GLOBAL OPTIONS: |
|
||||
{{range $index, $option := .VisibleFlags}}{{if $index}} |
|
||||
{{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} |
|
||||
|
|
||||
COPYRIGHT: |
|
||||
{{.Copyright}}{{end}} |
|
||||
` |
|
||||
|
|
||||
// CommandHelpTemplate is the text template for the command help topic.
|
|
||||
// cli.go uses text/template to render templates. You can
|
|
||||
// render custom help text by setting this variable.
|
|
||||
var CommandHelpTemplate = `NAME: |
|
||||
{{.HelpName}} - {{.Usage}} |
|
||||
|
|
||||
USAGE: |
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} |
|
||||
|
|
||||
CATEGORY: |
|
||||
{{.Category}}{{end}}{{if .Description}} |
|
||||
|
|
||||
DESCRIPTION: |
|
||||
{{.Description}}{{end}}{{if .VisibleFlags}} |
|
||||
|
|
||||
OPTIONS: |
|
||||
{{range .VisibleFlags}}{{.}} |
|
||||
{{end}}{{end}} |
|
||||
` |
|
||||
|
|
||||
// SubcommandHelpTemplate is the text template for the subcommand help topic.
|
|
||||
// cli.go uses text/template to render templates. You can
|
|
||||
// render custom help text by setting this variable.
|
|
||||
var SubcommandHelpTemplate = `NAME: |
|
||||
{{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} |
|
||||
|
|
||||
USAGE: |
|
||||
{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} |
|
||||
|
|
||||
COMMANDS:{{range .VisibleCategories}}{{if .Name}} |
|
||||
{{.Name}}:{{end}}{{range .VisibleCommands}} |
|
||||
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} |
|
||||
{{end}}{{if .VisibleFlags}} |
|
||||
OPTIONS: |
|
||||
{{range .VisibleFlags}}{{.}} |
|
||||
{{end}}{{end}} |
|
||||
` |
|
||||
|
|
||||
var helpCommand = Command{ |
|
||||
Name: "help", |
|
||||
Aliases: []string{"h"}, |
|
||||
Usage: "Shows a list of commands or help for one command", |
|
||||
ArgsUsage: "[command]", |
|
||||
Action: func(c *Context) error { |
|
||||
args := c.Args() |
|
||||
if args.Present() { |
|
||||
return ShowCommandHelp(c, args.First()) |
|
||||
} |
|
||||
|
|
||||
ShowAppHelp(c) |
|
||||
return nil |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
var helpSubcommand = Command{ |
|
||||
Name: "help", |
|
||||
Aliases: []string{"h"}, |
|
||||
Usage: "Shows a list of commands or help for one command", |
|
||||
ArgsUsage: "[command]", |
|
||||
Action: func(c *Context) error { |
|
||||
args := c.Args() |
|
||||
if args.Present() { |
|
||||
return ShowCommandHelp(c, args.First()) |
|
||||
} |
|
||||
|
|
||||
return ShowSubcommandHelp(c) |
|
||||
}, |
|
||||
} |
|
||||
|
|
||||
// Prints help for the App or Command
|
|
||||
type helpPrinter func(w io.Writer, templ string, data interface{}) |
|
||||
|
|
||||
// Prints help for the App or Command with custom template function.
|
|
||||
type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{}) |
|
||||
|
|
||||
// HelpPrinter is a function that writes the help output. If not set a default
|
|
||||
// is used. The function signature is:
|
|
||||
// func(w io.Writer, templ string, data interface{})
|
|
||||
var HelpPrinter helpPrinter = printHelp |
|
||||
|
|
||||
// HelpPrinterCustom is same as HelpPrinter but
|
|
||||
// takes a custom function for template function map.
|
|
||||
var HelpPrinterCustom helpPrinterCustom = printHelpCustom |
|
||||
|
|
||||
// VersionPrinter prints the version for the App
|
|
||||
var VersionPrinter = printVersion |
|
||||
|
|
||||
// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code.
|
|
||||
func ShowAppHelpAndExit(c *Context, exitCode int) { |
|
||||
ShowAppHelp(c) |
|
||||
os.Exit(exitCode) |
|
||||
} |
|
||||
|
|
||||
// ShowAppHelp is an action that displays the help.
|
|
||||
func ShowAppHelp(c *Context) (err error) { |
|
||||
if c.App.CustomAppHelpTemplate == "" { |
|
||||
HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) |
|
||||
return |
|
||||
} |
|
||||
customAppData := func() map[string]interface{} { |
|
||||
if c.App.ExtraInfo == nil { |
|
||||
return nil |
|
||||
} |
|
||||
return map[string]interface{}{ |
|
||||
"ExtraInfo": c.App.ExtraInfo, |
|
||||
} |
|
||||
} |
|
||||
HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData()) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// DefaultAppComplete prints the list of subcommands as the default app completion method
|
|
||||
func DefaultAppComplete(c *Context) { |
|
||||
for _, command := range c.App.Commands { |
|
||||
if command.Hidden { |
|
||||
continue |
|
||||
} |
|
||||
if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { |
|
||||
for _, name := range command.Names() { |
|
||||
fmt.Fprintf(c.App.Writer, "%s:%s\n", name, command.Usage) |
|
||||
} |
|
||||
} else { |
|
||||
for _, name := range command.Names() { |
|
||||
fmt.Fprintf(c.App.Writer, "%s\n", name) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// ShowCommandHelpAndExit - exits with code after showing help
|
|
||||
func ShowCommandHelpAndExit(c *Context, command string, code int) { |
|
||||
ShowCommandHelp(c, command) |
|
||||
os.Exit(code) |
|
||||
} |
|
||||
|
|
||||
// ShowCommandHelp prints help for the given command
|
|
||||
func ShowCommandHelp(ctx *Context, command string) error { |
|
||||
// show the subcommand help for a command with subcommands
|
|
||||
if command == "" { |
|
||||
HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
for _, c := range ctx.App.Commands { |
|
||||
if c.HasName(command) { |
|
||||
if c.CustomHelpTemplate != "" { |
|
||||
HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil) |
|
||||
} else { |
|
||||
HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if ctx.App.CommandNotFound == nil { |
|
||||
return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) |
|
||||
} |
|
||||
|
|
||||
ctx.App.CommandNotFound(ctx, command) |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// ShowSubcommandHelp prints help for the given subcommand
|
|
||||
func ShowSubcommandHelp(c *Context) error { |
|
||||
return ShowCommandHelp(c, c.Command.Name) |
|
||||
} |
|
||||
|
|
||||
// ShowVersion prints the version number of the App
|
|
||||
func ShowVersion(c *Context) { |
|
||||
VersionPrinter(c) |
|
||||
} |
|
||||
|
|
||||
func printVersion(c *Context) { |
|
||||
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) |
|
||||
} |
|
||||
|
|
||||
// ShowCompletions prints the lists of commands within a given context
|
|
||||
func ShowCompletions(c *Context) { |
|
||||
a := c.App |
|
||||
if a != nil && a.BashComplete != nil { |
|
||||
a.BashComplete(c) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// ShowCommandCompletions prints the custom completions for a given command
|
|
||||
func ShowCommandCompletions(ctx *Context, command string) { |
|
||||
c := ctx.App.Command(command) |
|
||||
if c != nil && c.BashComplete != nil { |
|
||||
c.BashComplete(ctx) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { |
|
||||
funcMap := template.FuncMap{ |
|
||||
"join": strings.Join, |
|
||||
} |
|
||||
if customFunc != nil { |
|
||||
for key, value := range customFunc { |
|
||||
funcMap[key] = value |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) |
|
||||
t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) |
|
||||
err := t.Execute(w, data) |
|
||||
if err != nil { |
|
||||
// If the writer is closed, t.Execute will fail, and there's nothing
|
|
||||
// we can do to recover.
|
|
||||
if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { |
|
||||
fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
w.Flush() |
|
||||
} |
|
||||
|
|
||||
func printHelp(out io.Writer, templ string, data interface{}) { |
|
||||
printHelpCustom(out, templ, data, nil) |
|
||||
} |
|
||||
|
|
||||
func checkVersion(c *Context) bool { |
|
||||
found := false |
|
||||
if VersionFlag.GetName() != "" { |
|
||||
eachName(VersionFlag.GetName(), func(name string) { |
|
||||
if c.GlobalBool(name) || c.Bool(name) { |
|
||||
found = true |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
return found |
|
||||
} |
|
||||
|
|
||||
func checkHelp(c *Context) bool { |
|
||||
found := false |
|
||||
if HelpFlag.GetName() != "" { |
|
||||
eachName(HelpFlag.GetName(), func(name string) { |
|
||||
if c.GlobalBool(name) || c.Bool(name) { |
|
||||
found = true |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
return found |
|
||||
} |
|
||||
|
|
||||
func checkCommandHelp(c *Context, name string) bool { |
|
||||
if c.Bool("h") || c.Bool("help") { |
|
||||
ShowCommandHelp(c, name) |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func checkSubcommandHelp(c *Context) bool { |
|
||||
if c.Bool("h") || c.Bool("help") { |
|
||||
ShowSubcommandHelp(c) |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { |
|
||||
if !a.EnableBashCompletion { |
|
||||
return false, arguments |
|
||||
} |
|
||||
|
|
||||
pos := len(arguments) - 1 |
|
||||
lastArg := arguments[pos] |
|
||||
|
|
||||
if lastArg != "--"+BashCompletionFlag.GetName() { |
|
||||
return false, arguments |
|
||||
} |
|
||||
|
|
||||
return true, arguments[:pos] |
|
||||
} |
|
||||
|
|
||||
func checkCompletions(c *Context) bool { |
|
||||
if !c.shellComplete { |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
if args := c.Args(); args.Present() { |
|
||||
name := args.First() |
|
||||
if cmd := c.App.Command(name); cmd != nil { |
|
||||
// let the command handle the completion
|
|
||||
return false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ShowCompletions(c) |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
func checkCommandCompletions(c *Context, name string) bool { |
|
||||
if !c.shellComplete { |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
ShowCommandCompletions(c, name) |
|
||||
return true |
|
||||
} |
|
@ -1,122 +0,0 @@ |
|||||
#!/usr/bin/env python |
|
||||
from __future__ import print_function |
|
||||
|
|
||||
import argparse |
|
||||
import os |
|
||||
import sys |
|
||||
import tempfile |
|
||||
|
|
||||
from subprocess import check_call, check_output |
|
||||
|
|
||||
|
|
||||
PACKAGE_NAME = os.environ.get( |
|
||||
'CLI_PACKAGE_NAME', 'github.com/urfave/cli' |
|
||||
) |
|
||||
|
|
||||
|
|
||||
def main(sysargs=sys.argv[:]): |
|
||||
targets = { |
|
||||
'vet': _vet, |
|
||||
'test': _test, |
|
||||
'gfmrun': _gfmrun, |
|
||||
'toc': _toc, |
|
||||
'gen': _gen, |
|
||||
} |
|
||||
|
|
||||
parser = argparse.ArgumentParser() |
|
||||
parser.add_argument( |
|
||||
'target', nargs='?', choices=tuple(targets.keys()), default='test' |
|
||||
) |
|
||||
args = parser.parse_args(sysargs[1:]) |
|
||||
|
|
||||
targets[args.target]() |
|
||||
return 0 |
|
||||
|
|
||||
|
|
||||
def _test(): |
|
||||
if check_output('go version'.split()).split()[2] < 'go1.2': |
|
||||
_run('go test -v .') |
|
||||
return |
|
||||
|
|
||||
coverprofiles = [] |
|
||||
for subpackage in ['', 'altsrc']: |
|
||||
coverprofile = 'cli.coverprofile' |
|
||||
if subpackage != '': |
|
||||
coverprofile = '{}.coverprofile'.format(subpackage) |
|
||||
|
|
||||
coverprofiles.append(coverprofile) |
|
||||
|
|
||||
_run('go test -v'.split() + [ |
|
||||
'-coverprofile={}'.format(coverprofile), |
|
||||
('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') |
|
||||
]) |
|
||||
|
|
||||
combined_name = _combine_coverprofiles(coverprofiles) |
|
||||
_run('go tool cover -func={}'.format(combined_name)) |
|
||||
os.remove(combined_name) |
|
||||
|
|
||||
|
|
||||
def _gfmrun(): |
|
||||
go_version = check_output('go version'.split()).split()[2] |
|
||||
if go_version < 'go1.3': |
|
||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr) |
|
||||
return |
|
||||
_run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md']) |
|
||||
|
|
||||
|
|
||||
def _vet(): |
|
||||
_run('go vet ./...') |
|
||||
|
|
||||
|
|
||||
def _toc(): |
|
||||
_run('node_modules/.bin/markdown-toc -i README.md') |
|
||||
_run('git diff --exit-code') |
|
||||
|
|
||||
|
|
||||
def _gen(): |
|
||||
go_version = check_output('go version'.split()).split()[2] |
|
||||
if go_version < 'go1.5': |
|
||||
print('runtests: skip on {}'.format(go_version), file=sys.stderr) |
|
||||
return |
|
||||
|
|
||||
_run('go generate ./...') |
|
||||
_run('git diff --exit-code') |
|
||||
|
|
||||
|
|
||||
def _run(command): |
|
||||
if hasattr(command, 'split'): |
|
||||
command = command.split() |
|
||||
print('runtests: {}'.format(' '.join(command)), file=sys.stderr) |
|
||||
check_call(command) |
|
||||
|
|
||||
|
|
||||
def _gfmrun_count(): |
|
||||
with open('README.md') as infile: |
|
||||
lines = infile.read().splitlines() |
|
||||
return len(filter(_is_go_runnable, lines)) |
|
||||
|
|
||||
|
|
||||
def _is_go_runnable(line): |
|
||||
return line.startswith('package main') |
|
||||
|
|
||||
|
|
||||
def _combine_coverprofiles(coverprofiles): |
|
||||
combined = tempfile.NamedTemporaryFile( |
|
||||
suffix='.coverprofile', delete=False |
|
||||
) |
|
||||
combined.write('mode: set\n') |
|
||||
|
|
||||
for coverprofile in coverprofiles: |
|
||||
with open(coverprofile, 'r') as infile: |
|
||||
for line in infile.readlines(): |
|
||||
if not line.startswith('mode: '): |
|
||||
combined.write(line) |
|
||||
|
|
||||
combined.flush() |
|
||||
name = combined.name |
|
||||
combined.close() |
|
||||
return name |
|
||||
|
|
||||
|
|
||||
if __name__ == '__main__': |
|
||||
sys.exit(main()) |
|
@ -1,29 +0,0 @@ |
|||||
package cli |
|
||||
|
|
||||
import "unicode" |
|
||||
|
|
||||
// lexicographicLess compares strings alphabetically considering case.
|
|
||||
func lexicographicLess(i, j string) bool { |
|
||||
iRunes := []rune(i) |
|
||||
jRunes := []rune(j) |
|
||||
|
|
||||
lenShared := len(iRunes) |
|
||||
if lenShared > len(jRunes) { |
|
||||
lenShared = len(jRunes) |
|
||||
} |
|
||||
|
|
||||
for index := 0; index < lenShared; index++ { |
|
||||
ir := iRunes[index] |
|
||||
jr := jRunes[index] |
|
||||
|
|
||||
if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr { |
|
||||
return lir < ljr |
|
||||
} |
|
||||
|
|
||||
if ir != jr { |
|
||||
return ir < jr |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return i < j |
|
||||
} |
|
@ -1,27 +0,0 @@ |
|||||
Copyright (c) 2009 The Go Authors. All rights reserved. |
|
||||
|
|
||||
Redistribution and use in source and binary forms, with or without |
|
||||
modification, are permitted provided that the following conditions are |
|
||||
met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above copyright |
|
||||
notice, this list of conditions and the following disclaimer. |
|
||||
* Redistributions in binary form must reproduce the above |
|
||||
copyright notice, this list of conditions and the following disclaimer |
|
||||
in the documentation and/or other materials provided with the |
|
||||
distribution. |
|
||||
* Neither the name of Google Inc. nor the names of its |
|
||||
contributors may be used to endorse or promote products derived from |
|
||||
this software without specific prior written permission. |
|
||||
|
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
@ -1,22 +0,0 @@ |
|||||
Additional IP Rights Grant (Patents) |
|
||||
|
|
||||
"This implementation" means the copyrightable works distributed by |
|
||||
Google as part of the Go project. |
|
||||
|
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive, |
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section) |
|
||||
patent license to make, have made, use, offer to sell, sell, import, |
|
||||
transfer and otherwise run, modify and propagate the contents of this |
|
||||
implementation of Go, where such license applies only to those patent |
|
||||
claims, both currently owned or controlled by Google and acquired in |
|
||||
the future, licensable by Google that are necessarily infringed by this |
|
||||
implementation of Go. This grant does not include claims that would be |
|
||||
infringed only as a consequence of further modification of this |
|
||||
implementation. If you or your agent or exclusive licensee institute or |
|
||||
order or agree to the institution of patent litigation against any |
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging |
|
||||
that this implementation of Go or any code incorporated within this |
|
||||
implementation of Go constitutes direct or contributory patent |
|
||||
infringement, or inducement of patent infringement, then any patent |
|
||||
rights granted to you under this License for this implementation of Go |
|
||||
shall terminate as of the date such litigation is filed. |
|
@ -1,951 +0,0 @@ |
|||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
package terminal |
|
||||
|
|
||||
import ( |
|
||||
"bytes" |
|
||||
"io" |
|
||||
"sync" |
|
||||
"unicode/utf8" |
|
||||
) |
|
||||
|
|
||||
// EscapeCodes contains escape sequences that can be written to the terminal in
|
|
||||
// order to achieve different styles of text.
|
|
||||
type EscapeCodes struct { |
|
||||
// Foreground colors
|
|
||||
Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte |
|
||||
|
|
||||
// Reset all attributes
|
|
||||
Reset []byte |
|
||||
} |
|
||||
|
|
||||
var vt100EscapeCodes = EscapeCodes{ |
|
||||
Black: []byte{keyEscape, '[', '3', '0', 'm'}, |
|
||||
Red: []byte{keyEscape, '[', '3', '1', 'm'}, |
|
||||
Green: []byte{keyEscape, '[', '3', '2', 'm'}, |
|
||||
Yellow: []byte{keyEscape, '[', '3', '3', 'm'}, |
|
||||
Blue: []byte{keyEscape, '[', '3', '4', 'm'}, |
|
||||
Magenta: []byte{keyEscape, '[', '3', '5', 'm'}, |
|
||||
Cyan: []byte{keyEscape, '[', '3', '6', 'm'}, |
|
||||
White: []byte{keyEscape, '[', '3', '7', 'm'}, |
|
||||
|
|
||||
Reset: []byte{keyEscape, '[', '0', 'm'}, |
|
||||
} |
|
||||
|
|
||||
// Terminal contains the state for running a VT100 terminal that is capable of
|
|
||||
// reading lines of input.
|
|
||||
type Terminal struct { |
|
||||
// AutoCompleteCallback, if non-null, is called for each keypress with
|
|
||||
// the full input line and the current position of the cursor (in
|
|
||||
// bytes, as an index into |line|). If it returns ok=false, the key
|
|
||||
// press is processed normally. Otherwise it returns a replacement line
|
|
||||
// and the new cursor position.
|
|
||||
AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool) |
|
||||
|
|
||||
// Escape contains a pointer to the escape codes for this terminal.
|
|
||||
// It's always a valid pointer, although the escape codes themselves
|
|
||||
// may be empty if the terminal doesn't support them.
|
|
||||
Escape *EscapeCodes |
|
||||
|
|
||||
// lock protects the terminal and the state in this object from
|
|
||||
// concurrent processing of a key press and a Write() call.
|
|
||||
lock sync.Mutex |
|
||||
|
|
||||
c io.ReadWriter |
|
||||
prompt []rune |
|
||||
|
|
||||
// line is the current line being entered.
|
|
||||
line []rune |
|
||||
// pos is the logical position of the cursor in line
|
|
||||
pos int |
|
||||
// echo is true if local echo is enabled
|
|
||||
echo bool |
|
||||
// pasteActive is true iff there is a bracketed paste operation in
|
|
||||
// progress.
|
|
||||
pasteActive bool |
|
||||
|
|
||||
// cursorX contains the current X value of the cursor where the left
|
|
||||
// edge is 0. cursorY contains the row number where the first row of
|
|
||||
// the current line is 0.
|
|
||||
cursorX, cursorY int |
|
||||
// maxLine is the greatest value of cursorY so far.
|
|
||||
maxLine int |
|
||||
|
|
||||
termWidth, termHeight int |
|
||||
|
|
||||
// outBuf contains the terminal data to be sent.
|
|
||||
outBuf []byte |
|
||||
// remainder contains the remainder of any partial key sequences after
|
|
||||
// a read. It aliases into inBuf.
|
|
||||
remainder []byte |
|
||||
inBuf [256]byte |
|
||||
|
|
||||
// history contains previously entered commands so that they can be
|
|
||||
// accessed with the up and down keys.
|
|
||||
history stRingBuffer |
|
||||
// historyIndex stores the currently accessed history entry, where zero
|
|
||||
// means the immediately previous entry.
|
|
||||
historyIndex int |
|
||||
// When navigating up and down the history it's possible to return to
|
|
||||
// the incomplete, initial line. That value is stored in
|
|
||||
// historyPending.
|
|
||||
historyPending string |
|
||||
} |
|
||||
|
|
||||
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
|
|
||||
// a local terminal, that terminal must first have been put into raw mode.
|
|
||||
// prompt is a string that is written at the start of each input line (i.e.
|
|
||||
// "> ").
|
|
||||
func NewTerminal(c io.ReadWriter, prompt string) *Terminal { |
|
||||
return &Terminal{ |
|
||||
Escape: &vt100EscapeCodes, |
|
||||
c: c, |
|
||||
prompt: []rune(prompt), |
|
||||
termWidth: 80, |
|
||||
termHeight: 24, |
|
||||
echo: true, |
|
||||
historyIndex: -1, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const ( |
|
||||
keyCtrlD = 4 |
|
||||
keyCtrlU = 21 |
|
||||
keyEnter = '\r' |
|
||||
keyEscape = 27 |
|
||||
keyBackspace = 127 |
|
||||
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota |
|
||||
keyUp |
|
||||
keyDown |
|
||||
keyLeft |
|
||||
keyRight |
|
||||
keyAltLeft |
|
||||
keyAltRight |
|
||||
keyHome |
|
||||
keyEnd |
|
||||
keyDeleteWord |
|
||||
keyDeleteLine |
|
||||
keyClearScreen |
|
||||
keyPasteStart |
|
||||
keyPasteEnd |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
crlf = []byte{'\r', '\n'} |
|
||||
pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'} |
|
||||
pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'} |
|
||||
) |
|
||||
|
|
||||
// bytesToKey tries to parse a key sequence from b. If successful, it returns
|
|
||||
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
|
|
||||
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { |
|
||||
if len(b) == 0 { |
|
||||
return utf8.RuneError, nil |
|
||||
} |
|
||||
|
|
||||
if !pasteActive { |
|
||||
switch b[0] { |
|
||||
case 1: // ^A
|
|
||||
return keyHome, b[1:] |
|
||||
case 5: // ^E
|
|
||||
return keyEnd, b[1:] |
|
||||
case 8: // ^H
|
|
||||
return keyBackspace, b[1:] |
|
||||
case 11: // ^K
|
|
||||
return keyDeleteLine, b[1:] |
|
||||
case 12: // ^L
|
|
||||
return keyClearScreen, b[1:] |
|
||||
case 23: // ^W
|
|
||||
return keyDeleteWord, b[1:] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if b[0] != keyEscape { |
|
||||
if !utf8.FullRune(b) { |
|
||||
return utf8.RuneError, b |
|
||||
} |
|
||||
r, l := utf8.DecodeRune(b) |
|
||||
return r, b[l:] |
|
||||
} |
|
||||
|
|
||||
if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { |
|
||||
switch b[2] { |
|
||||
case 'A': |
|
||||
return keyUp, b[3:] |
|
||||
case 'B': |
|
||||
return keyDown, b[3:] |
|
||||
case 'C': |
|
||||
return keyRight, b[3:] |
|
||||
case 'D': |
|
||||
return keyLeft, b[3:] |
|
||||
case 'H': |
|
||||
return keyHome, b[3:] |
|
||||
case 'F': |
|
||||
return keyEnd, b[3:] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { |
|
||||
switch b[5] { |
|
||||
case 'C': |
|
||||
return keyAltRight, b[6:] |
|
||||
case 'D': |
|
||||
return keyAltLeft, b[6:] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) { |
|
||||
return keyPasteStart, b[6:] |
|
||||
} |
|
||||
|
|
||||
if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) { |
|
||||
return keyPasteEnd, b[6:] |
|
||||
} |
|
||||
|
|
||||
// If we get here then we have a key that we don't recognise, or a
|
|
||||
// partial sequence. It's not clear how one should find the end of a
|
|
||||
// sequence without knowing them all, but it seems that [a-zA-Z~] only
|
|
||||
// appears at the end of a sequence.
|
|
||||
for i, c := range b[0:] { |
|
||||
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' { |
|
||||
return keyUnknown, b[i+1:] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return utf8.RuneError, b |
|
||||
} |
|
||||
|
|
||||
// queue appends data to the end of t.outBuf
|
|
||||
func (t *Terminal) queue(data []rune) { |
|
||||
t.outBuf = append(t.outBuf, []byte(string(data))...) |
|
||||
} |
|
||||
|
|
||||
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'} |
|
||||
var space = []rune{' '} |
|
||||
|
|
||||
func isPrintable(key rune) bool { |
|
||||
isInSurrogateArea := key >= 0xd800 && key <= 0xdbff |
|
||||
return key >= 32 && !isInSurrogateArea |
|
||||
} |
|
||||
|
|
||||
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
|
|
||||
// given, logical position in the text.
|
|
||||
func (t *Terminal) moveCursorToPos(pos int) { |
|
||||
if !t.echo { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
x := visualLength(t.prompt) + pos |
|
||||
y := x / t.termWidth |
|
||||
x = x % t.termWidth |
|
||||
|
|
||||
up := 0 |
|
||||
if y < t.cursorY { |
|
||||
up = t.cursorY - y |
|
||||
} |
|
||||
|
|
||||
down := 0 |
|
||||
if y > t.cursorY { |
|
||||
down = y - t.cursorY |
|
||||
} |
|
||||
|
|
||||
left := 0 |
|
||||
if x < t.cursorX { |
|
||||
left = t.cursorX - x |
|
||||
} |
|
||||
|
|
||||
right := 0 |
|
||||
if x > t.cursorX { |
|
||||
right = x - t.cursorX |
|
||||
} |
|
||||
|
|
||||
t.cursorX = x |
|
||||
t.cursorY = y |
|
||||
t.move(up, down, left, right) |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) move(up, down, left, right int) { |
|
||||
movement := make([]rune, 3*(up+down+left+right)) |
|
||||
m := movement |
|
||||
for i := 0; i < up; i++ { |
|
||||
m[0] = keyEscape |
|
||||
m[1] = '[' |
|
||||
m[2] = 'A' |
|
||||
m = m[3:] |
|
||||
} |
|
||||
for i := 0; i < down; i++ { |
|
||||
m[0] = keyEscape |
|
||||
m[1] = '[' |
|
||||
m[2] = 'B' |
|
||||
m = m[3:] |
|
||||
} |
|
||||
for i := 0; i < left; i++ { |
|
||||
m[0] = keyEscape |
|
||||
m[1] = '[' |
|
||||
m[2] = 'D' |
|
||||
m = m[3:] |
|
||||
} |
|
||||
for i := 0; i < right; i++ { |
|
||||
m[0] = keyEscape |
|
||||
m[1] = '[' |
|
||||
m[2] = 'C' |
|
||||
m = m[3:] |
|
||||
} |
|
||||
|
|
||||
t.queue(movement) |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) clearLineToRight() { |
|
||||
op := []rune{keyEscape, '[', 'K'} |
|
||||
t.queue(op) |
|
||||
} |
|
||||
|
|
||||
const maxLineLength = 4096 |
|
||||
|
|
||||
func (t *Terminal) setLine(newLine []rune, newPos int) { |
|
||||
if t.echo { |
|
||||
t.moveCursorToPos(0) |
|
||||
t.writeLine(newLine) |
|
||||
for i := len(newLine); i < len(t.line); i++ { |
|
||||
t.writeLine(space) |
|
||||
} |
|
||||
t.moveCursorToPos(newPos) |
|
||||
} |
|
||||
t.line = newLine |
|
||||
t.pos = newPos |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) advanceCursor(places int) { |
|
||||
t.cursorX += places |
|
||||
t.cursorY += t.cursorX / t.termWidth |
|
||||
if t.cursorY > t.maxLine { |
|
||||
t.maxLine = t.cursorY |
|
||||
} |
|
||||
t.cursorX = t.cursorX % t.termWidth |
|
||||
|
|
||||
if places > 0 && t.cursorX == 0 { |
|
||||
// Normally terminals will advance the current position
|
|
||||
// when writing a character. But that doesn't happen
|
|
||||
// for the last character in a line. However, when
|
|
||||
// writing a character (except a new line) that causes
|
|
||||
// a line wrap, the position will be advanced two
|
|
||||
// places.
|
|
||||
//
|
|
||||
// So, if we are stopping at the end of a line, we
|
|
||||
// need to write a newline so that our cursor can be
|
|
||||
// advanced to the next line.
|
|
||||
t.outBuf = append(t.outBuf, '\r', '\n') |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) eraseNPreviousChars(n int) { |
|
||||
if n == 0 { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
if t.pos < n { |
|
||||
n = t.pos |
|
||||
} |
|
||||
t.pos -= n |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
|
|
||||
copy(t.line[t.pos:], t.line[n+t.pos:]) |
|
||||
t.line = t.line[:len(t.line)-n] |
|
||||
if t.echo { |
|
||||
t.writeLine(t.line[t.pos:]) |
|
||||
for i := 0; i < n; i++ { |
|
||||
t.queue(space) |
|
||||
} |
|
||||
t.advanceCursor(n) |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// countToLeftWord returns then number of characters from the cursor to the
|
|
||||
// start of the previous word.
|
|
||||
func (t *Terminal) countToLeftWord() int { |
|
||||
if t.pos == 0 { |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
pos := t.pos - 1 |
|
||||
for pos > 0 { |
|
||||
if t.line[pos] != ' ' { |
|
||||
break |
|
||||
} |
|
||||
pos-- |
|
||||
} |
|
||||
for pos > 0 { |
|
||||
if t.line[pos] == ' ' { |
|
||||
pos++ |
|
||||
break |
|
||||
} |
|
||||
pos-- |
|
||||
} |
|
||||
|
|
||||
return t.pos - pos |
|
||||
} |
|
||||
|
|
||||
// countToRightWord returns then number of characters from the cursor to the
|
|
||||
// start of the next word.
|
|
||||
func (t *Terminal) countToRightWord() int { |
|
||||
pos := t.pos |
|
||||
for pos < len(t.line) { |
|
||||
if t.line[pos] == ' ' { |
|
||||
break |
|
||||
} |
|
||||
pos++ |
|
||||
} |
|
||||
for pos < len(t.line) { |
|
||||
if t.line[pos] != ' ' { |
|
||||
break |
|
||||
} |
|
||||
pos++ |
|
||||
} |
|
||||
return pos - t.pos |
|
||||
} |
|
||||
|
|
||||
// visualLength returns the number of visible glyphs in s.
|
|
||||
func visualLength(runes []rune) int { |
|
||||
inEscapeSeq := false |
|
||||
length := 0 |
|
||||
|
|
||||
for _, r := range runes { |
|
||||
switch { |
|
||||
case inEscapeSeq: |
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') { |
|
||||
inEscapeSeq = false |
|
||||
} |
|
||||
case r == '\x1b': |
|
||||
inEscapeSeq = true |
|
||||
default: |
|
||||
length++ |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return length |
|
||||
} |
|
||||
|
|
||||
// handleKey processes the given key and, optionally, returns a line of text
|
|
||||
// that the user has entered.
|
|
||||
func (t *Terminal) handleKey(key rune) (line string, ok bool) { |
|
||||
if t.pasteActive && key != keyEnter { |
|
||||
t.addKeyToLine(key) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
switch key { |
|
||||
case keyBackspace: |
|
||||
if t.pos == 0 { |
|
||||
return |
|
||||
} |
|
||||
t.eraseNPreviousChars(1) |
|
||||
case keyAltLeft: |
|
||||
// move left by a word.
|
|
||||
t.pos -= t.countToLeftWord() |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
case keyAltRight: |
|
||||
// move right by a word.
|
|
||||
t.pos += t.countToRightWord() |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
case keyLeft: |
|
||||
if t.pos == 0 { |
|
||||
return |
|
||||
} |
|
||||
t.pos-- |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
case keyRight: |
|
||||
if t.pos == len(t.line) { |
|
||||
return |
|
||||
} |
|
||||
t.pos++ |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
case keyHome: |
|
||||
if t.pos == 0 { |
|
||||
return |
|
||||
} |
|
||||
t.pos = 0 |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
case keyEnd: |
|
||||
if t.pos == len(t.line) { |
|
||||
return |
|
||||
} |
|
||||
t.pos = len(t.line) |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
case keyUp: |
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1) |
|
||||
if !ok { |
|
||||
return "", false |
|
||||
} |
|
||||
if t.historyIndex == -1 { |
|
||||
t.historyPending = string(t.line) |
|
||||
} |
|
||||
t.historyIndex++ |
|
||||
runes := []rune(entry) |
|
||||
t.setLine(runes, len(runes)) |
|
||||
case keyDown: |
|
||||
switch t.historyIndex { |
|
||||
case -1: |
|
||||
return |
|
||||
case 0: |
|
||||
runes := []rune(t.historyPending) |
|
||||
t.setLine(runes, len(runes)) |
|
||||
t.historyIndex-- |
|
||||
default: |
|
||||
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1) |
|
||||
if ok { |
|
||||
t.historyIndex-- |
|
||||
runes := []rune(entry) |
|
||||
t.setLine(runes, len(runes)) |
|
||||
} |
|
||||
} |
|
||||
case keyEnter: |
|
||||
t.moveCursorToPos(len(t.line)) |
|
||||
t.queue([]rune("\r\n")) |
|
||||
line = string(t.line) |
|
||||
ok = true |
|
||||
t.line = t.line[:0] |
|
||||
t.pos = 0 |
|
||||
t.cursorX = 0 |
|
||||
t.cursorY = 0 |
|
||||
t.maxLine = 0 |
|
||||
case keyDeleteWord: |
|
||||
// Delete zero or more spaces and then one or more characters.
|
|
||||
t.eraseNPreviousChars(t.countToLeftWord()) |
|
||||
case keyDeleteLine: |
|
||||
// Delete everything from the current cursor position to the
|
|
||||
// end of line.
|
|
||||
for i := t.pos; i < len(t.line); i++ { |
|
||||
t.queue(space) |
|
||||
t.advanceCursor(1) |
|
||||
} |
|
||||
t.line = t.line[:t.pos] |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
case keyCtrlD: |
|
||||
// Erase the character under the current position.
|
|
||||
// The EOF case when the line is empty is handled in
|
|
||||
// readLine().
|
|
||||
if t.pos < len(t.line) { |
|
||||
t.pos++ |
|
||||
t.eraseNPreviousChars(1) |
|
||||
} |
|
||||
case keyCtrlU: |
|
||||
t.eraseNPreviousChars(t.pos) |
|
||||
case keyClearScreen: |
|
||||
// Erases the screen and moves the cursor to the home position.
|
|
||||
t.queue([]rune("\x1b[2J\x1b[H")) |
|
||||
t.queue(t.prompt) |
|
||||
t.cursorX, t.cursorY = 0, 0 |
|
||||
t.advanceCursor(visualLength(t.prompt)) |
|
||||
t.setLine(t.line, t.pos) |
|
||||
default: |
|
||||
if t.AutoCompleteCallback != nil { |
|
||||
prefix := string(t.line[:t.pos]) |
|
||||
suffix := string(t.line[t.pos:]) |
|
||||
|
|
||||
t.lock.Unlock() |
|
||||
newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key) |
|
||||
t.lock.Lock() |
|
||||
|
|
||||
if completeOk { |
|
||||
t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos])) |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
if !isPrintable(key) { |
|
||||
return |
|
||||
} |
|
||||
if len(t.line) == maxLineLength { |
|
||||
return |
|
||||
} |
|
||||
t.addKeyToLine(key) |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// addKeyToLine inserts the given key at the current position in the current
|
|
||||
// line.
|
|
||||
func (t *Terminal) addKeyToLine(key rune) { |
|
||||
if len(t.line) == cap(t.line) { |
|
||||
newLine := make([]rune, len(t.line), 2*(1+len(t.line))) |
|
||||
copy(newLine, t.line) |
|
||||
t.line = newLine |
|
||||
} |
|
||||
t.line = t.line[:len(t.line)+1] |
|
||||
copy(t.line[t.pos+1:], t.line[t.pos:]) |
|
||||
t.line[t.pos] = key |
|
||||
if t.echo { |
|
||||
t.writeLine(t.line[t.pos:]) |
|
||||
} |
|
||||
t.pos++ |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) writeLine(line []rune) { |
|
||||
for len(line) != 0 { |
|
||||
remainingOnLine := t.termWidth - t.cursorX |
|
||||
todo := len(line) |
|
||||
if todo > remainingOnLine { |
|
||||
todo = remainingOnLine |
|
||||
} |
|
||||
t.queue(line[:todo]) |
|
||||
t.advanceCursor(visualLength(line[:todo])) |
|
||||
line = line[todo:] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// writeWithCRLF writes buf to w but replaces all occurrences of \n with \r\n.
|
|
||||
func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) { |
|
||||
for len(buf) > 0 { |
|
||||
i := bytes.IndexByte(buf, '\n') |
|
||||
todo := len(buf) |
|
||||
if i >= 0 { |
|
||||
todo = i |
|
||||
} |
|
||||
|
|
||||
var nn int |
|
||||
nn, err = w.Write(buf[:todo]) |
|
||||
n += nn |
|
||||
if err != nil { |
|
||||
return n, err |
|
||||
} |
|
||||
buf = buf[todo:] |
|
||||
|
|
||||
if i >= 0 { |
|
||||
if _, err = w.Write(crlf); err != nil { |
|
||||
return n, err |
|
||||
} |
|
||||
n++ |
|
||||
buf = buf[1:] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return n, nil |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) Write(buf []byte) (n int, err error) { |
|
||||
t.lock.Lock() |
|
||||
defer t.lock.Unlock() |
|
||||
|
|
||||
if t.cursorX == 0 && t.cursorY == 0 { |
|
||||
// This is the easy case: there's nothing on the screen that we
|
|
||||
// have to move out of the way.
|
|
||||
return writeWithCRLF(t.c, buf) |
|
||||
} |
|
||||
|
|
||||
// We have a prompt and possibly user input on the screen. We
|
|
||||
// have to clear it first.
|
|
||||
t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */) |
|
||||
t.cursorX = 0 |
|
||||
t.clearLineToRight() |
|
||||
|
|
||||
for t.cursorY > 0 { |
|
||||
t.move(1 /* up */, 0, 0, 0) |
|
||||
t.cursorY-- |
|
||||
t.clearLineToRight() |
|
||||
} |
|
||||
|
|
||||
if _, err = t.c.Write(t.outBuf); err != nil { |
|
||||
return |
|
||||
} |
|
||||
t.outBuf = t.outBuf[:0] |
|
||||
|
|
||||
if n, err = writeWithCRLF(t.c, buf); err != nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
t.writeLine(t.prompt) |
|
||||
if t.echo { |
|
||||
t.writeLine(t.line) |
|
||||
} |
|
||||
|
|
||||
t.moveCursorToPos(t.pos) |
|
||||
|
|
||||
if _, err = t.c.Write(t.outBuf); err != nil { |
|
||||
return |
|
||||
} |
|
||||
t.outBuf = t.outBuf[:0] |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// ReadPassword temporarily changes the prompt and reads a password, without
|
|
||||
// echo, from the terminal.
|
|
||||
func (t *Terminal) ReadPassword(prompt string) (line string, err error) { |
|
||||
t.lock.Lock() |
|
||||
defer t.lock.Unlock() |
|
||||
|
|
||||
oldPrompt := t.prompt |
|
||||
t.prompt = []rune(prompt) |
|
||||
t.echo = false |
|
||||
|
|
||||
line, err = t.readLine() |
|
||||
|
|
||||
t.prompt = oldPrompt |
|
||||
t.echo = true |
|
||||
|
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// ReadLine returns a line of input from the terminal.
|
|
||||
func (t *Terminal) ReadLine() (line string, err error) { |
|
||||
t.lock.Lock() |
|
||||
defer t.lock.Unlock() |
|
||||
|
|
||||
return t.readLine() |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) readLine() (line string, err error) { |
|
||||
// t.lock must be held at this point
|
|
||||
|
|
||||
if t.cursorX == 0 && t.cursorY == 0 { |
|
||||
t.writeLine(t.prompt) |
|
||||
t.c.Write(t.outBuf) |
|
||||
t.outBuf = t.outBuf[:0] |
|
||||
} |
|
||||
|
|
||||
lineIsPasted := t.pasteActive |
|
||||
|
|
||||
for { |
|
||||
rest := t.remainder |
|
||||
lineOk := false |
|
||||
for !lineOk { |
|
||||
var key rune |
|
||||
key, rest = bytesToKey(rest, t.pasteActive) |
|
||||
if key == utf8.RuneError { |
|
||||
break |
|
||||
} |
|
||||
if !t.pasteActive { |
|
||||
if key == keyCtrlD { |
|
||||
if len(t.line) == 0 { |
|
||||
return "", io.EOF |
|
||||
} |
|
||||
} |
|
||||
if key == keyPasteStart { |
|
||||
t.pasteActive = true |
|
||||
if len(t.line) == 0 { |
|
||||
lineIsPasted = true |
|
||||
} |
|
||||
continue |
|
||||
} |
|
||||
} else if key == keyPasteEnd { |
|
||||
t.pasteActive = false |
|
||||
continue |
|
||||
} |
|
||||
if !t.pasteActive { |
|
||||
lineIsPasted = false |
|
||||
} |
|
||||
line, lineOk = t.handleKey(key) |
|
||||
} |
|
||||
if len(rest) > 0 { |
|
||||
n := copy(t.inBuf[:], rest) |
|
||||
t.remainder = t.inBuf[:n] |
|
||||
} else { |
|
||||
t.remainder = nil |
|
||||
} |
|
||||
t.c.Write(t.outBuf) |
|
||||
t.outBuf = t.outBuf[:0] |
|
||||
if lineOk { |
|
||||
if t.echo { |
|
||||
t.historyIndex = -1 |
|
||||
t.history.Add(line) |
|
||||
} |
|
||||
if lineIsPasted { |
|
||||
err = ErrPasteIndicator |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// t.remainder is a slice at the beginning of t.inBuf
|
|
||||
// containing a partial key sequence
|
|
||||
readBuf := t.inBuf[len(t.remainder):] |
|
||||
var n int |
|
||||
|
|
||||
t.lock.Unlock() |
|
||||
n, err = t.c.Read(readBuf) |
|
||||
t.lock.Lock() |
|
||||
|
|
||||
if err != nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
t.remainder = t.inBuf[:n+len(t.remainder)] |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// SetPrompt sets the prompt to be used when reading subsequent lines.
|
|
||||
func (t *Terminal) SetPrompt(prompt string) { |
|
||||
t.lock.Lock() |
|
||||
defer t.lock.Unlock() |
|
||||
|
|
||||
t.prompt = []rune(prompt) |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) { |
|
||||
// Move cursor to column zero at the start of the line.
|
|
||||
t.move(t.cursorY, 0, t.cursorX, 0) |
|
||||
t.cursorX, t.cursorY = 0, 0 |
|
||||
t.clearLineToRight() |
|
||||
for t.cursorY < numPrevLines { |
|
||||
// Move down a line
|
|
||||
t.move(0, 1, 0, 0) |
|
||||
t.cursorY++ |
|
||||
t.clearLineToRight() |
|
||||
} |
|
||||
// Move back to beginning.
|
|
||||
t.move(t.cursorY, 0, 0, 0) |
|
||||
t.cursorX, t.cursorY = 0, 0 |
|
||||
|
|
||||
t.queue(t.prompt) |
|
||||
t.advanceCursor(visualLength(t.prompt)) |
|
||||
t.writeLine(t.line) |
|
||||
t.moveCursorToPos(t.pos) |
|
||||
} |
|
||||
|
|
||||
func (t *Terminal) SetSize(width, height int) error { |
|
||||
t.lock.Lock() |
|
||||
defer t.lock.Unlock() |
|
||||
|
|
||||
if width == 0 { |
|
||||
width = 1 |
|
||||
} |
|
||||
|
|
||||
oldWidth := t.termWidth |
|
||||
t.termWidth, t.termHeight = width, height |
|
||||
|
|
||||
switch { |
|
||||
case width == oldWidth: |
|
||||
// If the width didn't change then nothing else needs to be
|
|
||||
// done.
|
|
||||
return nil |
|
||||
case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0: |
|
||||
// If there is nothing on current line and no prompt printed,
|
|
||||
// just do nothing
|
|
||||
return nil |
|
||||
case width < oldWidth: |
|
||||
// Some terminals (e.g. xterm) will truncate lines that were
|
|
||||
// too long when shinking. Others, (e.g. gnome-terminal) will
|
|
||||
// attempt to wrap them. For the former, repainting t.maxLine
|
|
||||
// works great, but that behaviour goes badly wrong in the case
|
|
||||
// of the latter because they have doubled every full line.
|
|
||||
|
|
||||
// We assume that we are working on a terminal that wraps lines
|
|
||||
// and adjust the cursor position based on every previous line
|
|
||||
// wrapping and turning into two. This causes the prompt on
|
|
||||
// xterms to move upwards, which isn't great, but it avoids a
|
|
||||
// huge mess with gnome-terminal.
|
|
||||
if t.cursorX >= t.termWidth { |
|
||||
t.cursorX = t.termWidth - 1 |
|
||||
} |
|
||||
t.cursorY *= 2 |
|
||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2) |
|
||||
case width > oldWidth: |
|
||||
// If the terminal expands then our position calculations will
|
|
||||
// be wrong in the future because we think the cursor is
|
|
||||
// |t.pos| chars into the string, but there will be a gap at
|
|
||||
// the end of any wrapped line.
|
|
||||
//
|
|
||||
// But the position will actually be correct until we move, so
|
|
||||
// we can move back to the beginning and repaint everything.
|
|
||||
t.clearAndRepaintLinePlusNPrevious(t.maxLine) |
|
||||
} |
|
||||
|
|
||||
_, err := t.c.Write(t.outBuf) |
|
||||
t.outBuf = t.outBuf[:0] |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
type pasteIndicatorError struct{} |
|
||||
|
|
||||
func (pasteIndicatorError) Error() string { |
|
||||
return "terminal: ErrPasteIndicator not correctly handled" |
|
||||
} |
|
||||
|
|
||||
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
|
|
||||
// to valid line data. It indicates that bracketed paste mode is enabled and
|
|
||||
// that the returned line consists only of pasted data. Programs may wish to
|
|
||||
// interpret pasted data more literally than typed data.
|
|
||||
var ErrPasteIndicator = pasteIndicatorError{} |
|
||||
|
|
||||
// SetBracketedPasteMode requests that the terminal bracket paste operations
|
|
||||
// with markers. Not all terminals support this but, if it is supported, then
|
|
||||
// enabling this mode will stop any autocomplete callback from running due to
|
|
||||
// pastes. Additionally, any lines that are completely pasted will be returned
|
|
||||
// from ReadLine with the error set to ErrPasteIndicator.
|
|
||||
func (t *Terminal) SetBracketedPasteMode(on bool) { |
|
||||
if on { |
|
||||
io.WriteString(t.c, "\x1b[?2004h") |
|
||||
} else { |
|
||||
io.WriteString(t.c, "\x1b[?2004l") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// stRingBuffer is a ring buffer of strings.
|
|
||||
type stRingBuffer struct { |
|
||||
// entries contains max elements.
|
|
||||
entries []string |
|
||||
max int |
|
||||
// head contains the index of the element most recently added to the ring.
|
|
||||
head int |
|
||||
// size contains the number of elements in the ring.
|
|
||||
size int |
|
||||
} |
|
||||
|
|
||||
func (s *stRingBuffer) Add(a string) { |
|
||||
if s.entries == nil { |
|
||||
const defaultNumEntries = 100 |
|
||||
s.entries = make([]string, defaultNumEntries) |
|
||||
s.max = defaultNumEntries |
|
||||
} |
|
||||
|
|
||||
s.head = (s.head + 1) % s.max |
|
||||
s.entries[s.head] = a |
|
||||
if s.size < s.max { |
|
||||
s.size++ |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// NthPreviousEntry returns the value passed to the nth previous call to Add.
|
|
||||
// If n is zero then the immediately prior value is returned, if one, then the
|
|
||||
// next most recent, and so on. If such an element doesn't exist then ok is
|
|
||||
// false.
|
|
||||
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { |
|
||||
if n >= s.size { |
|
||||
return "", false |
|
||||
} |
|
||||
index := s.head - n |
|
||||
if index < 0 { |
|
||||
index += s.max |
|
||||
} |
|
||||
return s.entries[index], true |
|
||||
} |
|
||||
|
|
||||
// readPasswordLine reads from reader until it finds \n or io.EOF.
|
|
||||
// The slice returned does not include the \n.
|
|
||||
// readPasswordLine also ignores any \r it finds.
|
|
||||
func readPasswordLine(reader io.Reader) ([]byte, error) { |
|
||||
var buf [1]byte |
|
||||
var ret []byte |
|
||||
|
|
||||
for { |
|
||||
n, err := reader.Read(buf[:]) |
|
||||
if n > 0 { |
|
||||
switch buf[0] { |
|
||||
case '\n': |
|
||||
return ret, nil |
|
||||
case '\r': |
|
||||
// remove \r from passwords on Windows
|
|
||||
default: |
|
||||
ret = append(ret, buf[0]) |
|
||||
} |
|
||||
continue |
|
||||
} |
|
||||
if err != nil { |
|
||||
if err == io.EOF && len(ret) > 0 { |
|
||||
return ret, nil |
|
||||
} |
|
||||
return ret, err |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,114 +0,0 @@ |
|||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
|
|
||||
|
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
|
||||
// commonly found on UNIX systems.
|
|
||||
//
|
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||
//
|
|
||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||
// if err != nil {
|
|
||||
// panic(err)
|
|
||||
// }
|
|
||||
// defer terminal.Restore(0, oldState)
|
|
||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
|
||||
|
|
||||
import ( |
|
||||
"golang.org/x/sys/unix" |
|
||||
) |
|
||||
|
|
||||
// State contains the state of a terminal.
|
|
||||
type State struct { |
|
||||
termios unix.Termios |
|
||||
} |
|
||||
|
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||
func IsTerminal(fd int) bool { |
|
||||
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios) |
|
||||
return err == nil |
|
||||
} |
|
||||
|
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||
// restored.
|
|
||||
func MakeRaw(fd int) (*State, error) { |
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
oldState := State{termios: *termios} |
|
||||
|
|
||||
// This attempts to replicate the behaviour documented for cfmakeraw in
|
|
||||
// the termios(3) manpage.
|
|
||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON |
|
||||
termios.Oflag &^= unix.OPOST |
|
||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN |
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB |
|
||||
termios.Cflag |= unix.CS8 |
|
||||
termios.Cc[unix.VMIN] = 1 |
|
||||
termios.Cc[unix.VTIME] = 0 |
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, termios); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return &oldState, nil |
|
||||
} |
|
||||
|
|
||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||
// restore the terminal after a signal.
|
|
||||
func GetState(fd int) (*State, error) { |
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return &State{termios: *termios}, nil |
|
||||
} |
|
||||
|
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||
// previous state.
|
|
||||
func Restore(fd int, state *State) error { |
|
||||
return unix.IoctlSetTermios(fd, ioctlWriteTermios, &state.termios) |
|
||||
} |
|
||||
|
|
||||
// GetSize returns the dimensions of the given terminal.
|
|
||||
func GetSize(fd int) (width, height int, err error) { |
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) |
|
||||
if err != nil { |
|
||||
return -1, -1, err |
|
||||
} |
|
||||
return int(ws.Col), int(ws.Row), nil |
|
||||
} |
|
||||
|
|
||||
// passwordReader is an io.Reader that reads from a specific file descriptor.
|
|
||||
type passwordReader int |
|
||||
|
|
||||
func (r passwordReader) Read(buf []byte) (int, error) { |
|
||||
return unix.Read(int(r), buf) |
|
||||
} |
|
||||
|
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||
// returned does not include the \n.
|
|
||||
func ReadPassword(fd int) ([]byte, error) { |
|
||||
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
newState := *termios |
|
||||
newState.Lflag &^= unix.ECHO |
|
||||
newState.Lflag |= unix.ICANON | unix.ISIG |
|
||||
newState.Iflag |= unix.ICRNL |
|
||||
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios) |
|
||||
|
|
||||
return readPasswordLine(passwordReader(fd)) |
|
||||
} |
|
@ -1,12 +0,0 @@ |
|||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// +build darwin dragonfly freebsd netbsd openbsd
|
|
||||
|
|
||||
package terminal |
|
||||
|
|
||||
import "golang.org/x/sys/unix" |
|
||||
|
|
||||
const ioctlReadTermios = unix.TIOCGETA |
|
||||
const ioctlWriteTermios = unix.TIOCSETA |
|
@ -1,10 +0,0 @@ |
|||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
package terminal |
|
||||
|
|
||||
import "golang.org/x/sys/unix" |
|
||||
|
|
||||
const ioctlReadTermios = unix.TCGETS |
|
||||
const ioctlWriteTermios = unix.TCSETS |
|
@ -1,58 +0,0 @@ |
|||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
|
||||
// commonly found on UNIX systems.
|
|
||||
//
|
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||
//
|
|
||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||
// if err != nil {
|
|
||||
// panic(err)
|
|
||||
// }
|
|
||||
// defer terminal.Restore(0, oldState)
|
|
||||
package terminal |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"runtime" |
|
||||
) |
|
||||
|
|
||||
type State struct{} |
|
||||
|
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||
func IsTerminal(fd int) bool { |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||
// restored.
|
|
||||
func MakeRaw(fd int) (*State, error) { |
|
||||
return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) |
|
||||
} |
|
||||
|
|
||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||
// restore the terminal after a signal.
|
|
||||
func GetState(fd int) (*State, error) { |
|
||||
return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) |
|
||||
} |
|
||||
|
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||
// previous state.
|
|
||||
func Restore(fd int, state *State) error { |
|
||||
return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) |
|
||||
} |
|
||||
|
|
||||
// GetSize returns the dimensions of the given terminal.
|
|
||||
func GetSize(fd int) (width, height int, err error) { |
|
||||
return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) |
|
||||
} |
|
||||
|
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||
// returned does not include the \n.
|
|
||||
func ReadPassword(fd int) ([]byte, error) { |
|
||||
return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) |
|
||||
} |
|
@ -1,124 +0,0 @@ |
|||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// +build solaris
|
|
||||
|
|
||||
package terminal // import "golang.org/x/crypto/ssh/terminal"
|
|
||||
|
|
||||
import ( |
|
||||
"golang.org/x/sys/unix" |
|
||||
"io" |
|
||||
"syscall" |
|
||||
) |
|
||||
|
|
||||
// State contains the state of a terminal.
|
|
||||
type State struct { |
|
||||
termios unix.Termios |
|
||||
} |
|
||||
|
|
||||
// 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 |
|
||||
} |
|
||||
|
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||
// returned does not include the \n.
|
|
||||
func ReadPassword(fd int) ([]byte, error) { |
|
||||
// see also: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libast/common/uwin/getpass.c
|
|
||||
val, err := unix.IoctlGetTermios(fd, unix.TCGETS) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
oldState := *val |
|
||||
|
|
||||
newState := oldState |
|
||||
newState.Lflag &^= syscall.ECHO |
|
||||
newState.Lflag |= syscall.ICANON | syscall.ISIG |
|
||||
newState.Iflag |= syscall.ICRNL |
|
||||
err = unix.IoctlSetTermios(fd, unix.TCSETS, &newState) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
defer unix.IoctlSetTermios(fd, unix.TCSETS, &oldState) |
|
||||
|
|
||||
var buf [16]byte |
|
||||
var ret []byte |
|
||||
for { |
|
||||
n, err := syscall.Read(fd, buf[:]) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
if n == 0 { |
|
||||
if len(ret) == 0 { |
|
||||
return nil, io.EOF |
|
||||
} |
|
||||
break |
|
||||
} |
|
||||
if buf[n-1] == '\n' { |
|
||||
n-- |
|
||||
} |
|
||||
ret = append(ret, buf[:n]...) |
|
||||
if n < len(buf) { |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return ret, nil |
|
||||
} |
|
||||
|
|
||||
// MakeRaw puts the terminal connected to the given file descriptor into raw
|
|
||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||
// restored.
|
|
||||
// see http://cr.illumos.org/~webrev/andy_js/1060/
|
|
||||
func MakeRaw(fd int) (*State, error) { |
|
||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
oldState := State{termios: *termios} |
|
||||
|
|
||||
termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON |
|
||||
termios.Oflag &^= unix.OPOST |
|
||||
termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN |
|
||||
termios.Cflag &^= unix.CSIZE | unix.PARENB |
|
||||
termios.Cflag |= unix.CS8 |
|
||||
termios.Cc[unix.VMIN] = 1 |
|
||||
termios.Cc[unix.VTIME] = 0 |
|
||||
|
|
||||
if err := unix.IoctlSetTermios(fd, unix.TCSETS, termios); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return &oldState, nil |
|
||||
} |
|
||||
|
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||
// previous state.
|
|
||||
func Restore(fd int, oldState *State) error { |
|
||||
return unix.IoctlSetTermios(fd, unix.TCSETS, &oldState.termios) |
|
||||
} |
|
||||
|
|
||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||
// restore the terminal after a signal.
|
|
||||
func GetState(fd int) (*State, error) { |
|
||||
termios, err := unix.IoctlGetTermios(fd, unix.TCGETS) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
return &State{termios: *termios}, nil |
|
||||
} |
|
||||
|
|
||||
// GetSize returns the dimensions of the given terminal.
|
|
||||
func GetSize(fd int) (width, height int, err error) { |
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ) |
|
||||
if err != nil { |
|
||||
return 0, 0, err |
|
||||
} |
|
||||
return int(ws.Col), int(ws.Row), nil |
|
||||
} |
|
@ -1,103 +0,0 @@ |
|||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// +build windows
|
|
||||
|
|
||||
// Package terminal provides support functions for dealing with terminals, as
|
|
||||
// commonly found on UNIX systems.
|
|
||||
//
|
|
||||
// Putting a terminal into raw mode is the most common requirement:
|
|
||||
//
|
|
||||
// oldState, err := terminal.MakeRaw(0)
|
|
||||
// if err != nil {
|
|
||||
// panic(err)
|
|
||||
// }
|
|
||||
// defer terminal.Restore(0, oldState)
|
|
||||
package terminal |
|
||||
|
|
||||
import ( |
|
||||
"os" |
|
||||
|
|
||||
"golang.org/x/sys/windows" |
|
||||
) |
|
||||
|
|
||||
type State struct { |
|
||||
mode uint32 |
|
||||
} |
|
||||
|
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
|
||||
func IsTerminal(fd int) bool { |
|
||||
var st uint32 |
|
||||
err := windows.GetConsoleMode(windows.Handle(fd), &st) |
|
||||
return err == nil |
|
||||
} |
|
||||
|
|
||||
// MakeRaw put the terminal connected to the given file descriptor into raw
|
|
||||
// mode and returns the previous state of the terminal so that it can be
|
|
||||
// restored.
|
|
||||
func MakeRaw(fd int) (*State, error) { |
|
||||
var st uint32 |
|
||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) |
|
||||
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
return &State{st}, nil |
|
||||
} |
|
||||
|
|
||||
// GetState returns the current state of a terminal which may be useful to
|
|
||||
// restore the terminal after a signal.
|
|
||||
func GetState(fd int) (*State, error) { |
|
||||
var st uint32 |
|
||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
return &State{st}, nil |
|
||||
} |
|
||||
|
|
||||
// Restore restores the terminal connected to the given file descriptor to a
|
|
||||
// previous state.
|
|
||||
func Restore(fd int, state *State) error { |
|
||||
return windows.SetConsoleMode(windows.Handle(fd), state.mode) |
|
||||
} |
|
||||
|
|
||||
// GetSize returns the dimensions of the given terminal.
|
|
||||
func GetSize(fd int) (width, height int, err error) { |
|
||||
var info windows.ConsoleScreenBufferInfo |
|
||||
if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &info); err != nil { |
|
||||
return 0, 0, err |
|
||||
} |
|
||||
return int(info.Size.X), int(info.Size.Y), nil |
|
||||
} |
|
||||
|
|
||||
// ReadPassword reads a line of input from a terminal without local echo. This
|
|
||||
// is commonly used for inputting passwords and other sensitive data. The slice
|
|
||||
// returned does not include the \n.
|
|
||||
func ReadPassword(fd int) ([]byte, error) { |
|
||||
var st uint32 |
|
||||
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
old := st |
|
||||
|
|
||||
st &^= (windows.ENABLE_ECHO_INPUT) |
|
||||
st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) |
|
||||
if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
defer windows.SetConsoleMode(windows.Handle(fd), old) |
|
||||
|
|
||||
var h windows.Handle |
|
||||
p, _ := windows.GetCurrentProcess() |
|
||||
if err := windows.DuplicateHandle(p, windows.Handle(fd), p, &h, 0, false, windows.DUPLICATE_SAME_ACCESS); err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
|
|
||||
f := os.NewFile(uintptr(h), "stdin") |
|
||||
defer f.Close() |
|
||||
return readPasswordLine(f) |
|
||||
} |
|
@ -1,27 +0,0 @@ |
|||||
Copyright (c) 2009 The Go Authors. All rights reserved. |
|
||||
|
|
||||
Redistribution and use in source and binary forms, with or without |
|
||||
modification, are permitted provided that the following conditions are |
|
||||
met: |
|
||||
|
|
||||
* Redistributions of source code must retain the above copyright |
|
||||
notice, this list of conditions and the following disclaimer. |
|
||||
* Redistributions in binary form must reproduce the above |
|
||||
copyright notice, this list of conditions and the following disclaimer |
|
||||
in the documentation and/or other materials provided with the |
|
||||
distribution. |
|
||||
* Neither the name of Google Inc. nor the names of its |
|
||||
contributors may be used to endorse or promote products derived from |
|
||||
this software without specific prior written permission. |
|
||||
|
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
@ -1,22 +0,0 @@ |
|||||
Additional IP Rights Grant (Patents) |
|
||||
|
|
||||
"This implementation" means the copyrightable works distributed by |
|
||||
Google as part of the Go project. |
|
||||
|
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive, |
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section) |
|
||||
patent license to make, have made, use, offer to sell, sell, import, |
|
||||
transfer and otherwise run, modify and propagate the contents of this |
|
||||
implementation of Go, where such license applies only to those patent |
|
||||
claims, both currently owned or controlled by Google and acquired in |
|
||||
the future, licensable by Google that are necessarily infringed by this |
|
||||
implementation of Go. This grant does not include claims that would be |
|
||||
infringed only as a consequence of further modification of this |
|
||||
implementation. If you or your agent or exclusive licensee institute or |
|
||||
order or agree to the institution of patent litigation against any |
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging |
|
||||
that this implementation of Go or any code incorporated within this |
|
||||
implementation of Go constitutes direct or contributory patent |
|
||||
infringement, or inducement of patent infringement, then any patent |
|
||||
rights granted to you under this License for this implementation of Go |
|
||||
shall terminate as of the date such litigation is filed. |
|
@ -1,173 +0,0 @@ |
|||||
# Building `sys/unix` |
|
||||
|
|
||||
The sys/unix package provides access to the raw system call interface of the |
|
||||
underlying operating system. See: https://godoc.org/golang.org/x/sys/unix |
|
||||
|
|
||||
Porting Go to a new architecture/OS combination or adding syscalls, types, or |
|
||||
constants to an existing architecture/OS pair requires some manual effort; |
|
||||
however, there are tools that automate much of the process. |
|
||||
|
|
||||
## Build Systems |
|
||||
|
|
||||
There are currently two ways we generate the necessary files. We are currently |
|
||||
migrating the build system to use containers so the builds are reproducible. |
|
||||
This is being done on an OS-by-OS basis. Please update this documentation as |
|
||||
components of the build system change. |
|
||||
|
|
||||
### Old Build System (currently for `GOOS != "Linux" || GOARCH == "sparc64"`) |
|
||||
|
|
||||
The old build system generates the Go files based on the C header files |
|
||||
present on your system. This means that files |
|
||||
for a given GOOS/GOARCH pair must be generated on a system with that OS and |
|
||||
architecture. This also means that the generated code can differ from system |
|
||||
to system, based on differences in the header files. |
|
||||
|
|
||||
To avoid this, if you are using the old build system, only generate the Go |
|
||||
files on an installation with unmodified header files. It is also important to |
|
||||
keep track of which version of the OS the files were generated from (ex. |
|
||||
Darwin 14 vs Darwin 15). This makes it easier to track the progress of changes |
|
||||
and have each OS upgrade correspond to a single change. |
|
||||
|
|
||||
To build the files for your current OS and architecture, make sure GOOS and |
|
||||
GOARCH are set correctly and run `mkall.sh`. This will generate the files for |
|
||||
your specific system. Running `mkall.sh -n` shows the commands that will be run. |
|
||||
|
|
||||
Requirements: bash, perl, go |
|
||||
|
|
||||
### New Build System (currently for `GOOS == "Linux" && GOARCH != "sparc64"`) |
|
||||
|
|
||||
The new build system uses a Docker container to generate the go files directly |
|
||||
from source checkouts of the kernel and various system libraries. This means |
|
||||
that on any platform that supports Docker, all the files using the new build |
|
||||
system can be generated at once, and generated files will not change based on |
|
||||
what the person running the scripts has installed on their computer. |
|
||||
|
|
||||
The OS specific files for the new build system are located in the `${GOOS}` |
|
||||
directory, and the build is coordinated by the `${GOOS}/mkall.go` program. When |
|
||||
the kernel or system library updates, modify the Dockerfile at |
|
||||
`${GOOS}/Dockerfile` to checkout the new release of the source. |
|
||||
|
|
||||
To build all the files under the new build system, you must be on an amd64/Linux |
|
||||
system and have your GOOS and GOARCH set accordingly. Running `mkall.sh` will |
|
||||
then generate all of the files for all of the GOOS/GOARCH pairs in the new build |
|
||||
system. Running `mkall.sh -n` shows the commands that will be run. |
|
||||
|
|
||||
Requirements: bash, perl, go, docker |
|
||||
|
|
||||
## Component files |
|
||||
|
|
||||
This section describes the various files used in the code generation process. |
|
||||
It also contains instructions on how to modify these files to add a new |
|
||||
architecture/OS or to add additional syscalls, types, or constants. Note that |
|
||||
if you are using the new build system, the scripts cannot be called normally. |
|
||||
They must be called from within the docker container. |
|
||||
|
|
||||
### asm files |
|
||||
|
|
||||
The hand-written assembly file at `asm_${GOOS}_${GOARCH}.s` implements system |
|
||||
call dispatch. There are three entry points: |
|
||||
``` |
|
||||
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr) |
|
||||
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) |
|
||||
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr) |
|
||||
``` |
|
||||
The first and second are the standard ones; they differ only in how many |
|
||||
arguments can be passed to the kernel. The third is for low-level use by the |
|
||||
ForkExec wrapper. Unlike the first two, it does not call into the scheduler to |
|
||||
let it know that a system call is running. |
|
||||
|
|
||||
When porting Go to an new architecture/OS, this file must be implemented for |
|
||||
each GOOS/GOARCH pair. |
|
||||
|
|
||||
### mksysnum |
|
||||
|
|
||||
Mksysnum is a script located at `${GOOS}/mksysnum.pl` (or `mksysnum_${GOOS}.pl` |
|
||||
for the old system). This script takes in a list of header files containing the |
|
||||
syscall number declarations and parses them to produce the corresponding list of |
|
||||
Go numeric constants. See `zsysnum_${GOOS}_${GOARCH}.go` for the generated |
|
||||
constants. |
|
||||
|
|
||||
Adding new syscall numbers is mostly done by running the build on a sufficiently |
|
||||
new installation of the target OS (or updating the source checkouts for the |
|
||||
new build system). However, depending on the OS, you make need to update the |
|
||||
parsing in mksysnum. |
|
||||
|
|
||||
### mksyscall.pl |
|
||||
|
|
||||
The `syscall.go`, `syscall_${GOOS}.go`, `syscall_${GOOS}_${GOARCH}.go` are |
|
||||
hand-written Go files which implement system calls (for unix, the specific OS, |
|
||||
or the specific OS/Architecture pair respectively) that need special handling |
|
||||
and list `//sys` comments giving prototypes for ones that can be generated. |
|
||||
|
|
||||
The mksyscall.pl script takes the `//sys` and `//sysnb` comments and converts |
|
||||
them into syscalls. This requires the name of the prototype in the comment to |
|
||||
match a syscall number in the `zsysnum_${GOOS}_${GOARCH}.go` file. The function |
|
||||
prototype can be exported (capitalized) or not. |
|
||||
|
|
||||
Adding a new syscall often just requires adding a new `//sys` function prototype |
|
||||
with the desired arguments and a capitalized name so it is exported. However, if |
|
||||
you want the interface to the syscall to be different, often one will make an |
|
||||
unexported `//sys` prototype, an then write a custom wrapper in |
|
||||
`syscall_${GOOS}.go`. |
|
||||
|
|
||||
### types files |
|
||||
|
|
||||
For each OS, there is a hand-written Go file at `${GOOS}/types.go` (or |
|
||||
`types_${GOOS}.go` on the old system). This file includes standard C headers and |
|
||||
creates Go type aliases to the corresponding C types. The file is then fed |
|
||||
through godef to get the Go compatible definitions. Finally, the generated code |
|
||||
is fed though mkpost.go to format the code correctly and remove any hidden or |
|
||||
private identifiers. This cleaned-up code is written to |
|
||||
`ztypes_${GOOS}_${GOARCH}.go`. |
|
||||
|
|
||||
The hardest part about preparing this file is figuring out which headers to |
|
||||
include and which symbols need to be `#define`d to get the actual data |
|
||||
structures that pass through to the kernel system calls. Some C libraries |
|
||||
preset alternate versions for binary compatibility and translate them on the |
|
||||
way in and out of system calls, but there is almost always a `#define` that can |
|
||||
get the real ones. |
|
||||
See `types_darwin.go` and `linux/types.go` for examples. |
|
||||
|
|
||||
To add a new type, add in the necessary include statement at the top of the |
|
||||
file (if it is not already there) and add in a type alias line. Note that if |
|
||||
your type is significantly different on different architectures, you may need |
|
||||
some `#if/#elif` macros in your include statements. |
|
||||
|
|
||||
### mkerrors.sh |
|
||||
|
|
||||
This script is used to generate the system's various constants. This doesn't |
|
||||
just include the error numbers and error strings, but also the signal numbers |
|
||||
an a wide variety of miscellaneous constants. The constants come from the list |
|
||||
of include files in the `includes_${uname}` variable. A regex then picks out |
|
||||
the desired `#define` statements, and generates the corresponding Go constants. |
|
||||
The error numbers and strings are generated from `#include <errno.h>`, and the |
|
||||
signal numbers and strings are generated from `#include <signal.h>`. All of |
|
||||
these constants are written to `zerrors_${GOOS}_${GOARCH}.go` via a C program, |
|
||||
`_errors.c`, which prints out all the constants. |
|
||||
|
|
||||
To add a constant, add the header that includes it to the appropriate variable. |
|
||||
Then, edit the regex (if necessary) to match the desired constant. Avoid making |
|
||||
the regex too broad to avoid matching unintended constants. |
|
||||
|
|
||||
|
|
||||
## Generated files |
|
||||
|
|
||||
### `zerror_${GOOS}_${GOARCH}.go` |
|
||||
|
|
||||
A file containing all of the system's generated error numbers, error strings, |
|
||||
signal numbers, and constants. Generated by `mkerrors.sh` (see above). |
|
||||
|
|
||||
### `zsyscall_${GOOS}_${GOARCH}.go` |
|
||||
|
|
||||
A file containing all the generated syscalls for a specific GOOS and GOARCH. |
|
||||
Generated by `mksyscall.pl` (see above). |
|
||||
|
|
||||
### `zsysnum_${GOOS}_${GOARCH}.go` |
|
||||
|
|
||||
A list of numeric constants for all the syscall number of the specific GOOS |
|
||||
and GOARCH. Generated by mksysnum (see above). |
|
||||
|
|
||||
### `ztypes_${GOOS}_${GOARCH}.go` |
|
||||
|
|
||||
A file containing Go types for passing into (or returning from) syscalls. |
|
||||
Generated by godefs and the types file (see above). |
|
@ -1,124 +0,0 @@ |
|||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// CPU affinity functions
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
import ( |
|
||||
"unsafe" |
|
||||
) |
|
||||
|
|
||||
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS |
|
||||
|
|
||||
// CPUSet represents a CPU affinity mask.
|
|
||||
type CPUSet [cpuSetSize]cpuMask |
|
||||
|
|
||||
func schedAffinity(trap uintptr, pid int, set *CPUSet) error { |
|
||||
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set))) |
|
||||
if e != 0 { |
|
||||
return errnoErr(e) |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
|
|
||||
// If pid is 0 the calling thread is used.
|
|
||||
func SchedGetaffinity(pid int, set *CPUSet) error { |
|
||||
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set) |
|
||||
} |
|
||||
|
|
||||
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
|
|
||||
// If pid is 0 the calling thread is used.
|
|
||||
func SchedSetaffinity(pid int, set *CPUSet) error { |
|
||||
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set) |
|
||||
} |
|
||||
|
|
||||
// Zero clears the set s, so that it contains no CPUs.
|
|
||||
func (s *CPUSet) Zero() { |
|
||||
for i := range s { |
|
||||
s[i] = 0 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func cpuBitsIndex(cpu int) int { |
|
||||
return cpu / _NCPUBITS |
|
||||
} |
|
||||
|
|
||||
func cpuBitsMask(cpu int) cpuMask { |
|
||||
return cpuMask(1 << (uint(cpu) % _NCPUBITS)) |
|
||||
} |
|
||||
|
|
||||
// Set adds cpu to the set s.
|
|
||||
func (s *CPUSet) Set(cpu int) { |
|
||||
i := cpuBitsIndex(cpu) |
|
||||
if i < len(s) { |
|
||||
s[i] |= cpuBitsMask(cpu) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Clear removes cpu from the set s.
|
|
||||
func (s *CPUSet) Clear(cpu int) { |
|
||||
i := cpuBitsIndex(cpu) |
|
||||
if i < len(s) { |
|
||||
s[i] &^= cpuBitsMask(cpu) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// IsSet reports whether cpu is in the set s.
|
|
||||
func (s *CPUSet) IsSet(cpu int) bool { |
|
||||
i := cpuBitsIndex(cpu) |
|
||||
if i < len(s) { |
|
||||
return s[i]&cpuBitsMask(cpu) != 0 |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
// Count returns the number of CPUs in the set s.
|
|
||||
func (s *CPUSet) Count() int { |
|
||||
c := 0 |
|
||||
for _, b := range s { |
|
||||
c += onesCount64(uint64(b)) |
|
||||
} |
|
||||
return c |
|
||||
} |
|
||||
|
|
||||
// onesCount64 is a copy of Go 1.9's math/bits.OnesCount64.
|
|
||||
// Once this package can require Go 1.9, we can delete this
|
|
||||
// and update the caller to use bits.OnesCount64.
|
|
||||
func onesCount64(x uint64) int { |
|
||||
const m0 = 0x5555555555555555 // 01010101 ...
|
|
||||
const m1 = 0x3333333333333333 // 00110011 ...
|
|
||||
const m2 = 0x0f0f0f0f0f0f0f0f // 00001111 ...
|
|
||||
const m3 = 0x00ff00ff00ff00ff // etc.
|
|
||||
const m4 = 0x0000ffff0000ffff |
|
||||
|
|
||||
// Implementation: Parallel summing of adjacent bits.
|
|
||||
// See "Hacker's Delight", Chap. 5: Counting Bits.
|
|
||||
// The following pattern shows the general approach:
|
|
||||
//
|
|
||||
// x = x>>1&(m0&m) + x&(m0&m)
|
|
||||
// x = x>>2&(m1&m) + x&(m1&m)
|
|
||||
// x = x>>4&(m2&m) + x&(m2&m)
|
|
||||
// x = x>>8&(m3&m) + x&(m3&m)
|
|
||||
// x = x>>16&(m4&m) + x&(m4&m)
|
|
||||
// x = x>>32&(m5&m) + x&(m5&m)
|
|
||||
// return int(x)
|
|
||||
//
|
|
||||
// Masking (& operations) can be left away when there's no
|
|
||||
// danger that a field's sum will carry over into the next
|
|
||||
// field: Since the result cannot be > 64, 8 bits is enough
|
|
||||
// and we can ignore the masks for the shifts by 8 and up.
|
|
||||
// Per "Hacker's Delight", the first line can be simplified
|
|
||||
// more, but it saves at best one instruction, so we leave
|
|
||||
// it alone for clarity.
|
|
||||
const m = 1<<64 - 1 |
|
||||
x = x>>1&(m0&m) + x&(m0&m) |
|
||||
x = x>>2&(m1&m) + x&(m1&m) |
|
||||
x = (x>>4 + x) & (m2 & m) |
|
||||
x += x >> 8 |
|
||||
x += x >> 16 |
|
||||
x += x >> 32 |
|
||||
return int(x) & (1<<7 - 1) |
|
||||
} |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for 386, Darwin |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for AMD64, Darwin |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,30 +0,0 @@ |
|||||
// Copyright 2015 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
// +build arm,darwin |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for ARM, Darwin |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
B syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·RawSyscall6(SB) |
|
@ -1,30 +0,0 @@ |
|||||
// Copyright 2015 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
// +build arm64,darwin |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for AMD64, Darwin |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
B syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
B syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104 |
|
||||
B syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
B syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
B syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for AMD64, DragonFly |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for 386, FreeBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for AMD64, FreeBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2012 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for ARM, FreeBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
B syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·RawSyscall6(SB) |
|
@ -1,65 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for 386, Linux |
|
||||
// |
|
||||
|
|
||||
// See ../runtime/sys_linux_386.s for the reason why we always use int 0x80 |
|
||||
// instead of the glibc-specific "CALL 0x10(GS)". |
|
||||
#define INVOKE_SYSCALL INT $0x80 |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-24 |
|
||||
CALL runtime·entersyscall(SB) |
|
||||
MOVL trap+0(FP), AX // syscall entry |
|
||||
MOVL a1+4(FP), BX |
|
||||
MOVL a2+8(FP), CX |
|
||||
MOVL a3+12(FP), DX |
|
||||
MOVL $0, SI |
|
||||
MOVL $0, DI |
|
||||
INVOKE_SYSCALL |
|
||||
MOVL AX, r1+16(FP) |
|
||||
MOVL DX, r2+20(FP) |
|
||||
CALL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-24 |
|
||||
MOVL trap+0(FP), AX // syscall entry |
|
||||
MOVL a1+4(FP), BX |
|
||||
MOVL a2+8(FP), CX |
|
||||
MOVL a3+12(FP), DX |
|
||||
MOVL $0, SI |
|
||||
MOVL $0, DI |
|
||||
INVOKE_SYSCALL |
|
||||
MOVL AX, r1+16(FP) |
|
||||
MOVL DX, r2+20(FP) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·socketcall(SB),NOSPLIT,$0-36 |
|
||||
JMP syscall·socketcall(SB) |
|
||||
|
|
||||
TEXT ·rawsocketcall(SB),NOSPLIT,$0-36 |
|
||||
JMP syscall·rawsocketcall(SB) |
|
||||
|
|
||||
TEXT ·seek(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·seek(SB) |
|
@ -1,57 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for AMD64, Linux |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
CALL runtime·entersyscall(SB) |
|
||||
MOVQ a1+8(FP), DI |
|
||||
MOVQ a2+16(FP), SI |
|
||||
MOVQ a3+24(FP), DX |
|
||||
MOVQ $0, R10 |
|
||||
MOVQ $0, R8 |
|
||||
MOVQ $0, R9 |
|
||||
MOVQ trap+0(FP), AX // syscall entry |
|
||||
SYSCALL |
|
||||
MOVQ AX, r1+32(FP) |
|
||||
MOVQ DX, r2+40(FP) |
|
||||
CALL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
MOVQ a1+8(FP), DI |
|
||||
MOVQ a2+16(FP), SI |
|
||||
MOVQ a3+24(FP), DX |
|
||||
MOVQ $0, R10 |
|
||||
MOVQ $0, R8 |
|
||||
MOVQ $0, R9 |
|
||||
MOVQ trap+0(FP), AX // syscall entry |
|
||||
SYSCALL |
|
||||
MOVQ AX, r1+32(FP) |
|
||||
MOVQ DX, r2+40(FP) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·gettimeofday(SB),NOSPLIT,$0-16 |
|
||||
JMP syscall·gettimeofday(SB) |
|
@ -1,56 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for arm, Linux |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-24 |
|
||||
BL runtime·entersyscall(SB) |
|
||||
MOVW trap+0(FP), R7 |
|
||||
MOVW a1+4(FP), R0 |
|
||||
MOVW a2+8(FP), R1 |
|
||||
MOVW a3+12(FP), R2 |
|
||||
MOVW $0, R3 |
|
||||
MOVW $0, R4 |
|
||||
MOVW $0, R5 |
|
||||
SWI $0 |
|
||||
MOVW R0, r1+16(FP) |
|
||||
MOVW $0, R0 |
|
||||
MOVW R0, r2+20(FP) |
|
||||
BL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-24 |
|
||||
MOVW trap+0(FP), R7 // syscall entry |
|
||||
MOVW a1+4(FP), R0 |
|
||||
MOVW a2+8(FP), R1 |
|
||||
MOVW a3+12(FP), R2 |
|
||||
SWI $0 |
|
||||
MOVW R0, r1+16(FP) |
|
||||
MOVW $0, R0 |
|
||||
MOVW R0, r2+20(FP) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·seek(SB),NOSPLIT,$0-28 |
|
||||
B syscall·seek(SB) |
|
@ -1,52 +0,0 @@ |
|||||
// Copyright 2015 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build linux |
|
||||
// +build arm64 |
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
B syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
B syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
BL runtime·entersyscall(SB) |
|
||||
MOVD a1+8(FP), R0 |
|
||||
MOVD a2+16(FP), R1 |
|
||||
MOVD a3+24(FP), R2 |
|
||||
MOVD $0, R3 |
|
||||
MOVD $0, R4 |
|
||||
MOVD $0, R5 |
|
||||
MOVD trap+0(FP), R8 // syscall entry |
|
||||
SVC |
|
||||
MOVD R0, r1+32(FP) // r1 |
|
||||
MOVD R1, r2+40(FP) // r2 |
|
||||
BL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
B syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
B syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
MOVD a1+8(FP), R0 |
|
||||
MOVD a2+16(FP), R1 |
|
||||
MOVD a3+24(FP), R2 |
|
||||
MOVD $0, R3 |
|
||||
MOVD $0, R4 |
|
||||
MOVD $0, R5 |
|
||||
MOVD trap+0(FP), R8 // syscall entry |
|
||||
SVC |
|
||||
MOVD R0, r1+32(FP) |
|
||||
MOVD R1, r2+40(FP) |
|
||||
RET |
|
@ -1,56 +0,0 @@ |
|||||
// Copyright 2015 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build linux |
|
||||
// +build mips64 mips64le |
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for mips64, Linux |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
JAL runtime·entersyscall(SB) |
|
||||
MOVV a1+8(FP), R4 |
|
||||
MOVV a2+16(FP), R5 |
|
||||
MOVV a3+24(FP), R6 |
|
||||
MOVV R0, R7 |
|
||||
MOVV R0, R8 |
|
||||
MOVV R0, R9 |
|
||||
MOVV trap+0(FP), R2 // syscall entry |
|
||||
SYSCALL |
|
||||
MOVV R2, r1+32(FP) |
|
||||
MOVV R3, r2+40(FP) |
|
||||
JAL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
MOVV a1+8(FP), R4 |
|
||||
MOVV a2+16(FP), R5 |
|
||||
MOVV a3+24(FP), R6 |
|
||||
MOVV R0, R7 |
|
||||
MOVV R0, R8 |
|
||||
MOVV R0, R9 |
|
||||
MOVV trap+0(FP), R2 // syscall entry |
|
||||
SYSCALL |
|
||||
MOVV R2, r1+32(FP) |
|
||||
MOVV R3, r2+40(FP) |
|
||||
RET |
|
@ -1,54 +0,0 @@ |
|||||
// Copyright 2016 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build linux |
|
||||
// +build mips mipsle |
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for mips, Linux |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-24 |
|
||||
JAL runtime·entersyscall(SB) |
|
||||
MOVW a1+4(FP), R4 |
|
||||
MOVW a2+8(FP), R5 |
|
||||
MOVW a3+12(FP), R6 |
|
||||
MOVW R0, R7 |
|
||||
MOVW trap+0(FP), R2 // syscall entry |
|
||||
SYSCALL |
|
||||
MOVW R2, r1+16(FP) // r1 |
|
||||
MOVW R3, r2+20(FP) // r2 |
|
||||
JAL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-24 |
|
||||
MOVW a1+4(FP), R4 |
|
||||
MOVW a2+8(FP), R5 |
|
||||
MOVW a3+12(FP), R6 |
|
||||
MOVW trap+0(FP), R2 // syscall entry |
|
||||
SYSCALL |
|
||||
MOVW R2, r1+16(FP) |
|
||||
MOVW R3, r2+20(FP) |
|
||||
RET |
|
@ -1,56 +0,0 @@ |
|||||
// Copyright 2014 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build linux |
|
||||
// +build ppc64 ppc64le |
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for ppc64, Linux |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
BR syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
BR syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
BL runtime·entersyscall(SB) |
|
||||
MOVD a1+8(FP), R3 |
|
||||
MOVD a2+16(FP), R4 |
|
||||
MOVD a3+24(FP), R5 |
|
||||
MOVD R0, R6 |
|
||||
MOVD R0, R7 |
|
||||
MOVD R0, R8 |
|
||||
MOVD trap+0(FP), R9 // syscall entry |
|
||||
SYSCALL R9 |
|
||||
MOVD R3, r1+32(FP) |
|
||||
MOVD R4, r2+40(FP) |
|
||||
BL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
BR syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
BR syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
MOVD a1+8(FP), R3 |
|
||||
MOVD a2+16(FP), R4 |
|
||||
MOVD a3+24(FP), R5 |
|
||||
MOVD R0, R6 |
|
||||
MOVD R0, R7 |
|
||||
MOVD R0, R8 |
|
||||
MOVD trap+0(FP), R9 // syscall entry |
|
||||
SYSCALL R9 |
|
||||
MOVD R3, r1+32(FP) |
|
||||
MOVD R4, r2+40(FP) |
|
||||
RET |
|
@ -1,56 +0,0 @@ |
|||||
// Copyright 2016 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build s390x |
|
||||
// +build linux |
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for s390x, Linux |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
BR syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
BR syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
BL runtime·entersyscall(SB) |
|
||||
MOVD a1+8(FP), R2 |
|
||||
MOVD a2+16(FP), R3 |
|
||||
MOVD a3+24(FP), R4 |
|
||||
MOVD $0, R5 |
|
||||
MOVD $0, R6 |
|
||||
MOVD $0, R7 |
|
||||
MOVD trap+0(FP), R1 // syscall entry |
|
||||
SYSCALL |
|
||||
MOVD R2, r1+32(FP) |
|
||||
MOVD R3, r2+40(FP) |
|
||||
BL runtime·exitsyscall(SB) |
|
||||
RET |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
BR syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
BR syscall·RawSyscall6(SB) |
|
||||
|
|
||||
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48 |
|
||||
MOVD a1+8(FP), R2 |
|
||||
MOVD a2+16(FP), R3 |
|
||||
MOVD a3+24(FP), R4 |
|
||||
MOVD $0, R5 |
|
||||
MOVD $0, R6 |
|
||||
MOVD $0, R7 |
|
||||
MOVD trap+0(FP), R1 // syscall entry |
|
||||
SYSCALL |
|
||||
MOVD R2, r1+32(FP) |
|
||||
MOVD R3, r2+40(FP) |
|
||||
RET |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for 386, NetBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for AMD64, NetBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2013 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for ARM, NetBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
B syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for 386, OpenBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2009 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for AMD64, OpenBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104 |
|
||||
JMP syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56 |
|
||||
JMP syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80 |
|
||||
JMP syscall·RawSyscall6(SB) |
|
@ -1,29 +0,0 @@ |
|||||
// Copyright 2017 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System call support for ARM, OpenBSD |
|
||||
// |
|
||||
|
|
||||
// Just jump to package syscall's implementation for all these functions. |
|
||||
// The runtime may know about them. |
|
||||
|
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·Syscall(SB) |
|
||||
|
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·Syscall6(SB) |
|
||||
|
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
|
||||
B syscall·Syscall9(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
|
||||
B syscall·RawSyscall(SB) |
|
||||
|
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
|
||||
B syscall·RawSyscall6(SB) |
|
@ -1,17 +0,0 @@ |
|||||
// Copyright 2014 The Go Authors. All rights reserved. |
|
||||
// Use of this source code is governed by a BSD-style |
|
||||
// license that can be found in the LICENSE file. |
|
||||
|
|
||||
// +build !gccgo |
|
||||
|
|
||||
#include "textflag.h" |
|
||||
|
|
||||
// |
|
||||
// System calls for amd64, Solaris are implemented in runtime/syscall_solaris.go |
|
||||
// |
|
||||
|
|
||||
TEXT ·sysvicall6(SB),NOSPLIT,$0-88 |
|
||||
JMP syscall·sysvicall6(SB) |
|
||||
|
|
||||
TEXT ·rawSysvicall6(SB),NOSPLIT,$0-88 |
|
||||
JMP syscall·rawSysvicall6(SB) |
|
@ -1,35 +0,0 @@ |
|||||
// Copyright 2016 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// Bluetooth sockets and messages
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
// Bluetooth Protocols
|
|
||||
const ( |
|
||||
BTPROTO_L2CAP = 0 |
|
||||
BTPROTO_HCI = 1 |
|
||||
BTPROTO_SCO = 2 |
|
||||
BTPROTO_RFCOMM = 3 |
|
||||
BTPROTO_BNEP = 4 |
|
||||
BTPROTO_CMTP = 5 |
|
||||
BTPROTO_HIDP = 6 |
|
||||
BTPROTO_AVDTP = 7 |
|
||||
) |
|
||||
|
|
||||
const ( |
|
||||
HCI_CHANNEL_RAW = 0 |
|
||||
HCI_CHANNEL_USER = 1 |
|
||||
HCI_CHANNEL_MONITOR = 2 |
|
||||
HCI_CHANNEL_CONTROL = 3 |
|
||||
) |
|
||||
|
|
||||
// Socketoption Level
|
|
||||
const ( |
|
||||
SOL_BLUETOOTH = 0x112 |
|
||||
SOL_HCI = 0x0 |
|
||||
SOL_L2CAP = 0x6 |
|
||||
SOL_RFCOMM = 0x12 |
|
||||
SOL_SCO = 0x11 |
|
||||
) |
|
@ -1,195 +0,0 @@ |
|||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// +build freebsd
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
import ( |
|
||||
"errors" |
|
||||
"fmt" |
|
||||
) |
|
||||
|
|
||||
// Go implementation of C mostly found in /usr/src/sys/kern/subr_capability.c
|
|
||||
|
|
||||
const ( |
|
||||
// This is the version of CapRights this package understands. See C implementation for parallels.
|
|
||||
capRightsGoVersion = CAP_RIGHTS_VERSION_00 |
|
||||
capArSizeMin = CAP_RIGHTS_VERSION_00 + 2 |
|
||||
capArSizeMax = capRightsGoVersion + 2 |
|
||||
) |
|
||||
|
|
||||
var ( |
|
||||
bit2idx = []int{ |
|
||||
-1, 0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, |
|
||||
4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, |
|
||||
} |
|
||||
) |
|
||||
|
|
||||
func capidxbit(right uint64) int { |
|
||||
return int((right >> 57) & 0x1f) |
|
||||
} |
|
||||
|
|
||||
func rightToIndex(right uint64) (int, error) { |
|
||||
idx := capidxbit(right) |
|
||||
if idx < 0 || idx >= len(bit2idx) { |
|
||||
return -2, fmt.Errorf("index for right 0x%x out of range", right) |
|
||||
} |
|
||||
return bit2idx[idx], nil |
|
||||
} |
|
||||
|
|
||||
func caprver(right uint64) int { |
|
||||
return int(right >> 62) |
|
||||
} |
|
||||
|
|
||||
func capver(rights *CapRights) int { |
|
||||
return caprver(rights.Rights[0]) |
|
||||
} |
|
||||
|
|
||||
func caparsize(rights *CapRights) int { |
|
||||
return capver(rights) + 2 |
|
||||
} |
|
||||
|
|
||||
// CapRightsSet sets the permissions in setrights in rights.
|
|
||||
func CapRightsSet(rights *CapRights, setrights []uint64) error { |
|
||||
// This is essentially a copy of cap_rights_vset()
|
|
||||
if capver(rights) != CAP_RIGHTS_VERSION_00 { |
|
||||
return fmt.Errorf("bad rights version %d", capver(rights)) |
|
||||
} |
|
||||
|
|
||||
n := caparsize(rights) |
|
||||
if n < capArSizeMin || n > capArSizeMax { |
|
||||
return errors.New("bad rights size") |
|
||||
} |
|
||||
|
|
||||
for _, right := range setrights { |
|
||||
if caprver(right) != CAP_RIGHTS_VERSION_00 { |
|
||||
return errors.New("bad right version") |
|
||||
} |
|
||||
i, err := rightToIndex(right) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
if i >= n { |
|
||||
return errors.New("index overflow") |
|
||||
} |
|
||||
if capidxbit(rights.Rights[i]) != capidxbit(right) { |
|
||||
return errors.New("index mismatch") |
|
||||
} |
|
||||
rights.Rights[i] |= right |
|
||||
if capidxbit(rights.Rights[i]) != capidxbit(right) { |
|
||||
return errors.New("index mismatch (after assign)") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// CapRightsClear clears the permissions in clearrights from rights.
|
|
||||
func CapRightsClear(rights *CapRights, clearrights []uint64) error { |
|
||||
// This is essentially a copy of cap_rights_vclear()
|
|
||||
if capver(rights) != CAP_RIGHTS_VERSION_00 { |
|
||||
return fmt.Errorf("bad rights version %d", capver(rights)) |
|
||||
} |
|
||||
|
|
||||
n := caparsize(rights) |
|
||||
if n < capArSizeMin || n > capArSizeMax { |
|
||||
return errors.New("bad rights size") |
|
||||
} |
|
||||
|
|
||||
for _, right := range clearrights { |
|
||||
if caprver(right) != CAP_RIGHTS_VERSION_00 { |
|
||||
return errors.New("bad right version") |
|
||||
} |
|
||||
i, err := rightToIndex(right) |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
if i >= n { |
|
||||
return errors.New("index overflow") |
|
||||
} |
|
||||
if capidxbit(rights.Rights[i]) != capidxbit(right) { |
|
||||
return errors.New("index mismatch") |
|
||||
} |
|
||||
rights.Rights[i] &= ^(right & 0x01FFFFFFFFFFFFFF) |
|
||||
if capidxbit(rights.Rights[i]) != capidxbit(right) { |
|
||||
return errors.New("index mismatch (after assign)") |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// CapRightsIsSet checks whether all the permissions in setrights are present in rights.
|
|
||||
func CapRightsIsSet(rights *CapRights, setrights []uint64) (bool, error) { |
|
||||
// This is essentially a copy of cap_rights_is_vset()
|
|
||||
if capver(rights) != CAP_RIGHTS_VERSION_00 { |
|
||||
return false, fmt.Errorf("bad rights version %d", capver(rights)) |
|
||||
} |
|
||||
|
|
||||
n := caparsize(rights) |
|
||||
if n < capArSizeMin || n > capArSizeMax { |
|
||||
return false, errors.New("bad rights size") |
|
||||
} |
|
||||
|
|
||||
for _, right := range setrights { |
|
||||
if caprver(right) != CAP_RIGHTS_VERSION_00 { |
|
||||
return false, errors.New("bad right version") |
|
||||
} |
|
||||
i, err := rightToIndex(right) |
|
||||
if err != nil { |
|
||||
return false, err |
|
||||
} |
|
||||
if i >= n { |
|
||||
return false, errors.New("index overflow") |
|
||||
} |
|
||||
if capidxbit(rights.Rights[i]) != capidxbit(right) { |
|
||||
return false, errors.New("index mismatch") |
|
||||
} |
|
||||
if (rights.Rights[i] & right) != right { |
|
||||
return false, nil |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return true, nil |
|
||||
} |
|
||||
|
|
||||
func capright(idx uint64, bit uint64) uint64 { |
|
||||
return ((1 << (57 + idx)) | bit) |
|
||||
} |
|
||||
|
|
||||
// CapRightsInit returns a pointer to an initialised CapRights structure filled with rights.
|
|
||||
// See man cap_rights_init(3) and rights(4).
|
|
||||
func CapRightsInit(rights []uint64) (*CapRights, error) { |
|
||||
var r CapRights |
|
||||
r.Rights[0] = (capRightsGoVersion << 62) | capright(0, 0) |
|
||||
r.Rights[1] = capright(1, 0) |
|
||||
|
|
||||
err := CapRightsSet(&r, rights) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
return &r, nil |
|
||||
} |
|
||||
|
|
||||
// CapRightsLimit reduces the operations permitted on fd to at most those contained in rights.
|
|
||||
// The capability rights on fd can never be increased by CapRightsLimit.
|
|
||||
// See man cap_rights_limit(2) and rights(4).
|
|
||||
func CapRightsLimit(fd uintptr, rights *CapRights) error { |
|
||||
return capRightsLimit(int(fd), rights) |
|
||||
} |
|
||||
|
|
||||
// CapRightsGet returns a CapRights structure containing the operations permitted on fd.
|
|
||||
// See man cap_rights_get(3) and rights(4).
|
|
||||
func CapRightsGet(fd uintptr) (*CapRights, error) { |
|
||||
r, err := CapRightsInit(nil) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
err = capRightsGet(capRightsGoVersion, int(fd), r) |
|
||||
if err != nil { |
|
||||
return nil, err |
|
||||
} |
|
||||
return r, nil |
|
||||
} |
|
@ -1,13 +0,0 @@ |
|||||
// Copyright 2015 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
const ( |
|
||||
R_OK = 0x4 |
|
||||
W_OK = 0x2 |
|
||||
X_OK = 0x1 |
|
||||
) |
|
@ -1,24 +0,0 @@ |
|||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// Functions to access/create device major and minor numbers matching the
|
|
||||
// encoding used in Darwin's sys/types.h header.
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
// Major returns the major component of a Darwin device number.
|
|
||||
func Major(dev uint64) uint32 { |
|
||||
return uint32((dev >> 24) & 0xff) |
|
||||
} |
|
||||
|
|
||||
// Minor returns the minor component of a Darwin device number.
|
|
||||
func Minor(dev uint64) uint32 { |
|
||||
return uint32(dev & 0xffffff) |
|
||||
} |
|
||||
|
|
||||
// Mkdev returns a Darwin device number generated from the given major and minor
|
|
||||
// components.
|
|
||||
func Mkdev(major, minor uint32) uint64 { |
|
||||
return (uint64(major) << 24) | uint64(minor) |
|
||||
} |
|
@ -1,30 +0,0 @@ |
|||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// Functions to access/create device major and minor numbers matching the
|
|
||||
// encoding used in Dragonfly's sys/types.h header.
|
|
||||
//
|
|
||||
// The information below is extracted and adapted from sys/types.h:
|
|
||||
//
|
|
||||
// Minor gives a cookie instead of an index since in order to avoid changing the
|
|
||||
// meanings of bits 0-15 or wasting time and space shifting bits 16-31 for
|
|
||||
// devices that don't use them.
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
// Major returns the major component of a DragonFlyBSD device number.
|
|
||||
func Major(dev uint64) uint32 { |
|
||||
return uint32((dev >> 8) & 0xff) |
|
||||
} |
|
||||
|
|
||||
// Minor returns the minor component of a DragonFlyBSD device number.
|
|
||||
func Minor(dev uint64) uint32 { |
|
||||
return uint32(dev & 0xffff00ff) |
|
||||
} |
|
||||
|
|
||||
// Mkdev returns a DragonFlyBSD device number generated from the given major and
|
|
||||
// minor components.
|
|
||||
func Mkdev(major, minor uint32) uint64 { |
|
||||
return (uint64(major) << 8) | uint64(minor) |
|
||||
} |
|
@ -1,30 +0,0 @@ |
|||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// Functions to access/create device major and minor numbers matching the
|
|
||||
// encoding used in FreeBSD's sys/types.h header.
|
|
||||
//
|
|
||||
// The information below is extracted and adapted from sys/types.h:
|
|
||||
//
|
|
||||
// Minor gives a cookie instead of an index since in order to avoid changing the
|
|
||||
// meanings of bits 0-15 or wasting time and space shifting bits 16-31 for
|
|
||||
// devices that don't use them.
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
// Major returns the major component of a FreeBSD device number.
|
|
||||
func Major(dev uint64) uint32 { |
|
||||
return uint32((dev >> 8) & 0xff) |
|
||||
} |
|
||||
|
|
||||
// Minor returns the minor component of a FreeBSD device number.
|
|
||||
func Minor(dev uint64) uint32 { |
|
||||
return uint32(dev & 0xffff00ff) |
|
||||
} |
|
||||
|
|
||||
// Mkdev returns a FreeBSD device number generated from the given major and
|
|
||||
// minor components.
|
|
||||
func Mkdev(major, minor uint32) uint64 { |
|
||||
return (uint64(major) << 8) | uint64(minor) |
|
||||
} |
|
@ -1,42 +0,0 @@ |
|||||
// Copyright 2017 The Go Authors. All rights reserved.
|
|
||||
// Use of this source code is governed by a BSD-style
|
|
||||
// license that can be found in the LICENSE file.
|
|
||||
|
|
||||
// Functions to access/create device major and minor numbers matching the
|
|
||||
// encoding used by the Linux kernel and glibc.
|
|
||||
//
|
|
||||
// The information below is extracted and adapted from bits/sysmacros.h in the
|
|
||||
// glibc sources:
|
|
||||
//
|
|
||||
// dev_t in glibc is 64-bit, with 32-bit major and minor numbers. glibc's
|
|
||||
// default encoding is MMMM Mmmm mmmM MMmm, where M is a hex digit of the major
|
|
||||
// number and m is a hex digit of the minor number. This is backward compatible
|
|
||||
// with legacy systems where dev_t is 16 bits wide, encoded as MMmm. It is also
|
|
||||
// backward compatible with the Linux kernel, which for some architectures uses
|
|
||||
// 32-bit dev_t, encoded as mmmM MMmm.
|
|
||||
|
|
||||
package unix |
|
||||
|
|
||||
// Major returns the major component of a Linux device number.
|
|
||||
func Major(dev uint64) uint32 { |
|
||||
major := uint32((dev & 0x00000000000fff00) >> 8) |
|
||||
major |= uint32((dev & 0xfffff00000000000) >> 32) |
|
||||
return major |
|
||||
} |
|
||||
|
|
||||
// Minor returns the minor component of a Linux device number.
|
|
||||
func Minor(dev uint64) uint32 { |
|
||||
minor := uint32((dev & 0x00000000000000ff) >> 0) |
|
||||
minor |= uint32((dev & 0x00000ffffff00000) >> 12) |
|
||||
return minor |
|
||||
} |
|
||||
|
|
||||
// Mkdev returns a Linux device number generated from the given major and minor
|
|
||||
// components.
|
|
||||
func Mkdev(major, minor uint32) uint64 { |
|
||||
dev := (uint64(major) & 0x00000fff) << 8 |
|
||||
dev |= (uint64(major) & 0xfffff000) << 32 |
|
||||
dev |= (uint64(minor) & 0x000000ff) << 0 |
|
||||
dev |= (uint64(minor) & 0xffffff00) << 12 |
|
||||
return dev |
|
||||
} |
|
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue