Compare commits
No commits in common. "guimaster" and "fix-89" have entirely different histories.
|
@ -1 +0,0 @@
|
|||
github: [alexflint]
|
Binary file not shown.
Before Width: | Height: | Size: 9.8 KiB |
|
@ -1,37 +0,0 @@
|
|||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go: ['1.20', '1.21', '1.22']
|
||||
|
||||
steps:
|
||||
- id: go
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
run: go build -v .
|
||||
|
||||
- name: Test
|
||||
run: go test -v -coverprofile=profile.cov .
|
||||
|
||||
- name: Send coverage
|
||||
run: bash <(curl -s https://codecov.io/bash) -f profile.cov
|
|
@ -0,0 +1,16 @@
|
|||
language: go
|
||||
go:
|
||||
- "1.10"
|
||||
- "1.12"
|
||||
- tip
|
||||
env:
|
||||
- GO111MODULE=on # will only be used in go 1.11
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||
- bash test/compile_with_go110.sh
|
||||
- bash test/compile_with_go111.sh
|
||||
- bash test/compile_with_go111_inside_gopath.sh
|
11
Makefile
11
Makefile
|
@ -1,11 +0,0 @@
|
|||
all:
|
||||
@echo
|
||||
@echo
|
||||
|
||||
clean:
|
||||
rm -f go.*
|
||||
|
||||
redomod:
|
||||
rm -f go.*
|
||||
GO111MODULE= go mod init
|
||||
GO111MODULE= go mod tidy
|
451
README.md
451
README.md
|
@ -1,18 +1,14 @@
|
|||
<h1 align="center">
|
||||
<img src="./.github/banner.jpg" alt="go-arg" height="250px">
|
||||
<br>
|
||||
go-arg
|
||||
</br>
|
||||
</h1>
|
||||
<h4 align="center">Struct-based argument parsing for Go</h4>
|
||||
<p align="center">
|
||||
<a href="https://sourcegraph.com/github.com/alexflint/go-arg?badge"><img src="https://sourcegraph.com/github.com/alexflint/go-arg/-/badge.svg" alt="Sourcegraph"></a>
|
||||
<a href="https://pkg.go.dev/github.com/alexflint/go-arg"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square" alt="Documentation"></a>
|
||||
<a href="https://github.com/alexflint/go-arg/actions"><img src="https://github.com/alexflint/go-arg/workflows/Go/badge.svg" alt="Build Status"></a>
|
||||
<a href="https://codecov.io/gh/alexflint/go-arg"><img src="https://codecov.io/gh/alexflint/go-arg/branch/master/graph/badge.svg" alt="Coverage Status"></a>
|
||||
<a href="https://goreportcard.com/report/github.com/alexflint/go-arg"><img src="https://goreportcard.com/badge/github.com/alexflint/go-arg" alt="Go Report Card"></a>
|
||||
</p>
|
||||
<br>
|
||||
[![GoDoc](https://godoc.org/github.com/alexflint/go-arg?status.svg)](https://godoc.org/github.com/alexflint/go-arg)
|
||||
[![Build Status](https://travis-ci.org/alexflint/go-arg.svg?branch=master)](https://travis-ci.org/alexflint/go-arg)
|
||||
[![GolangCI](https://golangci.com/badges/github.com/alexflint/go-arg.svg)](https://golangci.com/r/github.com/alexflint/go-arg)
|
||||
[![Coverage Status](https://coveralls.io/repos/alexflint/go-arg/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexflint/go-arg?branch=master)
|
||||
[![Report Card](https://goreportcard.com/badge/github.com/alexflint/go-arg)](https://goreportcard.com/badge/github.com/alexflint/go-arg)
|
||||
|
||||
## Structured argument parsing for Go
|
||||
|
||||
```shell
|
||||
go get github.com/alexflint/go-arg
|
||||
```
|
||||
|
||||
Declare command line arguments for your program by defining a struct.
|
||||
|
||||
|
@ -30,12 +26,6 @@ $ ./example --foo=hello --bar
|
|||
hello true
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
```shell
|
||||
go get github.com/alexflint/go-arg
|
||||
```
|
||||
|
||||
### Required arguments
|
||||
|
||||
```go
|
||||
|
@ -64,7 +54,7 @@ fmt.Println("Input:", args.Input)
|
|||
fmt.Println("Output:", args.Output)
|
||||
```
|
||||
|
||||
```shell
|
||||
```
|
||||
$ ./example src.txt x.out y.out z.out
|
||||
Input: src.txt
|
||||
Output: [x.out y.out z.out]
|
||||
|
@ -80,12 +70,12 @@ arg.MustParse(&args)
|
|||
fmt.Println("Workers:", args.Workers)
|
||||
```
|
||||
|
||||
```shell
|
||||
```
|
||||
$ WORKERS=4 ./example
|
||||
Workers: 4
|
||||
```
|
||||
|
||||
```shell
|
||||
```
|
||||
$ WORKERS=4 ./example --workers=6
|
||||
Workers: 6
|
||||
```
|
||||
|
@ -100,12 +90,12 @@ arg.MustParse(&args)
|
|||
fmt.Println("Workers:", args.Workers)
|
||||
```
|
||||
|
||||
```shell
|
||||
```
|
||||
$ NUM_WORKERS=4 ./example
|
||||
Workers: 4
|
||||
```
|
||||
|
||||
You can provide multiple values in environment variables using commas:
|
||||
You can provide multiple values using the CSV (RFC 4180) format:
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
|
@ -115,55 +105,17 @@ arg.MustParse(&args)
|
|||
fmt.Println("Workers:", args.Workers)
|
||||
```
|
||||
|
||||
```shell
|
||||
```
|
||||
$ WORKERS='1,99' ./example
|
||||
Workers: [1 99]
|
||||
```
|
||||
|
||||
Command line arguments take precedence over environment variables:
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Workers int `arg:"--count,env:NUM_WORKERS"`
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
fmt.Println("Workers:", args.Workers)
|
||||
```
|
||||
|
||||
```shell
|
||||
$ NUM_WORKERS=6 ./example
|
||||
Workers: 6
|
||||
$ NUM_WORKERS=6 ./example --count 4
|
||||
Workers: 4
|
||||
```
|
||||
|
||||
Configuring a global environment variable name prefix is also possible:
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Workers int `arg:"--count,env:NUM_WORKERS"`
|
||||
}
|
||||
|
||||
p, err := arg.NewParser(arg.Config{
|
||||
EnvPrefix: "MYAPP_",
|
||||
}, &args)
|
||||
|
||||
p.MustParse(os.Args[1:])
|
||||
fmt.Println("Workers:", args.Workers)
|
||||
```
|
||||
|
||||
```shell
|
||||
$ MYAPP_NUM_WORKERS=6 ./example
|
||||
Workers: 6
|
||||
```
|
||||
|
||||
### Usage strings
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Input string `arg:"positional"`
|
||||
Output []string `arg:"positional"`
|
||||
Verbose bool `arg:"-v,--verbose" help:"verbosity level"`
|
||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||
Dataset string `help:"dataset to use"`
|
||||
Optimize int `arg:"-O" help:"optimization level"`
|
||||
}
|
||||
|
@ -190,38 +142,14 @@ Options:
|
|||
|
||||
```go
|
||||
var args struct {
|
||||
Foo string `default:"abc"`
|
||||
Foo string
|
||||
Bar bool
|
||||
}
|
||||
args.Foo = "default value"
|
||||
arg.MustParse(&args)
|
||||
```
|
||||
|
||||
Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value.
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Test string `arg:"-t,env:TEST" default:"something"`
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
```
|
||||
|
||||
#### Ignoring environment variables and/or default values
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Test string `arg:"-t,env:TEST" default:"something"`
|
||||
}
|
||||
|
||||
p, err := arg.NewParser(arg.Config{
|
||||
IgnoreEnv: true,
|
||||
IgnoreDefault: true,
|
||||
}, &args)
|
||||
|
||||
err = p.Parse(os.Args[1:])
|
||||
```
|
||||
|
||||
### Arguments with multiple values
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Database string
|
||||
|
@ -237,14 +165,12 @@ Fetching the following IDs from foo: [1 2 3]
|
|||
```
|
||||
|
||||
### Arguments that can be specified multiple times, mixed with positionals
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Commands []string `arg:"-c,separate"`
|
||||
Files []string `arg:"-f,separate"`
|
||||
Databases []string `arg:"positional"`
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
```
|
||||
|
||||
```shell
|
||||
|
@ -254,19 +180,22 @@ Files [file1 file2 file3]
|
|||
Databases [db1 db2 db3]
|
||||
```
|
||||
|
||||
### Arguments with keys and values
|
||||
|
||||
### Custom validation
|
||||
```go
|
||||
var args struct {
|
||||
UserIDs map[string]int
|
||||
Foo string
|
||||
Bar string
|
||||
}
|
||||
p := arg.MustParse(&args)
|
||||
if args.Foo == "" && args.Bar == "" {
|
||||
p.Fail("you must provide either --foo or --bar")
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
fmt.Println(args.UserIDs)
|
||||
```
|
||||
|
||||
```shell
|
||||
./example --userids john=123 mary=456
|
||||
map[john:123 mary:456]
|
||||
./example
|
||||
Usage: samples [--foo FOO] [--bar BAR]
|
||||
error: you must provide either --foo or --bar
|
||||
```
|
||||
|
||||
### Version strings
|
||||
|
@ -291,57 +220,12 @@ $ ./example --version
|
|||
someprogram 4.3.0
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> If a `--version` flag is defined in `args` or any subcommand, it overrides the built-in versioning.
|
||||
|
||||
### Custom validation
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Foo string
|
||||
Bar string
|
||||
}
|
||||
p := arg.MustParse(&args)
|
||||
if args.Foo == "" && args.Bar == "" {
|
||||
p.Fail("you must provide either --foo or --bar")
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
./example
|
||||
Usage: samples [--foo FOO] [--bar BAR]
|
||||
error: you must provide either --foo or --bar
|
||||
```
|
||||
|
||||
### Overriding option names
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Short string `arg:"-s"`
|
||||
Long string `arg:"--custom-long-option"`
|
||||
ShortAndLong string `arg:"-x,--my-option"`
|
||||
OnlyShort string `arg:"-o,--"`
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example --help
|
||||
Usage: example [-o ONLYSHORT] [--short SHORT] [--custom-long-option CUSTOM-LONG-OPTION] [--my-option MY-OPTION]
|
||||
|
||||
Options:
|
||||
--short SHORT, -s SHORT
|
||||
--custom-long-option CUSTOM-LONG-OPTION
|
||||
--my-option MY-OPTION, -x MY-OPTION
|
||||
-o ONLYSHORT
|
||||
--help, -h display this help and exit
|
||||
```
|
||||
|
||||
### Embedded structs
|
||||
|
||||
The fields of embedded structs are treated just like regular fields:
|
||||
|
||||
```go
|
||||
|
||||
type DatabaseOptions struct {
|
||||
Host string
|
||||
Username string
|
||||
|
@ -364,22 +248,6 @@ func main() {
|
|||
|
||||
As usual, any field tagged with `arg:"-"` is ignored.
|
||||
|
||||
### Supported types
|
||||
|
||||
The following types may be used as arguments:
|
||||
- built-in integer types: `int, int8, int16, int32, int64, byte, rune`
|
||||
- built-in floating point types: `float32, float64`
|
||||
- strings
|
||||
- booleans
|
||||
- URLs represented as `url.URL`
|
||||
- time durations represented as `time.Duration`
|
||||
- email addresses represented as `mail.Address`
|
||||
- MAC addresses represented as `net.HardwareAddr`
|
||||
- pointers to any of the above
|
||||
- slices of any of the above
|
||||
- maps using any of the above as keys and values
|
||||
- any type that implements `encoding.TextUnmarshaler`
|
||||
|
||||
### Custom parsing
|
||||
|
||||
Implement `encoding.TextUnmarshaler` to define your own parsing logic.
|
||||
|
@ -409,7 +277,6 @@ func main() {
|
|||
fmt.Printf("%#v\n", args.Name)
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example --name=foo.bar
|
||||
main.NameDotName{Head:"foo", Tail:"bar"}
|
||||
|
@ -440,13 +307,13 @@ func (n *NameDotName) MarshalText() ([]byte, error) {
|
|||
|
||||
func main() {
|
||||
var args struct {
|
||||
Name NameDotName `default:"file.txt"`
|
||||
Name NameDotName
|
||||
}
|
||||
args.Name = NameDotName{"file", "txt"} // set default value
|
||||
arg.MustParse(&args)
|
||||
fmt.Printf("%#v\n", args.Name)
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example --help
|
||||
Usage: test [--name NAME]
|
||||
|
@ -459,40 +326,8 @@ $ ./example
|
|||
main.NameDotName{Head:"file", Tail:"txt"}
|
||||
```
|
||||
|
||||
### Custom placeholders
|
||||
|
||||
Use the `placeholder` tag to control which placeholder text is used in the usage text.
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Input string `arg:"positional" placeholder:"SRC"`
|
||||
Output []string `arg:"positional" placeholder:"DST"`
|
||||
Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"`
|
||||
MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"`
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example -h
|
||||
Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]]
|
||||
|
||||
Positional arguments:
|
||||
SRC
|
||||
DST
|
||||
|
||||
Options:
|
||||
--optimize LEVEL, -O LEVEL
|
||||
optimization level
|
||||
--maxjobs N, -j N maximum number of simultaneous jobs
|
||||
--help, -h display this help and exit
|
||||
```
|
||||
|
||||
### Description strings
|
||||
|
||||
A descriptive message can be added at the top of the help text by implementing
|
||||
a `Description` function that returns a string.
|
||||
|
||||
```go
|
||||
type args struct {
|
||||
Foo string
|
||||
|
@ -518,37 +353,10 @@ Options:
|
|||
--help, -h display this help and exit
|
||||
```
|
||||
|
||||
Similarly an epilogue can be added at the end of the help text by implementing
|
||||
the `Epilogue` function.
|
||||
|
||||
```go
|
||||
type args struct {
|
||||
Foo string
|
||||
}
|
||||
|
||||
func (args) Epilogue() string {
|
||||
return "For more information visit github.com/alexflint/go-arg"
|
||||
}
|
||||
|
||||
func main() {
|
||||
var args args
|
||||
arg.MustParse(&args)
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example -h
|
||||
Usage: example [--foo FOO]
|
||||
|
||||
Options:
|
||||
--foo FOO
|
||||
--help, -h display this help and exit
|
||||
|
||||
For more information visit github.com/alexflint/go-arg
|
||||
```
|
||||
|
||||
### Subcommands
|
||||
|
||||
*Introduced in `v1.1.0`*
|
||||
|
||||
Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the `git` tool:
|
||||
```shell
|
||||
$ git checkout [arguments specific to checking out code]
|
||||
|
@ -597,199 +405,16 @@ Some additional rules apply when working with subcommands:
|
|||
* The `subcommand` tag can only be used with fields that are pointers to structs
|
||||
* Any struct that contains a subcommand must not contain any positionals
|
||||
|
||||
This package allows to have a program that accepts subcommands, but also does something else
|
||||
when no subcommands are specified.
|
||||
If on the other hand you want the program to terminate when no subcommands are specified,
|
||||
the recommended way is:
|
||||
|
||||
```go
|
||||
p := arg.MustParse(&args)
|
||||
if p.Subcommand() == nil {
|
||||
p.Fail("missing subcommand")
|
||||
}
|
||||
```
|
||||
|
||||
### Custom handling of --help and --version
|
||||
|
||||
The following reproduces the internal logic of `MustParse` for the simple case where
|
||||
you are not using subcommands or --version. This allows you to respond
|
||||
programatically to --help, and to any errors that come up.
|
||||
|
||||
```go
|
||||
var args struct {
|
||||
Something string
|
||||
}
|
||||
|
||||
p, err := arg.NewParser(arg.Config{}, &args)
|
||||
if err != nil {
|
||||
log.Fatalf("there was an error in the definition of the Go struct: %v", err)
|
||||
}
|
||||
|
||||
err = p.Parse(os.Args[1:])
|
||||
switch {
|
||||
case err == arg.ErrHelp: // indicates that user wrote "--help" on command line
|
||||
p.WriteHelp(os.Stdout)
|
||||
os.Exit(0)
|
||||
case err != nil:
|
||||
fmt.Printf("error: %v\n", err)
|
||||
p.WriteUsage(os.Stdout)
|
||||
os.Exit(1)
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ go run ./example --help
|
||||
Usage: ./example --something SOMETHING
|
||||
|
||||
Options:
|
||||
--something SOMETHING
|
||||
--help, -h display this help and exit
|
||||
|
||||
$ ./example --wrong
|
||||
error: unknown argument --wrong
|
||||
Usage: ./example --something SOMETHING
|
||||
|
||||
$ ./example
|
||||
error: --something is required
|
||||
Usage: ./example --something SOMETHING
|
||||
```
|
||||
|
||||
To also handle --version programatically, use the following:
|
||||
|
||||
```go
|
||||
type args struct {
|
||||
Something string
|
||||
}
|
||||
|
||||
func (args) Version() string {
|
||||
return "1.2.3"
|
||||
}
|
||||
|
||||
func main() {
|
||||
var args args
|
||||
p, err := arg.NewParser(arg.Config{}, &args)
|
||||
if err != nil {
|
||||
log.Fatalf("there was an error in the definition of the Go struct: %v", err)
|
||||
}
|
||||
|
||||
err = p.Parse(os.Args[1:])
|
||||
switch {
|
||||
case err == arg.ErrHelp: // found "--help" on command line
|
||||
p.WriteHelp(os.Stdout)
|
||||
os.Exit(0)
|
||||
case err == arg.ErrVersion: // found "--version" on command line
|
||||
fmt.Println(args.Version())
|
||||
os.Exit(0)
|
||||
case err != nil:
|
||||
fmt.Printf("error: %v\n", err)
|
||||
p.WriteUsage(os.Stdout)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("got %q\n", args.Something)
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example --version
|
||||
1.2.3
|
||||
|
||||
$ go run ./example --help
|
||||
1.2.3
|
||||
Usage: example --something SOMETHING
|
||||
|
||||
Options:
|
||||
--something SOMETHING
|
||||
--help, -h display this help and exit
|
||||
|
||||
$ ./example --wrong
|
||||
1.2.3
|
||||
error: unknown argument --wrong
|
||||
Usage: example --something SOMETHING
|
||||
|
||||
$ ./example
|
||||
error: --something is required
|
||||
Usage: example --something SOMETHING
|
||||
```
|
||||
|
||||
To generate subcommand-specific help messages, use the following most general version
|
||||
(this also works in absence of subcommands but is a bit more complex):
|
||||
|
||||
```go
|
||||
type fetchCmd struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
type args struct {
|
||||
Something string
|
||||
Fetch *fetchCmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
func (args) Version() string {
|
||||
return "1.2.3"
|
||||
}
|
||||
|
||||
func main() {
|
||||
var args args
|
||||
p, err := arg.NewParser(arg.Config{}, &args)
|
||||
if err != nil {
|
||||
log.Fatalf("there was an error in the definition of the Go struct: %v", err)
|
||||
}
|
||||
|
||||
err = p.Parse(os.Args[1:])
|
||||
switch {
|
||||
case err == arg.ErrHelp: // found "--help" on command line
|
||||
p.WriteHelpForSubcommand(os.Stdout, p.SubcommandNames()...)
|
||||
os.Exit(0)
|
||||
case err == arg.ErrVersion: // found "--version" on command line
|
||||
fmt.Println(args.Version())
|
||||
os.Exit(0)
|
||||
case err != nil:
|
||||
fmt.Printf("error: %v\n", err)
|
||||
p.WriteUsageForSubcommand(os.Stdout, p.SubcommandNames()...)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ ./example --version
|
||||
1.2.3
|
||||
|
||||
$ ./example --help
|
||||
1.2.3
|
||||
Usage: example [--something SOMETHING] <command> [<args>]
|
||||
|
||||
Options:
|
||||
--something SOMETHING
|
||||
--help, -h display this help and exit
|
||||
--version display version and exit
|
||||
|
||||
Commands:
|
||||
fetch
|
||||
|
||||
$ ./example fetch --help
|
||||
1.2.3
|
||||
Usage: example fetch [--count COUNT]
|
||||
|
||||
Options:
|
||||
--count COUNT
|
||||
|
||||
Global options:
|
||||
--something SOMETHING
|
||||
--help, -h display this help and exit
|
||||
--version display version and exit
|
||||
```
|
||||
|
||||
### API Documentation
|
||||
|
||||
https://pkg.go.dev/github.com/alexflint/go-arg
|
||||
https://godoc.org/github.com/alexflint/go-arg
|
||||
|
||||
### Rationale
|
||||
|
||||
There are many command line argument parsing libraries for Go, including one in the standard library, so why build another?
|
||||
|
||||
The `flag` library that ships in the standard library seems awkward to me. Positional arguments must precede options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. It also does not allow arguments to have both long (`--foo`) and short (`-f`) forms.
|
||||
The `flag` library that ships in the standard library seems awkward to me. Positional arguments must preceed options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. It also does not allow arguments to have both long (`--foo`) and short (`-f`) forms.
|
||||
|
||||
Many third-party argument parsing libraries are great for writing sophisticated command line interfaces, but feel to me like overkill for a simple script with a few flags.
|
||||
|
||||
|
@ -797,4 +422,4 @@ The idea behind `go-arg` is that Go already has an excellent way to describe dat
|
|||
|
||||
### Backward compatibility notes
|
||||
|
||||
Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which makes it possible to include commas inside help text.
|
||||
Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which removes most of the limits on the text you can write. In particular, you will need to use the new `help` tag if your help text includes any commas.
|
||||
|
|
|
@ -2,12 +2,8 @@ package arg
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func split(s string) []string {
|
||||
|
@ -34,11 +30,12 @@ func Example_defaultValues() {
|
|||
os.Args = split("./example")
|
||||
|
||||
var args struct {
|
||||
Foo string `default:"abc"`
|
||||
Foo string
|
||||
}
|
||||
args.Foo = "default value"
|
||||
MustParse(&args)
|
||||
fmt.Println(args.Foo)
|
||||
// output: abc
|
||||
// output: default value
|
||||
}
|
||||
|
||||
// This example demonstrates arguments that are required
|
||||
|
@ -86,48 +83,6 @@ func Example_multipleValues() {
|
|||
// output: Fetching the following IDs from localhost: [1 2 3]
|
||||
}
|
||||
|
||||
// This example demonstrates arguments with keys and values
|
||||
func Example_mappings() {
|
||||
// The args you would pass in on the command line
|
||||
os.Args = split("./example --userids john=123 mary=456")
|
||||
|
||||
var args struct {
|
||||
UserIDs map[string]int
|
||||
}
|
||||
MustParse(&args)
|
||||
fmt.Println(args.UserIDs)
|
||||
// output: map[john:123 mary:456]
|
||||
}
|
||||
|
||||
type commaSeparated struct {
|
||||
M map[string]string
|
||||
}
|
||||
|
||||
func (c *commaSeparated) UnmarshalText(b []byte) error {
|
||||
c.M = make(map[string]string)
|
||||
for _, part := range strings.Split(string(b), ",") {
|
||||
pos := strings.Index(part, "=")
|
||||
if pos == -1 {
|
||||
return fmt.Errorf("error parsing %q, expected format key=value", part)
|
||||
}
|
||||
c.M[part[:pos]] = part[pos+1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This example demonstrates arguments with keys and values separated by commas
|
||||
func Example_mappingWithCommas() {
|
||||
// The args you would pass in on the command line
|
||||
os.Args = split("./example --values one=two,three=four")
|
||||
|
||||
var args struct {
|
||||
Values commaSeparated
|
||||
}
|
||||
MustParse(&args)
|
||||
fmt.Println(args.Values.M)
|
||||
// output: map[one:two three:four]
|
||||
}
|
||||
|
||||
// This eample demonstrates multiple value arguments that can be mixed with
|
||||
// other arguments.
|
||||
func Example_multipleMixed() {
|
||||
|
@ -154,21 +109,20 @@ func Example_helpText() {
|
|||
os.Args = split("./example --help")
|
||||
|
||||
var args struct {
|
||||
Input string `arg:"positional,required"`
|
||||
Input string `arg:"positional"`
|
||||
Output []string `arg:"positional"`
|
||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||
Dataset string `help:"dataset to use"`
|
||||
Optimize int `arg:"-O,--optim" help:"optimization level"`
|
||||
Optimize int `arg:"-O,help:optimization level"`
|
||||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
mustParseExit = func(int) {}
|
||||
mustParseOut = os.Stdout
|
||||
osExit = func(int) {}
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example [--verbose] [--dataset DATASET] [--optim OPTIM] INPUT [OUTPUT [OUTPUT ...]]
|
||||
// Usage: example [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] INPUT [OUTPUT [OUTPUT ...]]
|
||||
//
|
||||
// Positional arguments:
|
||||
// INPUT
|
||||
|
@ -177,43 +131,11 @@ func Example_helpText() {
|
|||
// Options:
|
||||
// --verbose, -v verbosity level
|
||||
// --dataset DATASET dataset to use
|
||||
// --optim OPTIM, -O OPTIM
|
||||
// --optimize OPTIMIZE, -O OPTIMIZE
|
||||
// optimization level
|
||||
// --help, -h display this help and exit
|
||||
}
|
||||
|
||||
// This example shows the usage string generated by go-arg with customized placeholders
|
||||
func Example_helpPlaceholder() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example --help")
|
||||
|
||||
var args struct {
|
||||
Input string `arg:"positional,required" placeholder:"SRC"`
|
||||
Output []string `arg:"positional" placeholder:"DST"`
|
||||
Optimize int `arg:"-O" help:"optimization level" placeholder:"LEVEL"`
|
||||
MaxJobs int `arg:"-j" help:"maximum number of simultaneous jobs" placeholder:"N"`
|
||||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
mustParseExit = func(int) {}
|
||||
mustParseOut = os.Stdout
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]]
|
||||
//
|
||||
// Positional arguments:
|
||||
// SRC
|
||||
// DST
|
||||
//
|
||||
// Options:
|
||||
// --optimize LEVEL, -O LEVEL
|
||||
// optimization level
|
||||
// --maxjobs N, -j N maximum number of simultaneous jobs
|
||||
// --help, -h display this help and exit
|
||||
}
|
||||
|
||||
// This example shows the usage string generated by go-arg when using subcommands
|
||||
func Example_helpTextWithSubcommand() {
|
||||
// These are the args you would pass in on the command line
|
||||
|
@ -235,13 +157,12 @@ func Example_helpTextWithSubcommand() {
|
|||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
mustParseExit = func(int) {}
|
||||
mustParseOut = os.Stdout
|
||||
osExit = func(int) {}
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example [--verbose] <command> [<args>]
|
||||
// Usage: example [--verbose]
|
||||
//
|
||||
// Options:
|
||||
// --verbose
|
||||
|
@ -253,44 +174,7 @@ func Example_helpTextWithSubcommand() {
|
|||
}
|
||||
|
||||
// This example shows the usage string generated by go-arg when using subcommands
|
||||
func Example_helpTextWhenUsingSubcommand() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example get --help")
|
||||
|
||||
type getCmd struct {
|
||||
Item string `arg:"positional,required" help:"item to fetch"`
|
||||
}
|
||||
|
||||
type listCmd struct {
|
||||
Format string `help:"output format"`
|
||||
Limit int
|
||||
}
|
||||
|
||||
var args struct {
|
||||
Verbose bool
|
||||
Get *getCmd `arg:"subcommand" help:"fetch an item and print it"`
|
||||
List *listCmd `arg:"subcommand" help:"list available items"`
|
||||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
mustParseExit = func(int) {}
|
||||
mustParseOut = os.Stdout
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example get ITEM
|
||||
//
|
||||
// Positional arguments:
|
||||
// ITEM item to fetch
|
||||
//
|
||||
// Global options:
|
||||
// --verbose
|
||||
// --help, -h display this help and exit
|
||||
}
|
||||
|
||||
// This example shows how to print help for an explicit subcommand
|
||||
func Example_writeHelpForSubcommand() {
|
||||
func Example_helpTextForSubcommand() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example get --help")
|
||||
|
||||
|
@ -310,73 +194,17 @@ func Example_writeHelpForSubcommand() {
|
|||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
exit := func(int) {}
|
||||
osExit = func(int) {}
|
||||
|
||||
p, err := NewParser(Config{Exit: exit}, &args)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = p.WriteHelpForSubcommand(os.Stdout, "list")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example list [--format FORMAT] [--limit LIMIT]
|
||||
// Usage: example get ITEM
|
||||
//
|
||||
// Positional arguments:
|
||||
// ITEM item to fetch
|
||||
//
|
||||
// Options:
|
||||
// --format FORMAT output format
|
||||
// --limit LIMIT
|
||||
//
|
||||
// Global options:
|
||||
// --verbose
|
||||
// --help, -h display this help and exit
|
||||
}
|
||||
|
||||
// This example shows how to print help for a subcommand that is nested several levels deep
|
||||
func Example_writeHelpForSubcommandNested() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example get --help")
|
||||
|
||||
type mostNestedCmd struct {
|
||||
Item string
|
||||
}
|
||||
|
||||
type nestedCmd struct {
|
||||
MostNested *mostNestedCmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
type topLevelCmd struct {
|
||||
Nested *nestedCmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
var args struct {
|
||||
TopLevel *topLevelCmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
exit := func(int) {}
|
||||
|
||||
p, err := NewParser(Config{Exit: exit}, &args)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = p.WriteHelpForSubcommand(os.Stdout, "toplevel", "nested", "mostnested")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// output:
|
||||
// Usage: example toplevel nested mostnested [--item ITEM]
|
||||
//
|
||||
// Options:
|
||||
// --item ITEM
|
||||
// --help, -h display this help and exit
|
||||
}
|
||||
|
||||
|
@ -386,7 +214,7 @@ func Example_errorText() {
|
|||
os.Args = split("./example --optimize INVALID")
|
||||
|
||||
var args struct {
|
||||
Input string `arg:"positional,required"`
|
||||
Input string `arg:"positional"`
|
||||
Output []string `arg:"positional"`
|
||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||
Dataset string `help:"dataset to use"`
|
||||
|
@ -394,8 +222,8 @@ func Example_errorText() {
|
|||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
mustParseExit = func(int) {}
|
||||
mustParseOut = os.Stdout
|
||||
osExit = func(int) {}
|
||||
stderr = os.Stdout
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
|
@ -418,8 +246,8 @@ func Example_errorTextForSubcommand() {
|
|||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
mustParseExit = func(int) {}
|
||||
mustParseOut = os.Stdout
|
||||
osExit = func(int) {}
|
||||
stderr = os.Stdout
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
|
@ -454,8 +282,8 @@ func Example_subcommand() {
|
|||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
mustParseExit = func(int) {}
|
||||
mustParseOut = os.Stdout
|
||||
osExit = func(int) {}
|
||||
stderr = os.Stdout
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
|
@ -471,76 +299,3 @@ func Example_subcommand() {
|
|||
// output:
|
||||
// commit requested with message "what-this-commit-is-about"
|
||||
}
|
||||
|
||||
func Example_allSupportedTypes() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = []string{}
|
||||
|
||||
var args struct {
|
||||
Bool bool
|
||||
Byte byte
|
||||
Rune rune
|
||||
Int int
|
||||
Int8 int8
|
||||
Int16 int16
|
||||
Int32 int32
|
||||
Int64 int64
|
||||
Float32 float32
|
||||
Float64 float64
|
||||
String string
|
||||
Duration time.Duration
|
||||
URL url.URL
|
||||
Email mail.Address
|
||||
MAC net.HardwareAddr
|
||||
}
|
||||
|
||||
// go-arg supports each of the types above, as well as pointers to any of
|
||||
// the above and slices of any of the above. It also supports any types that
|
||||
// implements encoding.TextUnmarshaler.
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
}
|
||||
|
||||
func Example_envVarOnly() {
|
||||
os.Args = split("./example")
|
||||
_ = os.Setenv("AUTH_KEY", "my_key")
|
||||
|
||||
defer os.Unsetenv("AUTH_KEY")
|
||||
|
||||
var args struct {
|
||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
||||
}
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
fmt.Println(args.AuthKey)
|
||||
// output: my_key
|
||||
}
|
||||
|
||||
func Example_envVarOnlyShouldIgnoreFlag() {
|
||||
os.Args = split("./example --=my_key")
|
||||
|
||||
var args struct {
|
||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
||||
}
|
||||
|
||||
err := Parse(&args)
|
||||
|
||||
fmt.Println(err)
|
||||
// output: unknown argument --=my_key
|
||||
}
|
||||
|
||||
func Example_envVarOnlyShouldIgnoreShortFlag() {
|
||||
os.Args = split("./example -=my_key")
|
||||
|
||||
var args struct {
|
||||
AuthKey string `arg:"--,env:AUTH_KEY"`
|
||||
}
|
||||
|
||||
err := Parse(&args)
|
||||
|
||||
fmt.Println(err)
|
||||
// output: unknown argument -=my_key
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
module github.com/alexflint/go-arg
|
||||
|
||||
require (
|
||||
github.com/alexflint/go-scalar v1.0.0
|
||||
github.com/stretchr/testify v1.2.2
|
||||
)
|
|
@ -0,0 +1,8 @@
|
|||
github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
|
||||
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
454
parse.go
454
parse.go
|
@ -1,41 +1,40 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/dev/alexflint/scalar"
|
||||
scalar "github.com/alexflint/go-scalar"
|
||||
)
|
||||
|
||||
// to enable monkey-patching during tests
|
||||
var osExit = os.Exit
|
||||
|
||||
// path represents a sequence of steps to find the output location for an
|
||||
// argument or subcommand in the final destination struct
|
||||
type path struct {
|
||||
root int // index of the destination struct
|
||||
fields []reflect.StructField // sequence of struct fields to traverse
|
||||
fields []string // sequence of struct field names to traverse
|
||||
}
|
||||
|
||||
// String gets a string representation of the given path
|
||||
func (p path) String() string {
|
||||
s := "args"
|
||||
for _, f := range p.fields {
|
||||
s += "." + f.Name
|
||||
if len(p.fields) == 0 {
|
||||
return "args"
|
||||
}
|
||||
return s
|
||||
return "args." + strings.Join(p.fields, ".")
|
||||
}
|
||||
|
||||
// Child gets a new path representing a child of this path.
|
||||
func (p path) Child(f reflect.StructField) path {
|
||||
func (p path) Child(child string) path {
|
||||
// copy the entire slice of fields to avoid possible slice overwrite
|
||||
subfields := make([]reflect.StructField, len(p.fields)+1)
|
||||
copy(subfields, p.fields)
|
||||
subfields[len(subfields)-1] = f
|
||||
subfields := make([]string, len(p.fields)+1)
|
||||
copy(subfields, append(p.fields, child))
|
||||
return path{
|
||||
root: p.root,
|
||||
fields: subfields,
|
||||
|
@ -45,24 +44,21 @@ func (p path) Child(f reflect.StructField) path {
|
|||
// spec represents a command line option
|
||||
type spec struct {
|
||||
dest path
|
||||
field reflect.StructField // the struct field from which this option was created
|
||||
long string // the --long form for this option, or empty if none
|
||||
short string // the -s short form for this option, or empty if none
|
||||
cardinality cardinality // determines how many tokens will be present (possible values: zero, one, multiple)
|
||||
required bool // if true, this option must be present on the command line
|
||||
positional bool // if true, this option will be looked for in the positional flags
|
||||
separate bool // if true, each slice and map entry will have its own --flag
|
||||
help string // the help text for this option
|
||||
env string // the name of the environment variable for this option, or empty for none
|
||||
defaultValue reflect.Value // default value for this option
|
||||
defaultString string // default value for this option, in string form to be displayed in help text
|
||||
placeholder string // placeholder string in help
|
||||
typ reflect.Type
|
||||
long string
|
||||
short string
|
||||
multiple bool
|
||||
required bool
|
||||
positional bool
|
||||
separate bool
|
||||
help string
|
||||
env string
|
||||
boolean bool
|
||||
}
|
||||
|
||||
// command represents a named subcommand, or the top-level command
|
||||
type command struct {
|
||||
name string
|
||||
aliases []string
|
||||
help string
|
||||
dest path
|
||||
specs []*spec
|
||||
|
@ -70,49 +66,33 @@ type command struct {
|
|||
parent *command
|
||||
}
|
||||
|
||||
// ErrHelp indicates that the builtin -h or --help were provided
|
||||
// ErrHelp indicates that -h or --help were provided
|
||||
var ErrHelp = errors.New("help requested by user")
|
||||
|
||||
// ErrVersion indicates that the builtin --version was provided
|
||||
// ErrVersion indicates that --version was provided
|
||||
var ErrVersion = errors.New("version requested by user")
|
||||
|
||||
// for monkey patching in example and test code
|
||||
var mustParseExit = os.Exit
|
||||
var mustParseOut io.Writer = os.Stdout
|
||||
|
||||
// This stores the args sent from modules
|
||||
var register []interface{}
|
||||
|
||||
/*
|
||||
Use this in your packages to register
|
||||
variables with go-arg. Then add this to your init()
|
||||
|
||||
package 'foo'
|
||||
function init() {
|
||||
args.Register(&argsFoo)
|
||||
}
|
||||
*/
|
||||
func Register(dest ...interface{}) {
|
||||
register = append(register, dest...)
|
||||
}
|
||||
|
||||
// MustParse processes command line arguments and exits upon failure
|
||||
func MustParse(dest ...interface{}) *Parser {
|
||||
register = append(register, dest...)
|
||||
return mustParse(Config{Exit: mustParseExit, Out: mustParseOut}, register...)
|
||||
}
|
||||
|
||||
|
||||
// mustParse is a helper that facilitates testing
|
||||
func mustParse(config Config, dest ...interface{}) *Parser {
|
||||
p, err := NewParser(config, dest...)
|
||||
p, err := NewParser(Config{}, dest...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(config.Out, err)
|
||||
config.Exit(2)
|
||||
return nil
|
||||
fmt.Println(err)
|
||||
osExit(-1)
|
||||
return nil // just in case osExit was monkey-patched
|
||||
}
|
||||
|
||||
err = p.Parse(flags())
|
||||
switch {
|
||||
case err == ErrHelp:
|
||||
p.writeHelpForCommand(os.Stdout, p.lastCmd)
|
||||
osExit(0)
|
||||
case err == ErrVersion:
|
||||
fmt.Println(p.version)
|
||||
osExit(0)
|
||||
case err != nil:
|
||||
p.failWithCommand(err.Error(), p.lastCmd)
|
||||
}
|
||||
|
||||
p.MustParse(flags())
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -135,28 +115,7 @@ func flags() []string {
|
|||
|
||||
// Config represents configuration options for an argument parser
|
||||
type Config struct {
|
||||
// Program is the name of the program used in the help text
|
||||
Program string
|
||||
|
||||
// IgnoreEnv instructs the library not to read environment variables
|
||||
IgnoreEnv bool
|
||||
|
||||
// IgnoreDefault instructs the library not to reset the variables to the
|
||||
// default values, including pointers to sub commands
|
||||
IgnoreDefault bool
|
||||
|
||||
// StrictSubcommands intructs the library not to allow global commands after
|
||||
// subcommand
|
||||
StrictSubcommands bool
|
||||
|
||||
// EnvPrefix instructs the library to use a name prefix when reading environment variables.
|
||||
EnvPrefix string
|
||||
|
||||
// Exit is called to terminate the process with an error code (defaults to os.Exit)
|
||||
Exit func(int)
|
||||
|
||||
// Out is where help text, usage text, and failure messages are printed (defaults to os.Stdout)
|
||||
Out io.Writer
|
||||
Program string // Program is the name of the program used in the help text
|
||||
}
|
||||
|
||||
// Parser represents a set of command line options with destination values
|
||||
|
@ -166,10 +125,9 @@ type Parser struct {
|
|||
config Config
|
||||
version string
|
||||
description string
|
||||
epilogue string
|
||||
|
||||
// the following field changes during processing of command line arguments
|
||||
subcommand []string
|
||||
// the following fields change curing processing of command line arguments
|
||||
lastCmd *command
|
||||
}
|
||||
|
||||
// Versioned is the interface that the destination struct should implement to
|
||||
|
@ -188,45 +146,19 @@ type Described interface {
|
|||
Description() string
|
||||
}
|
||||
|
||||
// Epilogued is the interface that the destination struct should implement to
|
||||
// add an epilogue string at the bottom of the help message.
|
||||
type Epilogued interface {
|
||||
// Epilogue returns the string that will be printed on a line by itself
|
||||
// at the end of the help message.
|
||||
Epilogue() string
|
||||
}
|
||||
|
||||
// walkFields calls a function for each field of a struct, recursively expanding struct fields.
|
||||
func walkFields(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool) {
|
||||
walkFieldsImpl(t, visit, nil)
|
||||
}
|
||||
|
||||
func walkFieldsImpl(t reflect.Type, visit func(field reflect.StructField, owner reflect.Type) bool, path []int) {
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
field.Index = make([]int, len(path)+1)
|
||||
copy(field.Index, append(path, i))
|
||||
expand := visit(field, t)
|
||||
if expand && field.Type.Kind() == reflect.Struct {
|
||||
var subpath []int
|
||||
if field.Anonymous {
|
||||
subpath = append(path, i)
|
||||
}
|
||||
walkFieldsImpl(field.Type, visit, subpath)
|
||||
walkFields(field.Type, visit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewParser constructs a parser from a list of destination structs
|
||||
func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
||||
// fill in defaults
|
||||
if config.Exit == nil {
|
||||
config.Exit = os.Exit
|
||||
}
|
||||
if config.Out == nil {
|
||||
config.Out = os.Stdout
|
||||
}
|
||||
|
||||
// first pick a name for the command for use in the usage text
|
||||
var name string
|
||||
switch {
|
||||
|
@ -256,39 +188,10 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
|||
panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t))
|
||||
}
|
||||
|
||||
cmd, err := cmdFromStruct(name, path{root: i}, t, config.EnvPrefix)
|
||||
cmd, err := cmdFromStruct(name, path{root: i}, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// for backwards compatibility, add nonzero field values as defaults
|
||||
// this applies only to the top-level command, not to subcommands (this inconsistency
|
||||
// is the reason that this method for setting default values was deprecated)
|
||||
for _, spec := range cmd.specs {
|
||||
// get the value
|
||||
v := p.val(spec.dest)
|
||||
|
||||
// if the value is the "zero value" (e.g. nil pointer, empty struct) then ignore
|
||||
if isZero(v) {
|
||||
continue
|
||||
}
|
||||
|
||||
// store as a default
|
||||
spec.defaultValue = v
|
||||
|
||||
// we need a string to display in help text
|
||||
// if MarshalText is implemented then use that
|
||||
if m, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
s, err := m.MarshalText()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v: error marshaling default value to string: %v", spec.dest, err)
|
||||
}
|
||||
spec.defaultString = string(s)
|
||||
} else {
|
||||
spec.defaultString = fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
p.cmd.specs = append(p.cmd.specs, cmd.specs...)
|
||||
p.cmd.subcommands = append(p.cmd.subcommands, cmd.subcommands...)
|
||||
|
||||
|
@ -298,15 +201,12 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) {
|
|||
if dest, ok := dest.(Described); ok {
|
||||
p.description = dest.Description()
|
||||
}
|
||||
if dest, ok := dest.(Epilogued); ok {
|
||||
p.epilogue = dest.Epilogue()
|
||||
}
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*command, error) {
|
||||
func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
||||
// commands can only be created from pointers to structs
|
||||
if t.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a %s",
|
||||
|
@ -326,30 +226,23 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
|||
|
||||
var errs []string
|
||||
walkFields(t, func(field reflect.StructField, t reflect.Type) bool {
|
||||
// check for the ignore switch in the tag
|
||||
// Check for the ignore switch in the tag
|
||||
tag := field.Tag.Get("arg")
|
||||
if tag == "-" {
|
||||
return false
|
||||
}
|
||||
|
||||
// if this is an embedded struct then recurse into its fields, even if
|
||||
// it is unexported, because exported fields on unexported embedded
|
||||
// structs are still writable
|
||||
// If this is an embedded struct then recurse into its fields
|
||||
if field.Anonymous && field.Type.Kind() == reflect.Struct {
|
||||
return true
|
||||
}
|
||||
|
||||
// ignore any other unexported field
|
||||
if !isExported(field.Name) {
|
||||
return false
|
||||
}
|
||||
|
||||
// duplicate the entire path to avoid slice overwrites
|
||||
subdest := dest.Child(field)
|
||||
subdest := dest.Child(field.Name)
|
||||
spec := spec{
|
||||
dest: subdest,
|
||||
field: field,
|
||||
long: strings.ToLower(field.Name),
|
||||
typ: field.Type,
|
||||
}
|
||||
|
||||
help, exists := field.Tag.Lookup("help")
|
||||
|
@ -357,12 +250,10 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
|||
spec.help = help
|
||||
}
|
||||
|
||||
// process each comma-separated part of the tag
|
||||
var isSubcommand bool
|
||||
// Look at the tag
|
||||
var isSubcommand bool // tracks whether this field is a subcommand
|
||||
if tag != "" {
|
||||
for _, key := range strings.Split(tag, ",") {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
key = strings.TrimLeft(key, " ")
|
||||
var value string
|
||||
if pos := strings.Index(key, ":"); pos != -1 {
|
||||
|
@ -376,7 +267,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
|||
case strings.HasPrefix(key, "--"):
|
||||
spec.long = key[2:]
|
||||
case strings.HasPrefix(key, "-"):
|
||||
if len(key) > 2 {
|
||||
if len(key) != 2 {
|
||||
errs = append(errs, fmt.Sprintf("%s.%s: short arguments must be one character only",
|
||||
t.Name(), field.Name))
|
||||
return false
|
||||
|
@ -393,30 +284,24 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
|||
case key == "env":
|
||||
// Use override name if provided
|
||||
if value != "" {
|
||||
spec.env = envPrefix + value
|
||||
spec.env = value
|
||||
} else {
|
||||
spec.env = envPrefix + strings.ToUpper(field.Name)
|
||||
spec.env = strings.ToUpper(field.Name)
|
||||
}
|
||||
case key == "subcommand":
|
||||
// decide on a name for the subcommand
|
||||
var cmdnames []string
|
||||
if value == "" {
|
||||
cmdnames = []string{strings.ToLower(field.Name)}
|
||||
} else {
|
||||
cmdnames = strings.Split(value, "|")
|
||||
}
|
||||
for i := range cmdnames {
|
||||
cmdnames[i] = strings.TrimSpace(cmdnames[i])
|
||||
cmdname := value
|
||||
if cmdname == "" {
|
||||
cmdname = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
// parse the subcommand recursively
|
||||
subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type, envPrefix)
|
||||
subcmd, err := cmdFromStruct(cmdname, subdest, field.Type)
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
subcmd.aliases = cmdnames[1:]
|
||||
subcmd.parent = &cmd
|
||||
subcmd.help = field.Tag.Get("help")
|
||||
|
||||
|
@ -427,71 +312,24 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// placeholder is the string used in the help text like this: "--somearg PLACEHOLDER"
|
||||
placeholder, hasPlaceholder := field.Tag.Lookup("placeholder")
|
||||
if hasPlaceholder {
|
||||
spec.placeholder = placeholder
|
||||
} else if spec.long != "" {
|
||||
spec.placeholder = strings.ToUpper(spec.long)
|
||||
} else {
|
||||
spec.placeholder = strings.ToUpper(spec.field.Name)
|
||||
}
|
||||
|
||||
// if this is a subcommand then we've done everything we need to do
|
||||
if isSubcommand {
|
||||
return false
|
||||
}
|
||||
|
||||
// check whether this field is supported. It's good to do this here rather than
|
||||
// Check whether this field is supported. It's good to do this here rather than
|
||||
// wait until ParseValue because it means that a program with invalid argument
|
||||
// fields will always fail regardless of whether the arguments it received
|
||||
// exercised those fields.
|
||||
var err error
|
||||
spec.cardinality, err = cardinalityOf(field.Type)
|
||||
if err != nil {
|
||||
if !isSubcommand {
|
||||
cmd.specs = append(cmd.specs, &spec)
|
||||
|
||||
var parseable bool
|
||||
parseable, spec.boolean, spec.multiple = canParse(field.Type)
|
||||
if !parseable {
|
||||
errs = append(errs, fmt.Sprintf("%s.%s: %s fields are not supported",
|
||||
t.Name(), field.Name, field.Type.String()))
|
||||
return false
|
||||
}
|
||||
|
||||
defaultString, hasDefault := field.Tag.Lookup("default")
|
||||
if hasDefault {
|
||||
// we do not support default values for maps and slices
|
||||
if spec.cardinality == multiple {
|
||||
errs = append(errs, fmt.Sprintf("%s.%s: default values are not supported for slice or map fields",
|
||||
t.Name(), field.Name))
|
||||
return false
|
||||
}
|
||||
|
||||
// a required field cannot also have a default value
|
||||
if spec.required {
|
||||
errs = append(errs, fmt.Sprintf("%s.%s: 'required' cannot be used when a default value is specified",
|
||||
t.Name(), field.Name))
|
||||
return false
|
||||
}
|
||||
|
||||
// parse the default value
|
||||
spec.defaultString = defaultString
|
||||
if field.Type.Kind() == reflect.Ptr {
|
||||
// here we have a field of type *T and we create a new T, no need to dereference
|
||||
// in order for the value to be settable
|
||||
spec.defaultValue = reflect.New(field.Type.Elem())
|
||||
} else {
|
||||
// here we have a field of type T and we create a new T and then dereference it
|
||||
// so that the resulting value is settable
|
||||
spec.defaultValue = reflect.New(field.Type).Elem()
|
||||
}
|
||||
err := scalar.ParseValue(spec.defaultValue, defaultString)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf("%s.%s: error processing default value: %v", t.Name(), field.Name, err))
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// add the spec to the list of specs
|
||||
cmd.specs = append(cmd.specs, &spec)
|
||||
|
||||
// if this was an embedded field then we already returned true up above
|
||||
return false
|
||||
})
|
||||
|
@ -514,15 +352,8 @@ func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*c
|
|||
return &cmd, nil
|
||||
}
|
||||
|
||||
// Parse processes the given command line option, storing the results in the fields
|
||||
// of the structs from which NewParser was constructed.
|
||||
//
|
||||
// It returns ErrHelp if "--help" is one of the command line args and ErrVersion if
|
||||
// "--version" is one of the command line args (the latter only applies if the
|
||||
// destination struct passed to NewParser implements Versioned.)
|
||||
//
|
||||
// To respond to --help and --version in the way that MustParse does, see examples
|
||||
// in the README under "Custom handling of --help and --version".
|
||||
// Parse processes the given command line option, storing the results in the field
|
||||
// of the structs from which NewParser was constructed
|
||||
func (p *Parser) Parse(args []string) error {
|
||||
err := p.process(args)
|
||||
if err != nil {
|
||||
|
@ -539,20 +370,6 @@ func (p *Parser) Parse(args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func (p *Parser) MustParse(args []string) {
|
||||
err := p.Parse(args)
|
||||
switch {
|
||||
case err == ErrHelp:
|
||||
p.WriteHelpForSubcommand(p.config.Out, p.subcommand...)
|
||||
p.config.Exit(0)
|
||||
case err == ErrVersion:
|
||||
fmt.Fprintln(p.config.Out, p.version)
|
||||
p.config.Exit(0)
|
||||
case err != nil:
|
||||
p.FailSubcommand(err.Error(), p.subcommand...)
|
||||
}
|
||||
}
|
||||
|
||||
// process environment vars for the given arguments
|
||||
func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error {
|
||||
for _, spec := range specs {
|
||||
|
@ -565,13 +382,10 @@ func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error
|
|||
continue
|
||||
}
|
||||
|
||||
if spec.cardinality == multiple {
|
||||
if spec.multiple {
|
||||
// expect a CSV string in an environment
|
||||
// variable in the case of multiple values
|
||||
var values []string
|
||||
var err error
|
||||
if len(strings.TrimSpace(value)) > 0 {
|
||||
values, err = csv.NewReader(strings.NewReader(value)).Read()
|
||||
values, err := csv.NewReader(strings.NewReader(value)).Read()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"error reading a CSV string from environment variable %s with multiple values: %v",
|
||||
|
@ -579,8 +393,7 @@ func (p *Parser) captureEnvVars(specs []*spec, wasPresent map[*spec]bool) error
|
|||
err,
|
||||
)
|
||||
}
|
||||
}
|
||||
if err = setSliceOrMap(p.val(spec.dest), values, !spec.separate); err != nil {
|
||||
if err = setSlice(p.val(spec.dest), values, !spec.separate); err != nil {
|
||||
return fmt.Errorf(
|
||||
"error processing environment variable %s with multiple values: %v",
|
||||
spec.env,
|
||||
|
@ -606,28 +419,17 @@ func (p *Parser) process(args []string) error {
|
|||
|
||||
// union of specs for the chain of subcommands encountered so far
|
||||
curCmd := p.cmd
|
||||
p.subcommand = nil
|
||||
p.lastCmd = curCmd
|
||||
|
||||
// make a copy of the specs because we will add to this list each time we expand a subcommand
|
||||
specs := make([]*spec, len(curCmd.specs))
|
||||
copy(specs, curCmd.specs)
|
||||
|
||||
// deal with environment vars
|
||||
if !p.config.IgnoreEnv {
|
||||
err := p.captureEnvVars(specs, wasPresent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// determine if the current command has a version option spec
|
||||
var hasVersionOption bool
|
||||
for _, spec := range curCmd.specs {
|
||||
if spec.long == "version" {
|
||||
hasVersionOption = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// process each string from the command line
|
||||
var allpositional bool
|
||||
|
@ -636,7 +438,7 @@ func (p *Parser) process(args []string) error {
|
|||
// must use explicit for loop, not range, because we manipulate i inside the loop
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if arg == "--" && !allpositional {
|
||||
if arg == "--" {
|
||||
allpositional = true
|
||||
continue
|
||||
}
|
||||
|
@ -656,28 +458,19 @@ func (p *Parser) process(args []string) error {
|
|||
|
||||
// instantiate the field to point to a new struct
|
||||
v := p.val(subcmd.dest)
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem())) // we already checked that all subcommands are struct pointers
|
||||
}
|
||||
|
||||
// add the new options to the set of allowed options
|
||||
if p.config.StrictSubcommands {
|
||||
specs = make([]*spec, len(subcmd.specs))
|
||||
copy(specs, subcmd.specs)
|
||||
} else {
|
||||
specs = append(specs, subcmd.specs...)
|
||||
}
|
||||
|
||||
// capture environment vars for these new options
|
||||
if !p.config.IgnoreEnv {
|
||||
err := p.captureEnvVars(subcmd.specs, wasPresent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
curCmd = subcmd
|
||||
p.subcommand = append(p.subcommand, arg)
|
||||
p.lastCmd = curCmd
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -686,10 +479,8 @@ func (p *Parser) process(args []string) error {
|
|||
case "-h", "--help":
|
||||
return ErrHelp
|
||||
case "--version":
|
||||
if !hasVersionOption && p.version != "" {
|
||||
return ErrVersion
|
||||
}
|
||||
}
|
||||
|
||||
// check for an equals sign, as in "--foo=bar"
|
||||
var value string
|
||||
|
@ -702,13 +493,13 @@ func (p *Parser) process(args []string) error {
|
|||
// lookup the spec for this option (note that the "specs" slice changes as
|
||||
// we expand subcommands so it is better not to use a map)
|
||||
spec := findOption(specs, opt)
|
||||
if spec == nil || opt == "" {
|
||||
if spec == nil {
|
||||
return fmt.Errorf("unknown argument %s", arg)
|
||||
}
|
||||
wasPresent[spec] = true
|
||||
|
||||
// deal with the case of multiple values
|
||||
if spec.cardinality == multiple {
|
||||
if spec.multiple {
|
||||
var values []string
|
||||
if value == "" {
|
||||
for i+1 < len(args) && !isFlag(args[i+1]) && args[i+1] != "--" {
|
||||
|
@ -721,7 +512,7 @@ func (p *Parser) process(args []string) error {
|
|||
} else {
|
||||
values = append(values, value)
|
||||
}
|
||||
err := setSliceOrMap(p.val(spec.dest), values, !spec.separate)
|
||||
err := setSlice(p.val(spec.dest), values, !spec.separate)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing %s: %v", arg, err)
|
||||
}
|
||||
|
@ -730,7 +521,7 @@ func (p *Parser) process(args []string) error {
|
|||
|
||||
// if it's a flag and it has no value then set the value to true
|
||||
// use boolean because this takes account of TextUnmarshaler
|
||||
if spec.cardinality == zero && value == "" {
|
||||
if spec.boolean && value == "" {
|
||||
value = "true"
|
||||
}
|
||||
|
||||
|
@ -739,7 +530,7 @@ func (p *Parser) process(args []string) error {
|
|||
if i+1 == len(args) {
|
||||
return fmt.Errorf("missing value for %s", arg)
|
||||
}
|
||||
if !nextIsNumeric(spec.field.Type, args[i+1]) && isFlag(args[i+1]) {
|
||||
if !nextIsNumeric(spec.typ, args[i+1]) && isFlag(args[i+1]) {
|
||||
return fmt.Errorf("missing value for %s", arg)
|
||||
}
|
||||
value = args[i+1]
|
||||
|
@ -761,16 +552,16 @@ func (p *Parser) process(args []string) error {
|
|||
break
|
||||
}
|
||||
wasPresent[spec] = true
|
||||
if spec.cardinality == multiple {
|
||||
err := setSliceOrMap(p.val(spec.dest), positionals, true)
|
||||
if spec.multiple {
|
||||
err := setSlice(p.val(spec.dest), positionals, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing %s: %v", spec.field.Name, err)
|
||||
return fmt.Errorf("error processing %s: %v", spec.long, err)
|
||||
}
|
||||
positionals = nil
|
||||
} else {
|
||||
err := scalar.ParseValue(p.val(spec.dest), positionals[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing %s: %v", spec.field.Name, err)
|
||||
return fmt.Errorf("error processing %s: %v", spec.long, err)
|
||||
}
|
||||
positionals = positionals[1:]
|
||||
}
|
||||
|
@ -779,38 +570,14 @@ func (p *Parser) process(args []string) error {
|
|||
return fmt.Errorf("too many positional arguments at '%s'", positionals[0])
|
||||
}
|
||||
|
||||
// fill in defaults and check that all the required args were provided
|
||||
// finally check that all the required args were provided
|
||||
for _, spec := range specs {
|
||||
if wasPresent[spec] {
|
||||
continue
|
||||
}
|
||||
|
||||
name := strings.ToLower(spec.field.Name)
|
||||
if spec.long != "" && !spec.positional {
|
||||
if spec.required && !wasPresent[spec] {
|
||||
name := spec.long
|
||||
if !spec.positional {
|
||||
name = "--" + spec.long
|
||||
}
|
||||
|
||||
if spec.required {
|
||||
if spec.short == "" && spec.long == "" {
|
||||
msg := fmt.Sprintf("environment variable %s is required", spec.env)
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("%s is required", name)
|
||||
if spec.env != "" {
|
||||
msg += " (or environment variable " + spec.env + ")"
|
||||
}
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
if spec.defaultValue.IsValid() && !p.config.IgnoreDefault {
|
||||
// One issue here is that if the user now modifies the value then
|
||||
// the default value stored in the spec will be corrupted. There
|
||||
// is no general way to "deep-copy" values in Go, and we still
|
||||
// support the old-style method for specifying defaults as
|
||||
// Go values assigned directly to the struct field, so we are stuck.
|
||||
p.val(spec.dest).Set(spec.defaultValue)
|
||||
return fmt.Errorf("%s is required", name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -847,11 +614,49 @@ func (p *Parser) val(dest path) reflect.Value {
|
|||
v = v.Elem()
|
||||
}
|
||||
|
||||
v = v.FieldByIndex(field.Index)
|
||||
v = v.FieldByName(field)
|
||||
if !v.IsValid() {
|
||||
// it is appropriate to panic here because this can only happen due to
|
||||
// an internal bug in this library (since we construct the path ourselves
|
||||
// by reflecting on the same struct)
|
||||
panic(fmt.Errorf("error resolving path %v: %v has no field named %v",
|
||||
dest.fields, v.Type(), field))
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// parse a value as the appropriate type and store it in the struct
|
||||
func setSlice(dest reflect.Value, values []string, trunc bool) error {
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("field is not writable")
|
||||
}
|
||||
|
||||
var ptr bool
|
||||
elem := dest.Type().Elem()
|
||||
if elem.Kind() == reflect.Ptr && !elem.Implements(textUnmarshalerType) {
|
||||
ptr = true
|
||||
elem = elem.Elem()
|
||||
}
|
||||
|
||||
// Truncate the dest slice in case default values exist
|
||||
if trunc && !dest.IsNil() {
|
||||
dest.SetLen(0)
|
||||
}
|
||||
|
||||
for _, s := range values {
|
||||
v := reflect.New(elem)
|
||||
if err := scalar.ParseValue(v.Elem(), s); err != nil {
|
||||
return err
|
||||
}
|
||||
if !ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
dest.Set(reflect.Append(dest, v))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findOption finds an option from its name, or returns null if no spec is found
|
||||
func findOption(specs []*spec, name string) *spec {
|
||||
for _, spec := range specs {
|
||||
|
@ -871,11 +676,6 @@ func findSubcommand(cmds []*command, name string) *command {
|
|||
if cmd.name == name {
|
||||
return cmd
|
||||
}
|
||||
for _, alias := range cmd.aliases {
|
||||
if alias == name {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -28,34 +24,14 @@ func parse(cmdline string, dest interface{}) error {
|
|||
}
|
||||
|
||||
func pparse(cmdline string, dest interface{}) (*Parser, error) {
|
||||
return parseWithEnv(Config{}, cmdline, nil, dest)
|
||||
}
|
||||
|
||||
func parseWithEnv(config Config, cmdline string, env []string, dest interface{}) (*Parser, error) {
|
||||
p, err := NewParser(config, dest)
|
||||
p, err := NewParser(Config{}, dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// split the command line
|
||||
var parts []string
|
||||
if len(cmdline) > 0 {
|
||||
parts = strings.Split(cmdline, " ")
|
||||
}
|
||||
|
||||
// split the environment vars
|
||||
for _, s := range env {
|
||||
pos := strings.Index(s, "=")
|
||||
if pos == -1 {
|
||||
return nil, fmt.Errorf("missing equals sign in %q", s)
|
||||
}
|
||||
err := os.Setenv(s[:pos], s[pos+1:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// execute the parser
|
||||
return p, p.Parse(parts)
|
||||
}
|
||||
|
||||
|
@ -96,21 +72,6 @@ func TestInt(t *testing.T) {
|
|||
assert.EqualValues(t, 8, *args.Ptr)
|
||||
}
|
||||
|
||||
func TestHexOctBin(t *testing.T) {
|
||||
var args struct {
|
||||
Hex int
|
||||
Oct int
|
||||
Bin int
|
||||
Underscored int
|
||||
}
|
||||
err := parse("--hex 0xA --oct 0o10 --bin 0b101 --underscored 123_456", &args)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, 10, args.Hex)
|
||||
assert.EqualValues(t, 8, args.Oct)
|
||||
assert.EqualValues(t, 5, args.Bin)
|
||||
assert.EqualValues(t, 123456, args.Underscored)
|
||||
}
|
||||
|
||||
func TestNegativeInt(t *testing.T) {
|
||||
var args struct {
|
||||
Foo int
|
||||
|
@ -219,22 +180,6 @@ func TestRequired(t *testing.T) {
|
|||
require.Error(t, err, "--foo is required")
|
||||
}
|
||||
|
||||
func TestRequiredWithEnv(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"required,env:FOO"`
|
||||
}
|
||||
err := parse("", &args)
|
||||
require.Error(t, err, "--foo is required (or environment variable FOO)")
|
||||
}
|
||||
|
||||
func TestRequiredWithEnvOnly(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"required,--,-,env:FOO"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{}, &args)
|
||||
require.Error(t, err, "environment variable FOO is required")
|
||||
}
|
||||
|
||||
func TestShortFlag(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"-f"`
|
||||
|
@ -275,83 +220,6 @@ func TestLongFlag(t *testing.T) {
|
|||
assert.Equal(t, "xyz", args.Foo)
|
||||
}
|
||||
|
||||
func TestSlice(t *testing.T) {
|
||||
var args struct {
|
||||
Strings []string
|
||||
}
|
||||
err := parse("--strings a b c", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"a", "b", "c"}, args.Strings)
|
||||
}
|
||||
func TestSliceOfBools(t *testing.T) {
|
||||
var args struct {
|
||||
B []bool
|
||||
}
|
||||
|
||||
err := parse("--b true false true", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []bool{true, false, true}, args.B)
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
var args struct {
|
||||
Values map[string]int
|
||||
}
|
||||
err := parse("--values a=1 b=2 c=3", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, args.Values, 3)
|
||||
assert.Equal(t, 1, args.Values["a"])
|
||||
assert.Equal(t, 2, args.Values["b"])
|
||||
assert.Equal(t, 3, args.Values["c"])
|
||||
}
|
||||
|
||||
func TestMapPositional(t *testing.T) {
|
||||
var args struct {
|
||||
Values map[string]int `arg:"positional"`
|
||||
}
|
||||
err := parse("a=1 b=2 c=3", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, args.Values, 3)
|
||||
assert.Equal(t, 1, args.Values["a"])
|
||||
assert.Equal(t, 2, args.Values["b"])
|
||||
assert.Equal(t, 3, args.Values["c"])
|
||||
}
|
||||
|
||||
func TestMapWithSeparate(t *testing.T) {
|
||||
var args struct {
|
||||
Values map[string]int `arg:"separate"`
|
||||
}
|
||||
err := parse("--values a=1 --values b=2 --values c=3", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, args.Values, 3)
|
||||
assert.Equal(t, 1, args.Values["a"])
|
||||
assert.Equal(t, 2, args.Values["b"])
|
||||
assert.Equal(t, 3, args.Values["c"])
|
||||
}
|
||||
|
||||
func TestPlaceholder(t *testing.T) {
|
||||
var args struct {
|
||||
Input string `arg:"positional" placeholder:"SRC"`
|
||||
Output []string `arg:"positional" placeholder:"DST"`
|
||||
Optimize int `arg:"-O" placeholder:"LEVEL"`
|
||||
MaxJobs int `arg:"-j" placeholder:"N"`
|
||||
}
|
||||
err := parse("-O 5 --maxjobs 2 src dest1 dest2", &args)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestNoLongName(t *testing.T) {
|
||||
var args struct {
|
||||
ShortOnly string `arg:"-s,--"`
|
||||
EnvOnly string `arg:"--,env"`
|
||||
}
|
||||
setenv(t, "ENVONLY", "TestVal")
|
||||
err := parse("-s TestVal2", &args)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "TestVal", args.EnvOnly)
|
||||
assert.Equal(t, "TestVal2", args.ShortOnly)
|
||||
}
|
||||
|
||||
func TestCaseSensitive(t *testing.T) {
|
||||
var args struct {
|
||||
Lower bool `arg:"-v"`
|
||||
|
@ -516,7 +384,7 @@ func TestMissingValueAtEnd(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestMissingValueInMiddle(t *testing.T) {
|
||||
func TestMissingValueInMIddle(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string
|
||||
Bar string
|
||||
|
@ -601,23 +469,6 @@ func TestNoMoreOptions(t *testing.T) {
|
|||
assert.Equal(t, []string{"abc", "--foo", "xyz"}, args.Bar)
|
||||
}
|
||||
|
||||
func TestNoMoreOptionsBeforeHelp(t *testing.T) {
|
||||
var args struct {
|
||||
Foo int
|
||||
}
|
||||
err := parse("not_an_integer -- --help", &args)
|
||||
assert.NotEqual(t, ErrHelp, err)
|
||||
}
|
||||
|
||||
func TestNoMoreOptionsTwice(t *testing.T) {
|
||||
var args struct {
|
||||
X []string `arg:"positional"`
|
||||
}
|
||||
err := parse("-- --", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"--"}, args.X)
|
||||
}
|
||||
|
||||
func TestHelpFlag(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string
|
||||
|
@ -701,27 +552,13 @@ func TestMustParse(t *testing.T) {
|
|||
assert.NotNil(t, parser)
|
||||
}
|
||||
|
||||
func TestMustParseError(t *testing.T) {
|
||||
var args struct {
|
||||
Foo []string `default:""`
|
||||
}
|
||||
var exitCode int
|
||||
var stdout bytes.Buffer
|
||||
mustParseExit = func(code int) { exitCode = code }
|
||||
mustParseOut = &stdout
|
||||
os.Args = []string{"example"}
|
||||
parser := MustParse(&args)
|
||||
assert.Nil(t, parser)
|
||||
assert.Equal(t, 2, exitCode)
|
||||
assert.Contains(t, stdout.String(), "default values are not supported for slice or map fields")
|
||||
}
|
||||
|
||||
func TestEnvironmentVariable(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
||||
require.NoError(t, err)
|
||||
setenv(t, "FOO", "bar")
|
||||
os.Args = []string{"example"}
|
||||
MustParse(&args)
|
||||
assert.Equal(t, "bar", args.Foo)
|
||||
}
|
||||
|
||||
|
@ -729,8 +566,8 @@ func TestEnvironmentVariableNotPresent(t *testing.T) {
|
|||
var args struct {
|
||||
NotPresent string `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", nil, &args)
|
||||
require.NoError(t, err)
|
||||
os.Args = []string{"example"}
|
||||
MustParse(&args)
|
||||
assert.Equal(t, "", args.NotPresent)
|
||||
}
|
||||
|
||||
|
@ -738,8 +575,9 @@ func TestEnvironmentVariableOverrideName(t *testing.T) {
|
|||
var args struct {
|
||||
Foo string `arg:"env:BAZ"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{"BAZ=bar"}, &args)
|
||||
require.NoError(t, err)
|
||||
setenv(t, "BAZ", "bar")
|
||||
os.Args = []string{"example"}
|
||||
MustParse(&args)
|
||||
assert.Equal(t, "bar", args.Foo)
|
||||
}
|
||||
|
||||
|
@ -747,16 +585,19 @@ func TestEnvironmentVariableOverrideArgument(t *testing.T) {
|
|||
var args struct {
|
||||
Foo string `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "--foo zzz", []string{"FOO=bar"}, &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "zzz", args.Foo)
|
||||
setenv(t, "FOO", "bar")
|
||||
os.Args = []string{"example", "--foo", "baz"}
|
||||
MustParse(&args)
|
||||
assert.Equal(t, "baz", args.Foo)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableError(t *testing.T) {
|
||||
var args struct {
|
||||
Foo int `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
||||
setenv(t, "FOO", "bar")
|
||||
os.Args = []string{"example"}
|
||||
err := Parse(&args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -764,8 +605,9 @@ func TestEnvironmentVariableRequired(t *testing.T) {
|
|||
var args struct {
|
||||
Foo string `arg:"env,required"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{"FOO=bar"}, &args)
|
||||
require.NoError(t, err)
|
||||
setenv(t, "FOO", "bar")
|
||||
os.Args = []string{"example"}
|
||||
MustParse(&args)
|
||||
assert.Equal(t, "bar", args.Foo)
|
||||
}
|
||||
|
||||
|
@ -773,26 +615,17 @@ func TestEnvironmentVariableSliceArgumentString(t *testing.T) {
|
|||
var args struct {
|
||||
Foo []string `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=bar,"baz, qux"`}, &args)
|
||||
require.NoError(t, err)
|
||||
setenv(t, "FOO", `bar,"baz, qux"`)
|
||||
MustParse(&args)
|
||||
assert.Equal(t, []string{"bar", "baz, qux"}, args.Foo)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableSliceEmpty(t *testing.T) {
|
||||
var args struct {
|
||||
Foo []string `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=`}, &args)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, args.Foo, 0)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableSliceArgumentInteger(t *testing.T) {
|
||||
var args struct {
|
||||
Foo []int `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=1,99`}, &args)
|
||||
require.NoError(t, err)
|
||||
setenv(t, "FOO", "1,99")
|
||||
MustParse(&args)
|
||||
assert.Equal(t, []int{1, 99}, args.Foo)
|
||||
}
|
||||
|
||||
|
@ -800,8 +633,8 @@ func TestEnvironmentVariableSliceArgumentFloat(t *testing.T) {
|
|||
var args struct {
|
||||
Foo []float32 `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=1.1,99.9`}, &args)
|
||||
require.NoError(t, err)
|
||||
setenv(t, "FOO", "1.1,99.9")
|
||||
MustParse(&args)
|
||||
assert.Equal(t, []float32{1.1, 99.9}, args.Foo)
|
||||
}
|
||||
|
||||
|
@ -809,8 +642,8 @@ func TestEnvironmentVariableSliceArgumentBool(t *testing.T) {
|
|||
var args struct {
|
||||
Foo []bool `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=true,false,0,1`}, &args)
|
||||
require.NoError(t, err)
|
||||
setenv(t, "FOO", "true,false,0,1")
|
||||
MustParse(&args)
|
||||
assert.Equal(t, []bool{true, false, false, true}, args.Foo)
|
||||
}
|
||||
|
||||
|
@ -818,7 +651,8 @@ func TestEnvironmentVariableSliceArgumentWrongCsv(t *testing.T) {
|
|||
var args struct {
|
||||
Foo []int `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=1,99\"`}, &args)
|
||||
setenv(t, "FOO", "1,99\"")
|
||||
err := Parse(&args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
|
@ -826,142 +660,11 @@ func TestEnvironmentVariableSliceArgumentWrongType(t *testing.T) {
|
|||
var args struct {
|
||||
Foo []bool `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=one,two`}, &args)
|
||||
setenv(t, "FOO", "one,two")
|
||||
err := Parse(&args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableMap(t *testing.T) {
|
||||
var args struct {
|
||||
Foo map[int]string `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=1=one,99=ninetynine`}, &args)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, args.Foo, 2)
|
||||
assert.Equal(t, "one", args.Foo[1])
|
||||
assert.Equal(t, "ninetynine", args.Foo[99])
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableEmptyMap(t *testing.T) {
|
||||
var args struct {
|
||||
Foo map[int]string `arg:"env"`
|
||||
}
|
||||
_, err := parseWithEnv(Config{}, "", []string{`FOO=`}, &args)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, args.Foo, 0)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableWithPrefix(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"env"`
|
||||
}
|
||||
|
||||
_, err := parseWithEnv(Config{EnvPrefix: "MYAPP_"}, "", []string{"MYAPP_FOO=bar"}, &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "bar", args.Foo)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableIgnored(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"env"`
|
||||
}
|
||||
setenv(t, "FOO", "abc")
|
||||
|
||||
p, err := NewParser(Config{IgnoreEnv: true}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", args.Foo)
|
||||
}
|
||||
|
||||
func TestDefaultValuesIgnored(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `default:"bad"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{IgnoreDefault: true}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "", args.Foo)
|
||||
}
|
||||
|
||||
func TestRequiredEnvironmentOnlyVariableIsMissing(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"required,--,env:FOO"`
|
||||
}
|
||||
|
||||
_, err := parseWithEnv(Config{}, "", []string{""}, &args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestOptionalEnvironmentOnlyVariable(t *testing.T) {
|
||||
var args struct {
|
||||
Foo string `arg:"env:FOO"`
|
||||
}
|
||||
|
||||
_, err := parseWithEnv(Config{}, "", []string{}, &args)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestEnvironmentVariableInSubcommandIgnored(t *testing.T) {
|
||||
var args struct {
|
||||
Sub *struct {
|
||||
Foo string `arg:"env"`
|
||||
} `arg:"subcommand"`
|
||||
}
|
||||
setenv(t, "FOO", "abc")
|
||||
|
||||
p, err := NewParser(Config{IgnoreEnv: true}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"sub"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, args.Sub)
|
||||
assert.Equal(t, "", args.Sub.Foo)
|
||||
}
|
||||
|
||||
func TestParserMustParseEmptyArgs(t *testing.T) {
|
||||
// this mirrors TestEmptyArgs
|
||||
p, err := NewParser(Config{}, &struct{}{})
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
p.MustParse(nil)
|
||||
}
|
||||
|
||||
func TestParserMustParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args versioned
|
||||
cmdLine []string
|
||||
code int
|
||||
output string
|
||||
}{
|
||||
{name: "help", args: struct{}{}, cmdLine: []string{"--help"}, code: 0, output: "display this help and exit"},
|
||||
{name: "version", args: versioned{}, cmdLine: []string{"--version"}, code: 0, output: "example 3.2.1"},
|
||||
{name: "invalid", args: struct{}{}, cmdLine: []string{"invalid"}, code: 2, output: ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var exitCode int
|
||||
var stdout bytes.Buffer
|
||||
exit := func(code int) { exitCode = code }
|
||||
|
||||
p, err := NewParser(Config{Exit: exit, Out: &stdout}, &tt.args)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, p)
|
||||
|
||||
p.MustParse(tt.cmdLine)
|
||||
assert.NotNil(t, exitCode)
|
||||
assert.Equal(t, tt.code, exitCode)
|
||||
assert.Contains(t, stdout.String(), tt.output)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type textUnmarshaler struct {
|
||||
val int
|
||||
}
|
||||
|
@ -1100,24 +803,6 @@ func TestPtrToIP(t *testing.T) {
|
|||
assert.Equal(t, "192.168.0.1", args.Host.String())
|
||||
}
|
||||
|
||||
func TestURL(t *testing.T) {
|
||||
var args struct {
|
||||
URL url.URL
|
||||
}
|
||||
err := parse("--url https://example.com/get?item=xyz", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "https://example.com/get?item=xyz", args.URL.String())
|
||||
}
|
||||
|
||||
func TestPtrToURL(t *testing.T) {
|
||||
var args struct {
|
||||
URL *url.URL
|
||||
}
|
||||
err := parse("--url http://example.com/#xyz", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "http://example.com/#xyz", args.URL.String())
|
||||
}
|
||||
|
||||
func TestIPSlice(t *testing.T) {
|
||||
var args struct {
|
||||
Host []net.IP
|
||||
|
@ -1214,67 +899,6 @@ func TestEmbeddedPtrIgnored(t *testing.T) {
|
|||
assert.Equal(t, 321, args.Y)
|
||||
}
|
||||
|
||||
func TestEmbeddedWithDuplicateField(t *testing.T) {
|
||||
// see https://github.com/alexflint/go-arg/issues/100
|
||||
type T struct {
|
||||
A string `arg:"--cat"`
|
||||
}
|
||||
type U struct {
|
||||
A string `arg:"--dog"`
|
||||
}
|
||||
var args struct {
|
||||
T
|
||||
U
|
||||
}
|
||||
|
||||
err := parse("--cat=cat --dog=dog", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "cat", args.T.A)
|
||||
assert.Equal(t, "dog", args.U.A)
|
||||
}
|
||||
|
||||
func TestEmbeddedWithDuplicateField2(t *testing.T) {
|
||||
// see https://github.com/alexflint/go-arg/issues/100
|
||||
type T struct {
|
||||
A string
|
||||
}
|
||||
type U struct {
|
||||
A string
|
||||
}
|
||||
var args struct {
|
||||
T
|
||||
U
|
||||
}
|
||||
|
||||
err := parse("--a=xyz", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "xyz", args.T.A)
|
||||
assert.Equal(t, "", args.U.A)
|
||||
}
|
||||
|
||||
func TestUnexportedEmbedded(t *testing.T) {
|
||||
type embeddedArgs struct {
|
||||
Foo string
|
||||
}
|
||||
var args struct {
|
||||
embeddedArgs
|
||||
}
|
||||
err := parse("--foo bar", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "bar", args.Foo)
|
||||
}
|
||||
|
||||
func TestIgnoredEmbedded(t *testing.T) {
|
||||
type embeddedArgs struct {
|
||||
Foo string
|
||||
}
|
||||
var args struct {
|
||||
embeddedArgs `arg:"-"`
|
||||
}
|
||||
err := parse("--foo bar", &args)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestEmptyArgs(t *testing.T) {
|
||||
origArgs := os.Args
|
||||
|
||||
|
@ -1415,55 +1039,11 @@ func TestReuseParser(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNoVersion(t *testing.T) {
|
||||
func TestVersion(t *testing.T) {
|
||||
var args struct{}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"--version"})
|
||||
assert.Error(t, err)
|
||||
assert.NotEqual(t, ErrVersion, err)
|
||||
}
|
||||
|
||||
func TestBuiltinVersion(t *testing.T) {
|
||||
var args struct{}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
p.version = "example 3.2.1"
|
||||
|
||||
err = p.Parse([]string{"--version"})
|
||||
err := parse("--version", &args)
|
||||
assert.Equal(t, ErrVersion, err)
|
||||
}
|
||||
|
||||
func TestArgsVersion(t *testing.T) {
|
||||
var args struct {
|
||||
Version bool `arg:"--version"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"--version"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, args.Version, true)
|
||||
}
|
||||
|
||||
func TestArgsAndBuiltinVersion(t *testing.T) {
|
||||
var args struct {
|
||||
Version bool `arg:"--version"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
p.version = "example 3.2.1"
|
||||
|
||||
err = p.Parse([]string{"--version"})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, args.Version, true)
|
||||
}
|
||||
|
||||
func TestMultipleTerminates(t *testing.T) {
|
||||
|
@ -1477,305 +1057,3 @@ func TestMultipleTerminates(t *testing.T) {
|
|||
assert.Equal(t, []string{"a", "b"}, args.X)
|
||||
assert.Equal(t, "c", args.Y)
|
||||
}
|
||||
|
||||
func TestDefaultOptionValues(t *testing.T) {
|
||||
var args struct {
|
||||
A int `default:"123"`
|
||||
B *int `default:"123"`
|
||||
C string `default:"abc"`
|
||||
D *string `default:"abc"`
|
||||
E float64 `default:"1.23"`
|
||||
F *float64 `default:"1.23"`
|
||||
G bool `default:"true"`
|
||||
H *bool `default:"true"`
|
||||
}
|
||||
|
||||
err := parse("--c=xyz --e=4.56", &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 123, args.A)
|
||||
if assert.NotNil(t, args.B) {
|
||||
assert.Equal(t, 123, *args.B)
|
||||
}
|
||||
assert.Equal(t, "xyz", args.C)
|
||||
if assert.NotNil(t, args.D) {
|
||||
assert.Equal(t, "abc", *args.D)
|
||||
}
|
||||
assert.Equal(t, 4.56, args.E)
|
||||
if assert.NotNil(t, args.F) {
|
||||
assert.Equal(t, 1.23, *args.F)
|
||||
}
|
||||
assert.True(t, args.G)
|
||||
if assert.NotNil(t, args.H) {
|
||||
assert.True(t, *args.H)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultUnparseable(t *testing.T) {
|
||||
var args struct {
|
||||
A int `default:"x"`
|
||||
}
|
||||
|
||||
err := parse("", &args)
|
||||
assert.EqualError(t, err, `.A: error processing default value: strconv.ParseInt: parsing "x": invalid syntax`)
|
||||
}
|
||||
|
||||
func TestDefaultPositionalValues(t *testing.T) {
|
||||
var args struct {
|
||||
A int `arg:"positional" default:"123"`
|
||||
B *int `arg:"positional" default:"123"`
|
||||
C string `arg:"positional" default:"abc"`
|
||||
D *string `arg:"positional" default:"abc"`
|
||||
E float64 `arg:"positional" default:"1.23"`
|
||||
F *float64 `arg:"positional" default:"1.23"`
|
||||
G bool `arg:"positional" default:"true"`
|
||||
H *bool `arg:"positional" default:"true"`
|
||||
}
|
||||
|
||||
err := parse("456 789", &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 456, args.A)
|
||||
if assert.NotNil(t, args.B) {
|
||||
assert.Equal(t, 789, *args.B)
|
||||
}
|
||||
assert.Equal(t, "abc", args.C)
|
||||
if assert.NotNil(t, args.D) {
|
||||
assert.Equal(t, "abc", *args.D)
|
||||
}
|
||||
assert.Equal(t, 1.23, args.E)
|
||||
if assert.NotNil(t, args.F) {
|
||||
assert.Equal(t, 1.23, *args.F)
|
||||
}
|
||||
assert.True(t, args.G)
|
||||
if assert.NotNil(t, args.H) {
|
||||
assert.True(t, *args.H)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultValuesNotAllowedWithRequired(t *testing.T) {
|
||||
var args struct {
|
||||
A int `arg:"required" default:"123"` // required not allowed with default!
|
||||
}
|
||||
|
||||
err := parse("", &args)
|
||||
assert.EqualError(t, err, ".A: 'required' cannot be used when a default value is specified")
|
||||
}
|
||||
|
||||
func TestDefaultValuesNotAllowedWithSlice(t *testing.T) {
|
||||
var args struct {
|
||||
A []int `default:"invalid"` // default values not allowed with slices
|
||||
}
|
||||
|
||||
err := parse("", &args)
|
||||
assert.EqualError(t, err, ".A: default values are not supported for slice or map fields")
|
||||
}
|
||||
|
||||
func TestUnexportedFieldsSkipped(t *testing.T) {
|
||||
var args struct {
|
||||
unexported struct{}
|
||||
}
|
||||
|
||||
_, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestMustParseInvalidParser(t *testing.T) {
|
||||
var exitCode int
|
||||
var stdout bytes.Buffer
|
||||
exit := func(code int) { exitCode = code }
|
||||
|
||||
var args struct {
|
||||
CannotParse struct{}
|
||||
}
|
||||
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
||||
assert.Nil(t, parser)
|
||||
assert.Equal(t, 2, exitCode)
|
||||
}
|
||||
|
||||
func TestMustParsePrintsHelp(t *testing.T) {
|
||||
originalArgs := os.Args
|
||||
defer func() {
|
||||
os.Args = originalArgs
|
||||
}()
|
||||
|
||||
os.Args = []string{"someprogram", "--help"}
|
||||
|
||||
var exitCode int
|
||||
var stdout bytes.Buffer
|
||||
exit := func(code int) { exitCode = code }
|
||||
|
||||
var args struct{}
|
||||
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
||||
assert.NotNil(t, parser)
|
||||
assert.Equal(t, 0, exitCode)
|
||||
}
|
||||
|
||||
func TestMustParsePrintsVersion(t *testing.T) {
|
||||
originalArgs := os.Args
|
||||
defer func() {
|
||||
os.Args = originalArgs
|
||||
}()
|
||||
|
||||
var exitCode int
|
||||
var stdout bytes.Buffer
|
||||
exit := func(code int) { exitCode = code }
|
||||
|
||||
os.Args = []string{"someprogram", "--version"}
|
||||
|
||||
var args versioned
|
||||
parser := mustParse(Config{Out: &stdout, Exit: exit}, &args)
|
||||
require.NotNil(t, parser)
|
||||
assert.Equal(t, 0, exitCode)
|
||||
assert.Equal(t, "example 3.2.1\n", stdout.String())
|
||||
}
|
||||
|
||||
type mapWithUnmarshalText struct {
|
||||
val map[string]string
|
||||
}
|
||||
|
||||
func (v *mapWithUnmarshalText) UnmarshalText(data []byte) error {
|
||||
return json.Unmarshal(data, &v.val)
|
||||
}
|
||||
|
||||
func TestTextUnmarshalerEmpty(t *testing.T) {
|
||||
// based on https://github.com/alexflint/go-arg/issues/184
|
||||
var args struct {
|
||||
Config mapWithUnmarshalText `arg:"--config"`
|
||||
}
|
||||
|
||||
err := parse("", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, args.Config)
|
||||
}
|
||||
|
||||
func TestTextUnmarshalerEmptyPointer(t *testing.T) {
|
||||
// a slight variant on https://github.com/alexflint/go-arg/issues/184
|
||||
var args struct {
|
||||
Config *mapWithUnmarshalText `arg:"--config"`
|
||||
}
|
||||
|
||||
err := parse("", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, args.Config)
|
||||
}
|
||||
|
||||
// similar to the above but also implements MarshalText
|
||||
type mapWithMarshalText struct {
|
||||
val map[string]string
|
||||
}
|
||||
|
||||
func (v *mapWithMarshalText) MarshalText(data []byte) error {
|
||||
return json.Unmarshal(data, &v.val)
|
||||
}
|
||||
|
||||
func (v *mapWithMarshalText) UnmarshalText(data []byte) error {
|
||||
return json.Unmarshal(data, &v.val)
|
||||
}
|
||||
|
||||
func TestTextMarshalerUnmarshalerEmpty(t *testing.T) {
|
||||
// based on https://github.com/alexflint/go-arg/issues/184
|
||||
var args struct {
|
||||
Config mapWithMarshalText `arg:"--config"`
|
||||
}
|
||||
|
||||
err := parse("", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, args.Config)
|
||||
}
|
||||
|
||||
func TestTextMarshalerUnmarshalerEmptyPointer(t *testing.T) {
|
||||
// a slight variant on https://github.com/alexflint/go-arg/issues/184
|
||||
var args struct {
|
||||
Config *mapWithMarshalText `arg:"--config"`
|
||||
}
|
||||
|
||||
err := parse("", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, args.Config)
|
||||
}
|
||||
|
||||
func TestSubcommandGlobalFlag_Before(t *testing.T) {
|
||||
var args struct {
|
||||
Global bool `arg:"-g"`
|
||||
Sub *struct {
|
||||
} `arg:"subcommand"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{StrictSubcommands: false}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"-g", "sub"})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, args.Global)
|
||||
}
|
||||
|
||||
func TestSubcommandGlobalFlag_InCommand(t *testing.T) {
|
||||
var args struct {
|
||||
Global bool `arg:"-g"`
|
||||
Sub *struct {
|
||||
} `arg:"subcommand"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{StrictSubcommands: false}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"sub", "-g"})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, args.Global)
|
||||
}
|
||||
|
||||
func TestSubcommandGlobalFlag_Before_Strict(t *testing.T) {
|
||||
var args struct {
|
||||
Global bool `arg:"-g"`
|
||||
Sub *struct {
|
||||
} `arg:"subcommand"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{StrictSubcommands: true}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"-g", "sub"})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, args.Global)
|
||||
}
|
||||
|
||||
func TestSubcommandGlobalFlag_InCommand_Strict(t *testing.T) {
|
||||
var args struct {
|
||||
Global bool `arg:"-g"`
|
||||
Sub *struct {
|
||||
} `arg:"subcommand"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{StrictSubcommands: true}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"sub", "-g"})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSubcommandGlobalFlag_InCommand_Strict_Inner(t *testing.T) {
|
||||
var args struct {
|
||||
Global bool `arg:"-g"`
|
||||
Sub *struct {
|
||||
Guard bool `arg:"-g"`
|
||||
} `arg:"subcommand"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{StrictSubcommands: true}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = p.Parse([]string{"sub", "-g"})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, args.Global)
|
||||
require.NotNil(t, args.Sub)
|
||||
assert.True(t, args.Sub.Guard)
|
||||
}
|
||||
|
||||
func TestExitFunctionAndOutStreamGetFilledIn(t *testing.T) {
|
||||
var args struct{}
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, p.config.Exit) // go prohibits function pointer comparison
|
||||
assert.Equal(t, p.config.Out, os.Stdout)
|
||||
}
|
114
reflect.go
114
reflect.go
|
@ -2,82 +2,55 @@ package arg
|
|||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"go.wit.com/dev/alexflint/scalar"
|
||||
scalar "github.com/alexflint/go-scalar"
|
||||
)
|
||||
|
||||
var textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem()
|
||||
|
||||
// cardinality tracks how many tokens are expected for a given spec
|
||||
// - zero is a boolean, which does to expect any value
|
||||
// - one is an ordinary option that will be parsed from a single token
|
||||
// - multiple is a slice or map that can accept zero or more tokens
|
||||
type cardinality int
|
||||
|
||||
const (
|
||||
zero cardinality = iota
|
||||
one
|
||||
multiple
|
||||
unsupported
|
||||
)
|
||||
|
||||
func (k cardinality) String() string {
|
||||
switch k {
|
||||
case zero:
|
||||
return "zero"
|
||||
case one:
|
||||
return "one"
|
||||
case multiple:
|
||||
return "multiple"
|
||||
case unsupported:
|
||||
return "unsupported"
|
||||
default:
|
||||
return fmt.Sprintf("unknown(%d)", int(k))
|
||||
}
|
||||
}
|
||||
|
||||
// cardinalityOf returns true if the type can be parsed from a string
|
||||
func cardinalityOf(t reflect.Type) (cardinality, error) {
|
||||
if scalar.CanParse(t) {
|
||||
if isBoolean(t) {
|
||||
return zero, nil
|
||||
}
|
||||
return one, nil
|
||||
// canParse returns true if the type can be parsed from a string
|
||||
func canParse(t reflect.Type) (parseable, boolean, multiple bool) {
|
||||
parseable = scalar.CanParse(t)
|
||||
boolean = isBoolean(t)
|
||||
if parseable {
|
||||
return
|
||||
}
|
||||
|
||||
// look inside pointer types
|
||||
// Look inside pointer types
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
// Look inside slice types
|
||||
if t.Kind() == reflect.Slice {
|
||||
multiple = true
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
parseable = scalar.CanParse(t)
|
||||
boolean = isBoolean(t)
|
||||
if parseable {
|
||||
return
|
||||
}
|
||||
|
||||
// Look inside pointer types (again, in case of []*Type)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
// look inside slice and map types
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
if !scalar.CanParse(t.Elem()) {
|
||||
return unsupported, fmt.Errorf("cannot parse into %v because %v not supported", t, t.Elem())
|
||||
}
|
||||
return multiple, nil
|
||||
case reflect.Map:
|
||||
if !scalar.CanParse(t.Key()) {
|
||||
return unsupported, fmt.Errorf("cannot parse into %v because key type %v not supported", t, t.Elem())
|
||||
}
|
||||
if !scalar.CanParse(t.Elem()) {
|
||||
return unsupported, fmt.Errorf("cannot parse into %v because value type %v not supported", t, t.Elem())
|
||||
}
|
||||
return multiple, nil
|
||||
default:
|
||||
return unsupported, fmt.Errorf("cannot parse into %v", t)
|
||||
parseable = scalar.CanParse(t)
|
||||
boolean = isBoolean(t)
|
||||
if parseable {
|
||||
return
|
||||
}
|
||||
|
||||
return false, false, false
|
||||
}
|
||||
|
||||
// isBoolean returns true if the type is a boolean or a pointer to a boolean
|
||||
// isBoolean returns true if the type can be parsed from a single string
|
||||
func isBoolean(t reflect.Type) bool {
|
||||
switch {
|
||||
case isTextUnmarshaler(t):
|
||||
case t.Implements(textUnmarshalerType):
|
||||
return false
|
||||
case t.Kind() == reflect.Bool:
|
||||
return true
|
||||
|
@ -87,26 +60,3 @@ func isBoolean(t reflect.Type) bool {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isTextUnmarshaler returns true if the type or its pointer implements encoding.TextUnmarshaler
|
||||
func isTextUnmarshaler(t reflect.Type) bool {
|
||||
return t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType)
|
||||
}
|
||||
|
||||
// isExported returns true if the struct field name is exported
|
||||
func isExported(field string) bool {
|
||||
r, _ := utf8.DecodeRuneInString(field) // returns RuneError for empty string or invalid UTF8
|
||||
return unicode.IsLetter(r) && unicode.IsUpper(r)
|
||||
}
|
||||
|
||||
// isZero returns true if v contains the zero value for its type
|
||||
func isZero(v reflect.Value) bool {
|
||||
t := v.Type()
|
||||
if t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice || t.Kind() == reflect.Map || t.Kind() == reflect.Chan || t.Kind() == reflect.Interface {
|
||||
return v.IsNil()
|
||||
}
|
||||
if !t.Comparable() {
|
||||
return false
|
||||
}
|
||||
return v.Interface() == reflect.Zero(t).Interface()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func assertCanParse(t *testing.T, typ reflect.Type, parseable, boolean, multiple bool) {
|
||||
p, b, m := canParse(typ)
|
||||
assert.Equal(t, parseable, p, "expected %v to have parseable=%v but was %v", typ, parseable, p)
|
||||
assert.Equal(t, boolean, b, "expected %v to have boolean=%v but was %v", typ, boolean, b)
|
||||
assert.Equal(t, multiple, m, "expected %v to have multiple=%v but was %v", typ, multiple, m)
|
||||
}
|
||||
|
||||
func TestCanParse(t *testing.T) {
|
||||
var b bool
|
||||
var i int
|
||||
var s string
|
||||
var f float64
|
||||
var bs []bool
|
||||
var is []int
|
||||
|
||||
assertCanParse(t, reflect.TypeOf(b), true, true, false)
|
||||
assertCanParse(t, reflect.TypeOf(i), true, false, false)
|
||||
assertCanParse(t, reflect.TypeOf(s), true, false, false)
|
||||
assertCanParse(t, reflect.TypeOf(f), true, false, false)
|
||||
|
||||
assertCanParse(t, reflect.TypeOf(&b), true, true, false)
|
||||
assertCanParse(t, reflect.TypeOf(&s), true, false, false)
|
||||
assertCanParse(t, reflect.TypeOf(&i), true, false, false)
|
||||
assertCanParse(t, reflect.TypeOf(&f), true, false, false)
|
||||
|
||||
assertCanParse(t, reflect.TypeOf(bs), true, true, true)
|
||||
assertCanParse(t, reflect.TypeOf(&bs), true, true, true)
|
||||
|
||||
assertCanParse(t, reflect.TypeOf(is), true, false, true)
|
||||
assertCanParse(t, reflect.TypeOf(&is), true, false, true)
|
||||
}
|
||||
|
||||
type implementsTextUnmarshaler struct{}
|
||||
|
||||
func (*implementsTextUnmarshaler) UnmarshalText(text []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCanParseTextUnmarshaler(t *testing.T) {
|
||||
var u implementsTextUnmarshaler
|
||||
var su []implementsTextUnmarshaler
|
||||
assertCanParse(t, reflect.TypeOf(u), true, false, false)
|
||||
assertCanParse(t, reflect.TypeOf(&u), true, false, false)
|
||||
assertCanParse(t, reflect.TypeOf(su), true, false, true)
|
||||
assertCanParse(t, reflect.TypeOf(&su), true, false, true)
|
||||
}
|
123
sequence.go
123
sequence.go
|
@ -1,123 +0,0 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"go.wit.com/dev/alexflint/scalar"
|
||||
)
|
||||
|
||||
// setSliceOrMap parses a sequence of strings into a slice or map. If clear is
|
||||
// true then any values already in the slice or map are first removed.
|
||||
func setSliceOrMap(dest reflect.Value, values []string, clear bool) error {
|
||||
if !dest.CanSet() {
|
||||
return fmt.Errorf("field is not writable")
|
||||
}
|
||||
|
||||
t := dest.Type()
|
||||
if t.Kind() == reflect.Ptr {
|
||||
dest = dest.Elem()
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Slice:
|
||||
return setSlice(dest, values, clear)
|
||||
case reflect.Map:
|
||||
return setMap(dest, values, clear)
|
||||
default:
|
||||
return fmt.Errorf("setSliceOrMap cannot insert values into a %v", t)
|
||||
}
|
||||
}
|
||||
|
||||
// setSlice parses a sequence of strings and inserts them into a slice. If clear
|
||||
// is true then any values already in the slice are removed.
|
||||
func setSlice(dest reflect.Value, values []string, clear bool) error {
|
||||
var ptr bool
|
||||
elem := dest.Type().Elem()
|
||||
if elem.Kind() == reflect.Ptr && !elem.Implements(textUnmarshalerType) {
|
||||
ptr = true
|
||||
elem = elem.Elem()
|
||||
}
|
||||
|
||||
// clear the slice in case default values exist
|
||||
if clear && !dest.IsNil() {
|
||||
dest.SetLen(0)
|
||||
}
|
||||
|
||||
// parse the values one-by-one
|
||||
for _, s := range values {
|
||||
v := reflect.New(elem)
|
||||
if err := scalar.ParseValue(v.Elem(), s); err != nil {
|
||||
return err
|
||||
}
|
||||
if !ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
dest.Set(reflect.Append(dest, v))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setMap parses a sequence of name=value strings and inserts them into a map.
|
||||
// If clear is true then any values already in the map are removed.
|
||||
func setMap(dest reflect.Value, values []string, clear bool) error {
|
||||
// determine the key and value type
|
||||
var keyIsPtr bool
|
||||
keyType := dest.Type().Key()
|
||||
if keyType.Kind() == reflect.Ptr && !keyType.Implements(textUnmarshalerType) {
|
||||
keyIsPtr = true
|
||||
keyType = keyType.Elem()
|
||||
}
|
||||
|
||||
var valIsPtr bool
|
||||
valType := dest.Type().Elem()
|
||||
if valType.Kind() == reflect.Ptr && !valType.Implements(textUnmarshalerType) {
|
||||
valIsPtr = true
|
||||
valType = valType.Elem()
|
||||
}
|
||||
|
||||
// clear the slice in case default values exist
|
||||
if clear && !dest.IsNil() {
|
||||
for _, k := range dest.MapKeys() {
|
||||
dest.SetMapIndex(k, reflect.Value{})
|
||||
}
|
||||
}
|
||||
|
||||
// allocate the map if it is not allocated
|
||||
if dest.IsNil() {
|
||||
dest.Set(reflect.MakeMap(dest.Type()))
|
||||
}
|
||||
|
||||
// parse the values one-by-one
|
||||
for _, s := range values {
|
||||
// split at the first equals sign
|
||||
pos := strings.Index(s, "=")
|
||||
if pos == -1 {
|
||||
return fmt.Errorf("cannot parse %q into a map, expected format key=value", s)
|
||||
}
|
||||
|
||||
// parse the key
|
||||
k := reflect.New(keyType)
|
||||
if err := scalar.ParseValue(k.Elem(), s[:pos]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !keyIsPtr {
|
||||
k = k.Elem()
|
||||
}
|
||||
|
||||
// parse the value
|
||||
v := reflect.New(valType)
|
||||
if err := scalar.ParseValue(v.Elem(), s[pos+1:]); err != nil {
|
||||
return err
|
||||
}
|
||||
if !valIsPtr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
// add it to the map
|
||||
dest.SetMapIndex(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package arg
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Subcommand returns the user struct for the subcommand selected by
|
||||
// the command line arguments most recently processed by the parser.
|
||||
// The return value is always a pointer to a struct. If no subcommand
|
||||
|
@ -9,35 +7,31 @@ import "fmt"
|
|||
// no command line arguments have been processed by this parser then it
|
||||
// returns nil.
|
||||
func (p *Parser) Subcommand() interface{} {
|
||||
if len(p.subcommand) == 0 {
|
||||
if p.lastCmd == nil || p.lastCmd.parent == nil {
|
||||
return nil
|
||||
}
|
||||
cmd, err := p.lookupCommand(p.subcommand...)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return p.val(cmd.dest).Interface()
|
||||
return p.val(p.lastCmd.dest).Interface()
|
||||
}
|
||||
|
||||
// SubcommandNames returns the sequence of subcommands specified by the
|
||||
// user. If no subcommands were given then it returns an empty slice.
|
||||
func (p *Parser) SubcommandNames() []string {
|
||||
return p.subcommand
|
||||
}
|
||||
if p.lastCmd == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// lookupCommand finds a subcommand based on a sequence of subcommand names. The
|
||||
// first string should be a top-level subcommand, the next should be a child
|
||||
// subcommand of that subcommand, and so on. If no strings are given then the
|
||||
// root command is returned. If no such subcommand exists then an error is
|
||||
// returned.
|
||||
func (p *Parser) lookupCommand(path ...string) (*command, error) {
|
||||
cmd := p.cmd
|
||||
for _, name := range path {
|
||||
found := findSubcommand(cmd.subcommands, name)
|
||||
if found == nil {
|
||||
return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name)
|
||||
// make a list of ancestor commands
|
||||
var ancestors []string
|
||||
cur := p.lastCmd
|
||||
for cur.parent != nil { // we want to exclude the root
|
||||
ancestors = append(ancestors, cur.name)
|
||||
cur = cur.parent
|
||||
}
|
||||
cmd = found
|
||||
|
||||
// reverse the list
|
||||
out := make([]string, len(ancestors))
|
||||
for i := 0; i < len(ancestors); i++ {
|
||||
out[i] = ancestors[len(ancestors)-i-1]
|
||||
}
|
||||
return cmd, nil
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -49,17 +48,6 @@ func TestMinimalSubcommand(t *testing.T) {
|
|||
assert.Equal(t, []string{"list"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
func TestSubcommandNamesBeforeParsing(t *testing.T) {
|
||||
type listCmd struct{}
|
||||
var args struct {
|
||||
List *listCmd `arg:"subcommand"`
|
||||
}
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, p.Subcommand())
|
||||
assert.Nil(t, p.SubcommandNames())
|
||||
}
|
||||
|
||||
func TestNoSuchSubcommand(t *testing.T) {
|
||||
type listCmd struct {
|
||||
}
|
||||
|
@ -83,19 +71,6 @@ func TestNamedSubcommand(t *testing.T) {
|
|||
assert.Equal(t, []string{"ls"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
func TestSubcommandAliases(t *testing.T) {
|
||||
type listCmd struct {
|
||||
}
|
||||
var args struct {
|
||||
List *listCmd `arg:"subcommand:list|ls"`
|
||||
}
|
||||
p, err := pparse("ls", &args)
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, args.List)
|
||||
assert.Equal(t, args.List, p.Subcommand())
|
||||
assert.Equal(t, []string{"ls"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
func TestEmptySubcommand(t *testing.T) {
|
||||
type listCmd struct {
|
||||
}
|
||||
|
@ -126,23 +101,6 @@ func TestTwoSubcommands(t *testing.T) {
|
|||
assert.Equal(t, []string{"list"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
func TestTwoSubcommandsWithAliases(t *testing.T) {
|
||||
type getCmd struct {
|
||||
}
|
||||
type listCmd struct {
|
||||
}
|
||||
var args struct {
|
||||
Get *getCmd `arg:"subcommand:get|g"`
|
||||
List *listCmd `arg:"subcommand:list|ls"`
|
||||
}
|
||||
p, err := pparse("ls", &args)
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, args.Get)
|
||||
assert.NotNil(t, args.List)
|
||||
assert.Equal(t, args.List, p.Subcommand())
|
||||
assert.Equal(t, []string{"ls"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
func TestSubcommandsWithOptions(t *testing.T) {
|
||||
type getCmd struct {
|
||||
Name string
|
||||
|
@ -221,36 +179,6 @@ func TestSubcommandsWithOptions(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSubcommandsWithEnvVars(t *testing.T) {
|
||||
type getCmd struct {
|
||||
Name string `arg:"env"`
|
||||
}
|
||||
type listCmd struct {
|
||||
Limit int `arg:"env"`
|
||||
}
|
||||
type cmd struct {
|
||||
Verbose bool
|
||||
Get *getCmd `arg:"subcommand"`
|
||||
List *listCmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
{
|
||||
var args cmd
|
||||
setenv(t, "LIMIT", "123")
|
||||
err := parse("list", &args)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, args.List)
|
||||
assert.Equal(t, 123, args.List.Limit)
|
||||
}
|
||||
|
||||
{
|
||||
var args cmd
|
||||
setenv(t, "LIMIT", "not_an_integer")
|
||||
err := parse("list", &args)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNestedSubcommands(t *testing.T) {
|
||||
type child struct{}
|
||||
type parent struct {
|
||||
|
@ -305,60 +233,6 @@ func TestNestedSubcommands(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNestedSubcommandsWithAliases(t *testing.T) {
|
||||
type child struct{}
|
||||
type parent struct {
|
||||
Child *child `arg:"subcommand:child|ch"`
|
||||
}
|
||||
type grandparent struct {
|
||||
Parent *parent `arg:"subcommand:parent|pa"`
|
||||
}
|
||||
type root struct {
|
||||
Grandparent *grandparent `arg:"subcommand:grandparent|gp"`
|
||||
}
|
||||
|
||||
{
|
||||
var args root
|
||||
p, err := pparse("gp parent child", &args)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, args.Grandparent)
|
||||
require.NotNil(t, args.Grandparent.Parent)
|
||||
require.NotNil(t, args.Grandparent.Parent.Child)
|
||||
assert.Equal(t, args.Grandparent.Parent.Child, p.Subcommand())
|
||||
assert.Equal(t, []string{"gp", "parent", "child"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
{
|
||||
var args root
|
||||
p, err := pparse("grandparent pa", &args)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, args.Grandparent)
|
||||
require.NotNil(t, args.Grandparent.Parent)
|
||||
require.Nil(t, args.Grandparent.Parent.Child)
|
||||
assert.Equal(t, args.Grandparent.Parent, p.Subcommand())
|
||||
assert.Equal(t, []string{"grandparent", "pa"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
{
|
||||
var args root
|
||||
p, err := pparse("grandparent", &args)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, args.Grandparent)
|
||||
require.Nil(t, args.Grandparent.Parent)
|
||||
assert.Equal(t, args.Grandparent, p.Subcommand())
|
||||
assert.Equal(t, []string{"grandparent"}, p.SubcommandNames())
|
||||
}
|
||||
|
||||
{
|
||||
var args root
|
||||
p, err := pparse("", &args)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, args.Grandparent)
|
||||
assert.Nil(t, p.Subcommand())
|
||||
assert.Empty(t, p.SubcommandNames())
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubcommandsWithPositionals(t *testing.T) {
|
||||
type listCmd struct {
|
||||
Pattern string `arg:"positional"`
|
||||
|
@ -479,30 +353,3 @@ func TestSubcommandsWithMultiplePositionals(t *testing.T) {
|
|||
assert.Equal(t, 5, args.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValForNilStruct(t *testing.T) {
|
||||
type subcmd struct{}
|
||||
var cmd struct {
|
||||
Sub *subcmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
typ := reflect.TypeOf(cmd)
|
||||
subField, _ := typ.FieldByName("Sub")
|
||||
|
||||
v := p.val(path{fields: []reflect.StructField{subField, subField}})
|
||||
assert.False(t, v.IsValid())
|
||||
}
|
||||
|
||||
func TestSubcommandInvalidInternal(t *testing.T) {
|
||||
// this situation should never arise in practice but still good to test for it
|
||||
var cmd struct{}
|
||||
p, err := NewParser(Config{}, &cmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
p.subcommand = []string{"should", "never", "happen"}
|
||||
sub := p.Subcommand()
|
||||
assert.Nil(t, sub)
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
all:
|
||||
@echo
|
||||
@echo
|
||||
|
||||
test:
|
||||
|
||||
redomod:
|
||||
rm -f go.*
|
||||
GO111MODULE= go mod init
|
||||
GO111MODULE= go mod tidy
|
|
@ -0,0 +1 @@
|
|||
This directory contains integration tests that check that go-arg can be installed and compiled under both tgo 1.10 and go 1.11 (corresponding to the transition to the new go module system)
|
|
@ -0,0 +1,14 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This test checks that we can correctly "go get" and then use the go-arg package
|
||||
# under go 1.10, which was the last release before introduction of the new go
|
||||
# module system.
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-v $DIR/some-program:/src \
|
||||
-w /src \
|
||||
golang:1.10 \
|
||||
bash -c "go get github.com/alexflint/go-arg && go build -o /dev/null"
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This test checks that we can compile some code that depends on go-arg when using go 1.11
|
||||
# with the new go module system active.
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-v $DIR/some-program:/src \
|
||||
-w /src \
|
||||
golang:1.11 \
|
||||
go build -o /dev/null
|
|
@ -0,0 +1,13 @@
|
|||
#!/bin/bash
|
||||
|
||||
# This test checks that we can correctly "go get" and then use the go-arg package using
|
||||
# go 1.11 when the code is within the GOPATH (in which case modules are disabled by default).
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
docker run \
|
||||
--rm \
|
||||
-v $DIR/some-program:/go/src/some-program \
|
||||
-w /go/src/some-program \
|
||||
golang:1.11 \
|
||||
bash -c "go get github.com/alexflint/go-arg && go build -o /dev/null"
|
11
test/go.mod
11
test/go.mod
|
@ -1,11 +0,0 @@
|
|||
module go.wit.com/dev/alexflint/arg/test
|
||||
|
||||
go 1.21.4
|
||||
|
||||
require github.com/stretchr/testify v1.8.4
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
10
test/go.sum
10
test/go.sum
|
@ -1,10 +0,0 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@ -1,112 +0,0 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func assertCardinality(t *testing.T, typ reflect.Type, expected cardinality) {
|
||||
actual, err := cardinalityOf(typ)
|
||||
assert.Equal(t, expected, actual, "expected %v to have cardinality %v but got %v", typ, expected, actual)
|
||||
if expected == unsupported {
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCardinalityOf(t *testing.T) {
|
||||
var b bool
|
||||
var i int
|
||||
var s string
|
||||
var f float64
|
||||
var bs []bool
|
||||
var is []int
|
||||
var m map[string]int
|
||||
var unsupported1 struct{}
|
||||
var unsupported2 []struct{}
|
||||
var unsupported3 map[string]struct{}
|
||||
var unsupported4 map[struct{}]string
|
||||
|
||||
assertCardinality(t, reflect.TypeOf(b), zero)
|
||||
assertCardinality(t, reflect.TypeOf(i), one)
|
||||
assertCardinality(t, reflect.TypeOf(s), one)
|
||||
assertCardinality(t, reflect.TypeOf(f), one)
|
||||
|
||||
assertCardinality(t, reflect.TypeOf(&b), zero)
|
||||
assertCardinality(t, reflect.TypeOf(&s), one)
|
||||
assertCardinality(t, reflect.TypeOf(&i), one)
|
||||
assertCardinality(t, reflect.TypeOf(&f), one)
|
||||
|
||||
assertCardinality(t, reflect.TypeOf(bs), multiple)
|
||||
assertCardinality(t, reflect.TypeOf(is), multiple)
|
||||
|
||||
assertCardinality(t, reflect.TypeOf(&bs), multiple)
|
||||
assertCardinality(t, reflect.TypeOf(&is), multiple)
|
||||
|
||||
assertCardinality(t, reflect.TypeOf(m), multiple)
|
||||
assertCardinality(t, reflect.TypeOf(&m), multiple)
|
||||
|
||||
assertCardinality(t, reflect.TypeOf(unsupported1), unsupported)
|
||||
assertCardinality(t, reflect.TypeOf(&unsupported1), unsupported)
|
||||
assertCardinality(t, reflect.TypeOf(unsupported2), unsupported)
|
||||
assertCardinality(t, reflect.TypeOf(&unsupported2), unsupported)
|
||||
assertCardinality(t, reflect.TypeOf(unsupported3), unsupported)
|
||||
assertCardinality(t, reflect.TypeOf(&unsupported3), unsupported)
|
||||
assertCardinality(t, reflect.TypeOf(unsupported4), unsupported)
|
||||
assertCardinality(t, reflect.TypeOf(&unsupported4), unsupported)
|
||||
}
|
||||
|
||||
type implementsTextUnmarshaler struct{}
|
||||
|
||||
func (*implementsTextUnmarshaler) UnmarshalText(text []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCardinalityTextUnmarshaler(t *testing.T) {
|
||||
var x implementsTextUnmarshaler
|
||||
var s []implementsTextUnmarshaler
|
||||
var m []implementsTextUnmarshaler
|
||||
assertCardinality(t, reflect.TypeOf(x), one)
|
||||
assertCardinality(t, reflect.TypeOf(&x), one)
|
||||
assertCardinality(t, reflect.TypeOf(s), multiple)
|
||||
assertCardinality(t, reflect.TypeOf(&s), multiple)
|
||||
assertCardinality(t, reflect.TypeOf(m), multiple)
|
||||
assertCardinality(t, reflect.TypeOf(&m), multiple)
|
||||
}
|
||||
|
||||
func TestIsExported(t *testing.T) {
|
||||
assert.True(t, isExported("Exported"))
|
||||
assert.False(t, isExported("notExported"))
|
||||
assert.False(t, isExported(""))
|
||||
assert.False(t, isExported(string([]byte{255})))
|
||||
}
|
||||
|
||||
func TestCardinalityString(t *testing.T) {
|
||||
assert.Equal(t, "zero", zero.String())
|
||||
assert.Equal(t, "one", one.String())
|
||||
assert.Equal(t, "multiple", multiple.String())
|
||||
assert.Equal(t, "unsupported", unsupported.String())
|
||||
assert.Equal(t, "unknown(42)", cardinality(42).String())
|
||||
}
|
||||
|
||||
func TestIsZero(t *testing.T) {
|
||||
var zero int
|
||||
var notZero = 3
|
||||
var nilSlice []int
|
||||
var nonNilSlice = []int{1, 2, 3}
|
||||
var nilMap map[string]string
|
||||
var nonNilMap = map[string]string{"foo": "bar"}
|
||||
var uncomparable = func() {}
|
||||
|
||||
assert.True(t, isZero(reflect.ValueOf(zero)))
|
||||
assert.False(t, isZero(reflect.ValueOf(notZero)))
|
||||
|
||||
assert.True(t, isZero(reflect.ValueOf(nilSlice)))
|
||||
assert.False(t, isZero(reflect.ValueOf(nonNilSlice)))
|
||||
|
||||
assert.True(t, isZero(reflect.ValueOf(nilMap)))
|
||||
assert.False(t, isZero(reflect.ValueOf(nonNilMap)))
|
||||
|
||||
assert.False(t, isZero(reflect.ValueOf(uncomparable)))
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSetSliceWithoutClearing(t *testing.T) {
|
||||
xs := []int{10}
|
||||
entries := []string{"1", "2", "3"}
|
||||
err := setSlice(reflect.ValueOf(&xs).Elem(), entries, false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []int{10, 1, 2, 3}, xs)
|
||||
}
|
||||
|
||||
func TestSetSliceAfterClearing(t *testing.T) {
|
||||
xs := []int{100}
|
||||
entries := []string{"1", "2", "3"}
|
||||
err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []int{1, 2, 3}, xs)
|
||||
}
|
||||
|
||||
func TestSetSliceInvalid(t *testing.T) {
|
||||
xs := []int{100}
|
||||
entries := []string{"invalid"}
|
||||
err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSetSlicePtr(t *testing.T) {
|
||||
var xs []*int
|
||||
entries := []string{"1", "2", "3"}
|
||||
err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, xs, 3)
|
||||
assert.Equal(t, 1, *xs[0])
|
||||
assert.Equal(t, 2, *xs[1])
|
||||
assert.Equal(t, 3, *xs[2])
|
||||
}
|
||||
|
||||
func TestSetSliceTextUnmarshaller(t *testing.T) {
|
||||
// textUnmarshaler is a struct that captures the length of the string passed to it
|
||||
var xs []*textUnmarshaler
|
||||
entries := []string{"a", "aa", "aaa"}
|
||||
err := setSlice(reflect.ValueOf(&xs).Elem(), entries, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, xs, 3)
|
||||
assert.Equal(t, 1, xs[0].val)
|
||||
assert.Equal(t, 2, xs[1].val)
|
||||
assert.Equal(t, 3, xs[2].val)
|
||||
}
|
||||
|
||||
func TestSetMapWithoutClearing(t *testing.T) {
|
||||
m := map[string]int{"foo": 10}
|
||||
entries := []string{"a=1", "b=2"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, false)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, m, 3)
|
||||
assert.Equal(t, 1, m["a"])
|
||||
assert.Equal(t, 2, m["b"])
|
||||
assert.Equal(t, 10, m["foo"])
|
||||
}
|
||||
|
||||
func TestSetMapAfterClearing(t *testing.T) {
|
||||
m := map[string]int{"foo": 10}
|
||||
entries := []string{"a=1", "b=2"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, m, 2)
|
||||
assert.Equal(t, 1, m["a"])
|
||||
assert.Equal(t, 2, m["b"])
|
||||
}
|
||||
|
||||
func TestSetMapWithKeyPointer(t *testing.T) {
|
||||
// textUnmarshaler is a struct that captures the length of the string passed to it
|
||||
var m map[*string]int
|
||||
entries := []string{"abc=123"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, m, 1)
|
||||
}
|
||||
|
||||
func TestSetMapWithValuePointer(t *testing.T) {
|
||||
// textUnmarshaler is a struct that captures the length of the string passed to it
|
||||
var m map[string]*int
|
||||
entries := []string{"abc=123"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, m, 1)
|
||||
assert.Equal(t, 123, *m["abc"])
|
||||
}
|
||||
|
||||
func TestSetMapTextUnmarshaller(t *testing.T) {
|
||||
// textUnmarshaler is a struct that captures the length of the string passed to it
|
||||
var m map[textUnmarshaler]*textUnmarshaler
|
||||
entries := []string{"a=123", "aa=12", "aaa=1"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, true)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, m, 3)
|
||||
assert.Equal(t, &textUnmarshaler{3}, m[textUnmarshaler{1}])
|
||||
assert.Equal(t, &textUnmarshaler{2}, m[textUnmarshaler{2}])
|
||||
assert.Equal(t, &textUnmarshaler{1}, m[textUnmarshaler{3}])
|
||||
}
|
||||
|
||||
func TestSetMapInvalidKey(t *testing.T) {
|
||||
var m map[int]int
|
||||
entries := []string{"invalid=123"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSetMapInvalidValue(t *testing.T) {
|
||||
var m map[int]int
|
||||
entries := []string{"123=invalid"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSetMapMalformed(t *testing.T) {
|
||||
// textUnmarshaler is a struct that captures the length of the string passed to it
|
||||
var m map[string]string
|
||||
entries := []string{"missing_equals_sign"}
|
||||
err := setMap(reflect.ValueOf(&m).Elem(), entries, true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSetSliceOrMapErrors(t *testing.T) {
|
||||
var err error
|
||||
var dest reflect.Value
|
||||
|
||||
// converting a slice to a reflect.Value in this way will make it read only
|
||||
var cannotSet []int
|
||||
dest = reflect.ValueOf(cannotSet)
|
||||
err = setSliceOrMap(dest, nil, false)
|
||||
assert.Error(t, err)
|
||||
|
||||
// check what happens when we pass in something that is not a slice or a map
|
||||
var notSliceOrMap string
|
||||
dest = reflect.ValueOf(¬SliceOrMap).Elem()
|
||||
err = setSliceOrMap(dest, nil, false)
|
||||
assert.Error(t, err)
|
||||
|
||||
// check what happens when we pass in a pointer to something that is not a slice or a map
|
||||
var stringPtr *string
|
||||
dest = reflect.ValueOf(&stringPtr).Elem()
|
||||
err = setSliceOrMap(dest, nil, false)
|
||||
assert.Error(t, err)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module some-program
|
||||
|
||||
require github.com/alexflint/go-arg v1.0.0
|
|
@ -0,0 +1,10 @@
|
|||
github.com/alexflint/go-arg v1.0.0 h1:VWNnY3DyBHiq5lcwY2FlCE5t5qyHNV0o5i1bkCIHprU=
|
||||
github.com/alexflint/go-arg v1.0.0/go.mod h1:Cto8k5VtkP4pp0EXiWD4ZJMFOOinZ38ggVcQ/6CGuRI=
|
||||
github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
|
||||
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
@ -0,0 +1,11 @@
|
|||
package main
|
||||
|
||||
import "github.com/alexflint/go-arg"
|
||||
|
||||
func main() {
|
||||
var args struct {
|
||||
Test string
|
||||
}
|
||||
arg.MustParse(&args)
|
||||
_ = args.Test
|
||||
}
|
1086
test/usage_test.go
1086
test/usage_test.go
File diff suppressed because it is too large
Load Diff
317
usage.go
317
usage.go
|
@ -1,84 +1,68 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// the width of the left column
|
||||
const colWidth = 25
|
||||
|
||||
// Fail prints usage information to p.Config.Out and exits with status code 2.
|
||||
// to allow monkey patching in tests
|
||||
var stderr = os.Stderr
|
||||
|
||||
// Fail prints usage information to stderr and exits with non-zero status
|
||||
func (p *Parser) Fail(msg string) {
|
||||
p.FailSubcommand(msg)
|
||||
p.failWithCommand(msg, p.cmd)
|
||||
}
|
||||
|
||||
// FailSubcommand prints usage information for a specified subcommand to p.Config.Out,
|
||||
// then exits with status code 2. To write usage information for a top-level
|
||||
// subcommand, provide just the name of that subcommand. To write usage
|
||||
// information for a subcommand that is nested under another subcommand, provide
|
||||
// a sequence of subcommand names starting with the top-level subcommand and so
|
||||
// on down the tree.
|
||||
func (p *Parser) FailSubcommand(msg string, subcommand ...string) error {
|
||||
err := p.WriteUsageForSubcommand(p.config.Out, subcommand...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(p.config.Out, "error:", msg)
|
||||
p.config.Exit(2)
|
||||
return nil
|
||||
// failWithCommand prints usage information for the given subcommand to stderr and exits with non-zero status
|
||||
func (p *Parser) failWithCommand(msg string, cmd *command) {
|
||||
p.writeUsageForCommand(stderr, cmd)
|
||||
fmt.Fprintln(stderr, "error:", msg)
|
||||
osExit(-1)
|
||||
}
|
||||
|
||||
// WriteUsage writes usage information to the given writer
|
||||
func (p *Parser) WriteUsage(w io.Writer) {
|
||||
p.WriteUsageForSubcommand(w, p.subcommand...)
|
||||
p.writeUsageForCommand(w, p.cmd)
|
||||
}
|
||||
|
||||
// WriteUsageForSubcommand writes the usage information for a specified
|
||||
// subcommand. To write usage information for a top-level subcommand, provide
|
||||
// just the name of that subcommand. To write usage information for a subcommand
|
||||
// that is nested under another subcommand, provide a sequence of subcommand
|
||||
// names starting with the top-level subcommand and so on down the tree.
|
||||
func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) error {
|
||||
cmd, err := p.lookupCommand(subcommand...)
|
||||
if err != nil {
|
||||
return err
|
||||
// writeUsageForCommand writes usage information for the given subcommand
|
||||
func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) {
|
||||
var positionals, options []*spec
|
||||
for _, spec := range cmd.specs {
|
||||
if spec.positional {
|
||||
positionals = append(positionals, spec)
|
||||
} else {
|
||||
options = append(options, spec)
|
||||
}
|
||||
}
|
||||
|
||||
var positionals, longOptions, shortOptions []*spec
|
||||
for _, spec := range cmd.specs {
|
||||
switch {
|
||||
case spec.positional:
|
||||
positionals = append(positionals, spec)
|
||||
case spec.long != "":
|
||||
longOptions = append(longOptions, spec)
|
||||
case spec.short != "":
|
||||
shortOptions = append(shortOptions, spec)
|
||||
if p.version != "" {
|
||||
fmt.Fprintln(w, p.version)
|
||||
}
|
||||
|
||||
// make a list of ancestor commands so that we print with full context
|
||||
var ancestors []string
|
||||
ancestor := cmd
|
||||
for ancestor != nil {
|
||||
ancestors = append(ancestors, ancestor.name)
|
||||
ancestor = ancestor.parent
|
||||
}
|
||||
|
||||
// print the beginning of the usage string
|
||||
fmt.Fprintf(w, "Usage: %s", p.cmd.name)
|
||||
for _, s := range subcommand {
|
||||
fmt.Fprint(w, " "+s)
|
||||
fmt.Fprint(w, "Usage:")
|
||||
for i := len(ancestors) - 1; i >= 0; i-- {
|
||||
fmt.Fprint(w, " "+ancestors[i])
|
||||
}
|
||||
|
||||
// write the option component of the usage message
|
||||
for _, spec := range shortOptions {
|
||||
// prefix with a space
|
||||
fmt.Fprint(w, " ")
|
||||
if !spec.required {
|
||||
fmt.Fprint(w, "[")
|
||||
}
|
||||
fmt.Fprint(w, synopsis(spec, "-"+spec.short))
|
||||
if !spec.required {
|
||||
fmt.Fprint(w, "]")
|
||||
}
|
||||
}
|
||||
|
||||
for _, spec := range longOptions {
|
||||
for _, spec := range options {
|
||||
// prefix with a space
|
||||
fmt.Fprint(w, " ")
|
||||
if !spec.required {
|
||||
|
@ -90,254 +74,139 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro
|
|||
}
|
||||
}
|
||||
|
||||
// When we parse positionals, we check that:
|
||||
// 1. required positionals come before non-required positionals
|
||||
// 2. there is at most one multiple-value positional
|
||||
// 3. if there is a multiple-value positional then it comes after all other positionals
|
||||
// Here we merely print the usage string, so we do not explicitly re-enforce those rules
|
||||
|
||||
// write the positionals in following form:
|
||||
// REQUIRED1 REQUIRED2
|
||||
// REQUIRED1 REQUIRED2 [OPTIONAL1 [OPTIONAL2]]
|
||||
// REQUIRED1 REQUIRED2 REPEATED [REPEATED ...]
|
||||
// REQUIRED1 REQUIRED2 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]
|
||||
// REQUIRED1 REQUIRED2 [OPTIONAL1 [REPEATEDOPTIONAL [REPEATEDOPTIONAL ...]]]
|
||||
var closeBrackets int
|
||||
// write the positional component of the usage message
|
||||
for _, spec := range positionals {
|
||||
// prefix with a space
|
||||
fmt.Fprint(w, " ")
|
||||
up := strings.ToUpper(spec.long)
|
||||
if spec.multiple {
|
||||
if !spec.required {
|
||||
fmt.Fprint(w, "[")
|
||||
closeBrackets += 1
|
||||
}
|
||||
if spec.cardinality == multiple {
|
||||
fmt.Fprintf(w, "%s [%s ...]", spec.placeholder, spec.placeholder)
|
||||
fmt.Fprintf(w, "%s [%s ...]", up, up)
|
||||
if !spec.required {
|
||||
fmt.Fprint(w, "]")
|
||||
}
|
||||
} else {
|
||||
fmt.Fprint(w, spec.placeholder)
|
||||
fmt.Fprint(w, up)
|
||||
}
|
||||
}
|
||||
fmt.Fprint(w, strings.Repeat("]", closeBrackets))
|
||||
|
||||
// if the program supports subcommands, give a hint to the user about their existence
|
||||
if len(cmd.subcommands) > 0 {
|
||||
fmt.Fprint(w, " <command> [<args>]")
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
// print prints a line like this:
|
||||
//
|
||||
// --option FOO A description of the option [default: 123]
|
||||
//
|
||||
// If the text on the left is longer than a certain threshold, the description is moved to the next line:
|
||||
//
|
||||
// --verylongoptionoption VERY_LONG_VARIABLE
|
||||
// A description of the option [default: 123]
|
||||
//
|
||||
// If multiple "extras" are provided then they are put inside a single set of square brackets:
|
||||
//
|
||||
// --option FOO A description of the option [default: 123, env: FOO]
|
||||
func print(w io.Writer, item, description string, bracketed ...string) {
|
||||
lhs := " " + item
|
||||
func printTwoCols(w io.Writer, left, help string, defaultVal *string) {
|
||||
lhs := " " + left
|
||||
fmt.Fprint(w, lhs)
|
||||
if description != "" {
|
||||
if help != "" {
|
||||
if len(lhs)+2 < colWidth {
|
||||
fmt.Fprint(w, strings.Repeat(" ", colWidth-len(lhs)))
|
||||
} else {
|
||||
fmt.Fprint(w, "\n"+strings.Repeat(" ", colWidth))
|
||||
}
|
||||
fmt.Fprint(w, description)
|
||||
fmt.Fprint(w, help)
|
||||
}
|
||||
|
||||
var brack string
|
||||
for _, s := range bracketed {
|
||||
if s != "" {
|
||||
if brack != "" {
|
||||
brack += ", "
|
||||
}
|
||||
brack += s
|
||||
}
|
||||
}
|
||||
|
||||
if brack != "" {
|
||||
fmt.Fprintf(w, " [%s]", brack)
|
||||
if defaultVal != nil {
|
||||
fmt.Fprintf(w, " [default: %s]", *defaultVal)
|
||||
}
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
|
||||
func withDefault(s string) string {
|
||||
if s == "" {
|
||||
return ""
|
||||
}
|
||||
return "default: " + s
|
||||
}
|
||||
|
||||
func withEnv(env string) string {
|
||||
if env == "" {
|
||||
return ""
|
||||
}
|
||||
return "env: " + env
|
||||
}
|
||||
|
||||
// WriteHelp writes the usage string followed by the full help string for each option
|
||||
func (p *Parser) WriteHelp(w io.Writer) {
|
||||
p.WriteHelpForSubcommand(w, p.subcommand...)
|
||||
p.writeHelpForCommand(w, p.cmd)
|
||||
}
|
||||
|
||||
// WriteHelpForSubcommand writes the usage string followed by the full help
|
||||
// string for a specified subcommand. To write help for a top-level subcommand,
|
||||
// provide just the name of that subcommand. To write help for a subcommand that
|
||||
// is nested under another subcommand, provide a sequence of subcommand names
|
||||
// starting with the top-level subcommand and so on down the tree.
|
||||
func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error {
|
||||
cmd, err := p.lookupCommand(subcommand...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var positionals, longOptions, shortOptions, envOnlyOptions []*spec
|
||||
var hasVersionOption bool
|
||||
// writeHelp writes the usage string for the given subcommand
|
||||
func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
||||
var positionals, options []*spec
|
||||
for _, spec := range cmd.specs {
|
||||
switch {
|
||||
case spec.positional:
|
||||
if spec.positional {
|
||||
positionals = append(positionals, spec)
|
||||
case spec.long != "":
|
||||
longOptions = append(longOptions, spec)
|
||||
if spec.long == "version" {
|
||||
hasVersionOption = true
|
||||
} else {
|
||||
options = append(options, spec)
|
||||
}
|
||||
case spec.short != "":
|
||||
shortOptions = append(shortOptions, spec)
|
||||
case spec.short == "" && spec.long == "":
|
||||
envOnlyOptions = append(envOnlyOptions, spec)
|
||||
}
|
||||
}
|
||||
|
||||
// obtain a flattened list of options from all ancestors
|
||||
// also determine if any ancestor has a version option spec
|
||||
var globals []*spec
|
||||
ancestor := cmd.parent
|
||||
for ancestor != nil {
|
||||
for _, spec := range ancestor.specs {
|
||||
if spec.long == "version" {
|
||||
hasVersionOption = true
|
||||
break
|
||||
}
|
||||
}
|
||||
globals = append(globals, ancestor.specs...)
|
||||
ancestor = ancestor.parent
|
||||
}
|
||||
|
||||
if p.description != "" {
|
||||
fmt.Fprintln(w, p.description)
|
||||
}
|
||||
|
||||
if !hasVersionOption && p.version != "" {
|
||||
fmt.Fprintln(w, p.version)
|
||||
}
|
||||
|
||||
p.WriteUsageForSubcommand(w, subcommand...)
|
||||
p.writeUsageForCommand(w, cmd)
|
||||
|
||||
// write the list of positionals
|
||||
if len(positionals) > 0 {
|
||||
fmt.Fprint(w, "\nPositional arguments:\n")
|
||||
for _, spec := range positionals {
|
||||
print(w, spec.placeholder, spec.help, withDefault(spec.defaultString), withEnv(spec.env))
|
||||
printTwoCols(w, strings.ToUpper(spec.long), spec.help, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// write the list of options with the short-only ones first to match the usage string
|
||||
if len(shortOptions)+len(longOptions) > 0 || cmd.parent == nil {
|
||||
// write the list of options
|
||||
fmt.Fprint(w, "\nOptions:\n")
|
||||
for _, spec := range shortOptions {
|
||||
for _, spec := range options {
|
||||
p.printOption(w, spec)
|
||||
}
|
||||
for _, spec := range longOptions {
|
||||
p.printOption(w, spec)
|
||||
}
|
||||
}
|
||||
|
||||
// write the list of global options
|
||||
if len(globals) > 0 {
|
||||
fmt.Fprint(w, "\nGlobal options:\n")
|
||||
for _, spec := range globals {
|
||||
p.printOption(w, spec)
|
||||
}
|
||||
}
|
||||
|
||||
// write the list of built in options
|
||||
p.printOption(w, &spec{
|
||||
cardinality: zero,
|
||||
boolean: true,
|
||||
long: "help",
|
||||
short: "h",
|
||||
help: "display this help and exit",
|
||||
})
|
||||
if !hasVersionOption && p.version != "" {
|
||||
if p.version != "" {
|
||||
p.printOption(w, &spec{
|
||||
cardinality: zero,
|
||||
boolean: true,
|
||||
long: "version",
|
||||
help: "display version and exit",
|
||||
})
|
||||
}
|
||||
|
||||
// write the list of environment only variables
|
||||
if len(envOnlyOptions) > 0 {
|
||||
fmt.Fprint(w, "\nEnvironment variables:\n")
|
||||
for _, spec := range envOnlyOptions {
|
||||
p.printEnvOnlyVar(w, spec)
|
||||
}
|
||||
}
|
||||
|
||||
// write the list of subcommands
|
||||
if len(cmd.subcommands) > 0 {
|
||||
fmt.Fprint(w, "\nCommands:\n")
|
||||
for _, subcmd := range cmd.subcommands {
|
||||
names := append([]string{subcmd.name}, subcmd.aliases...)
|
||||
print(w, strings.Join(names, ", "), subcmd.help)
|
||||
printTwoCols(w, subcmd.name, subcmd.help, nil)
|
||||
}
|
||||
}
|
||||
|
||||
if p.epilogue != "" {
|
||||
fmt.Fprintln(w, "\n"+p.epilogue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Parser) printOption(w io.Writer, spec *spec) {
|
||||
ways := make([]string, 0, 2)
|
||||
if spec.long != "" {
|
||||
ways = append(ways, synopsis(spec, "--"+spec.long))
|
||||
}
|
||||
left := synopsis(spec, "--"+spec.long)
|
||||
if spec.short != "" {
|
||||
ways = append(ways, synopsis(spec, "-"+spec.short))
|
||||
left += ", " + synopsis(spec, "-"+spec.short)
|
||||
}
|
||||
if len(ways) > 0 {
|
||||
print(w, strings.Join(ways, ", "), spec.help, withDefault(spec.defaultString), withEnv(spec.env))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Parser) printEnvOnlyVar(w io.Writer, spec *spec) {
|
||||
ways := make([]string, 0, 2)
|
||||
if spec.required {
|
||||
ways = append(ways, "Required.")
|
||||
// If spec.dest is not the zero value then a default value has been added.
|
||||
var v reflect.Value
|
||||
if len(spec.dest.fields) > 0 {
|
||||
v = p.val(spec.dest)
|
||||
}
|
||||
|
||||
var defaultVal *string
|
||||
if v.IsValid() {
|
||||
z := reflect.Zero(v.Type())
|
||||
if (v.Type().Comparable() && z.Type().Comparable() && v.Interface() != z.Interface()) || v.Kind() == reflect.Slice && !v.IsNil() {
|
||||
if scalar, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||
if value, err := scalar.MarshalText(); err != nil {
|
||||
defaultVal = ptrTo(fmt.Sprintf("error: %v", err))
|
||||
} else {
|
||||
ways = append(ways, "Optional.")
|
||||
defaultVal = ptrTo(fmt.Sprintf("%v", string(value)))
|
||||
}
|
||||
|
||||
if spec.help != "" {
|
||||
ways = append(ways, spec.help)
|
||||
} else {
|
||||
defaultVal = ptrTo(fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
print(w, spec.env, strings.Join(ways, " "), withDefault(spec.defaultString))
|
||||
}
|
||||
}
|
||||
printTwoCols(w, left, spec.help, defaultVal)
|
||||
}
|
||||
|
||||
func synopsis(spec *spec, form string) string {
|
||||
// if the user omits the placeholder tag then we pick one automatically,
|
||||
// but if the user explicitly specifies an empty placeholder then we
|
||||
// leave out the placeholder in the help message
|
||||
if spec.cardinality == zero || spec.placeholder == "" {
|
||||
if spec.boolean {
|
||||
return form
|
||||
}
|
||||
return form + " " + spec.placeholder
|
||||
return form + " " + strings.ToUpper(spec.long)
|
||||
}
|
||||
|
||||
func ptrTo(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
package arg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type NameDotName struct {
|
||||
Head, Tail string
|
||||
}
|
||||
|
||||
func (n *NameDotName) UnmarshalText(b []byte) error {
|
||||
s := string(b)
|
||||
pos := strings.Index(s, ".")
|
||||
if pos == -1 {
|
||||
return fmt.Errorf("missing period in %s", s)
|
||||
}
|
||||
n.Head = s[:pos]
|
||||
n.Tail = s[pos+1:]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *NameDotName) MarshalText() (text []byte, err error) {
|
||||
text = []byte(fmt.Sprintf("%s.%s", n.Head, n.Tail))
|
||||
return
|
||||
}
|
||||
|
||||
func TestWriteUsage(t *testing.T) {
|
||||
expectedUsage := "Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] [--file FILE] INPUT [OUTPUT [OUTPUT ...]]\n"
|
||||
|
||||
expectedHelp := `Usage: example [--name NAME] [--value VALUE] [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] [--ids IDS] [--values VALUES] [--workers WORKERS] [--file FILE] INPUT [OUTPUT [OUTPUT ...]]
|
||||
|
||||
Positional arguments:
|
||||
INPUT
|
||||
OUTPUT list of outputs
|
||||
|
||||
Options:
|
||||
--name NAME name to use [default: Foo Bar]
|
||||
--value VALUE secret value [default: 42]
|
||||
--verbose, -v verbosity level
|
||||
--dataset DATASET dataset to use
|
||||
--optimize OPTIMIZE, -O OPTIMIZE
|
||||
optimization level
|
||||
--ids IDS Ids
|
||||
--values VALUES Values [default: [3.14 42 256]]
|
||||
--workers WORKERS, -w WORKERS
|
||||
number of workers to start
|
||||
--file FILE, -f FILE File with mandatory extension [default: scratch.txt]
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
Input string `arg:"positional"`
|
||||
Output []string `arg:"positional" help:"list of outputs"`
|
||||
Name string `help:"name to use"`
|
||||
Value int `help:"secret value"`
|
||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||
Dataset string `help:"dataset to use"`
|
||||
Optimize int `arg:"-O" help:"optimization level"`
|
||||
Ids []int64 `help:"Ids"`
|
||||
Values []float64 `help:"Values"`
|
||||
Workers int `arg:"-w,env:WORKERS" help:"number of workers to start"`
|
||||
File *NameDotName `arg:"-f" help:"File with mandatory extension"`
|
||||
}
|
||||
args.Name = "Foo Bar"
|
||||
args.Value = 42
|
||||
args.Values = []float64{3.14, 42, 256}
|
||||
args.File = &NameDotName{"scratch", "txt"}
|
||||
p, err := NewParser(Config{"example"}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Args[0] = "example"
|
||||
|
||||
var usage bytes.Buffer
|
||||
p.WriteUsage(&usage)
|
||||
assert.Equal(t, expectedUsage, usage.String())
|
||||
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
||||
type MyEnum int
|
||||
|
||||
func (n *MyEnum) UnmarshalText(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *MyEnum) MarshalText() ([]byte, error) {
|
||||
return nil, errors.New("There was a problem")
|
||||
}
|
||||
|
||||
func TestUsageError(t *testing.T) {
|
||||
expectedHelp := `Usage: example [--name NAME]
|
||||
|
||||
Options:
|
||||
--name NAME [default: error: There was a problem]
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
Name *MyEnum
|
||||
}
|
||||
v := MyEnum(42)
|
||||
args.Name = &v
|
||||
p, err := NewParser(Config{"example"}, &args)
|
||||
|
||||
// NB: some might might expect there to be an error here
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
||||
func TestUsageLongPositionalWithHelp_legacyForm(t *testing.T) {
|
||||
expectedHelp := `Usage: example VERYLONGPOSITIONALWITHHELP
|
||||
|
||||
Positional arguments:
|
||||
VERYLONGPOSITIONALWITHHELP
|
||||
this positional argument is very long but cannot include commas
|
||||
|
||||
Options:
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
VeryLongPositionalWithHelp string `arg:"positional,help:this positional argument is very long but cannot include commas"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Args[0] = "example"
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
||||
func TestUsageLongPositionalWithHelp_newForm(t *testing.T) {
|
||||
expectedHelp := `Usage: example VERYLONGPOSITIONALWITHHELP
|
||||
|
||||
Positional arguments:
|
||||
VERYLONGPOSITIONALWITHHELP
|
||||
this positional argument is very long, and includes: commas, colons etc
|
||||
|
||||
Options:
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
VeryLongPositionalWithHelp string `arg:"positional" help:"this positional argument is very long, and includes: commas, colons etc"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Args[0] = "example"
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
||||
func TestUsageWithProgramName(t *testing.T) {
|
||||
expectedHelp := `Usage: myprogram
|
||||
|
||||
Options:
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
config := Config{
|
||||
Program: "myprogram",
|
||||
}
|
||||
p, err := NewParser(config, &struct{}{})
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Args[0] = "example"
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
||||
|
||||
type versioned struct{}
|
||||
|
||||
// Version returns the version for this program
|
||||
func (versioned) Version() string {
|
||||
return "example 3.2.1"
|
||||
}
|
||||
|
||||
func TestUsageWithVersion(t *testing.T) {
|
||||
expectedHelp := `example 3.2.1
|
||||
Usage: example
|
||||
|
||||
Options:
|
||||
--help, -h display this help and exit
|
||||
--version display version and exit
|
||||
`
|
||||
os.Args[0] = "example"
|
||||
p, err := NewParser(Config{}, &versioned{})
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
actual := help.String()
|
||||
t.Logf("Expected:\n%s", expectedHelp)
|
||||
t.Logf("Actual:\n%s", actual)
|
||||
if expectedHelp != actual {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
type described struct{}
|
||||
|
||||
// Described returns the description for this program
|
||||
func (described) Description() string {
|
||||
return "this program does this and that"
|
||||
}
|
||||
|
||||
func TestUsageWithDescription(t *testing.T) {
|
||||
expectedHelp := `this program does this and that
|
||||
Usage: example
|
||||
|
||||
Options:
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
os.Args[0] = "example"
|
||||
p, err := NewParser(Config{}, &described{})
|
||||
require.NoError(t, err)
|
||||
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
actual := help.String()
|
||||
t.Logf("Expected:\n%s", expectedHelp)
|
||||
t.Logf("Actual:\n%s", actual)
|
||||
if expectedHelp != actual {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredMultiplePositionals(t *testing.T) {
|
||||
expectedHelp := `Usage: example REQUIREDMULTIPLE [REQUIREDMULTIPLE ...]
|
||||
|
||||
Positional arguments:
|
||||
REQUIREDMULTIPLE required multiple positional
|
||||
|
||||
Options:
|
||||
--help, -h display this help and exit
|
||||
`
|
||||
var args struct {
|
||||
RequiredMultiple []string `arg:"positional,required" help:"required multiple positional"`
|
||||
}
|
||||
|
||||
p, err := NewParser(Config{}, &args)
|
||||
require.NoError(t, err)
|
||||
|
||||
os.Args[0] = "example"
|
||||
var help bytes.Buffer
|
||||
p.WriteHelp(&help)
|
||||
assert.Equal(t, expectedHelp, help.String())
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -0,0 +1,9 @@
|
|||
language: go
|
||||
go:
|
||||
- tip
|
||||
before_install:
|
||||
- go get github.com/axw/gocov/gocov
|
||||
- go get github.com/mattn/goveralls
|
||||
- if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2015, Alex Flint
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
[![GoDoc](https://godoc.org/github.com/alexflint/go-scalar?status.svg)](https://godoc.org/github.com/alexflint/go-scalar)
|
||||
[![Build Status](https://travis-ci.org/alexflint/go-scalar.svg?branch=master)](https://travis-ci.org/alexflint/go-scalar)
|
||||
[![Coverage Status](https://coveralls.io/repos/alexflint/go-scalar/badge.svg?branch=master&service=github)](https://coveralls.io/github/alexflint/go-scalar?branch=master)
|
||||
[![Report Card](https://goreportcard.com/badge/github.com/alexflint/go-scalar)](https://goreportcard.com/badge/github.com/alexflint/go-scalar)
|
||||
|
||||
## Scalar parsing library
|
||||
|
||||
Scalar is a library for parsing strings into arbitrary scalars (integers,
|
||||
floats, strings, booleans, etc). It is helpful for tasks such as parsing
|
||||
strings passed as environment variables or command line arguments.
|
||||
|
||||
```shell
|
||||
go get github.com/alexflint/go-scalar
|
||||
```
|
||||
|
||||
The main API works as follows:
|
||||
|
||||
```go
|
||||
var value int
|
||||
err := scalar.Parse(&value, "123")
|
||||
```
|
||||
|
||||
There is also a variant that takes a `reflect.Value`:
|
||||
|
||||
```go
|
||||
var value int
|
||||
err := scalar.ParseValue(reflect.ValueOf(&value), "123")
|
||||
```
|
|
@ -0,0 +1,7 @@
|
|||
module github.com/alexflint/go-scalar
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
)
|
|
@ -0,0 +1,6 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
@ -0,0 +1,153 @@
|
|||
// Package scalar parses strings into values of scalar type.
|
||||
|
||||
package scalar
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/mail"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The reflected form of some special types
|
||||
var (
|
||||
textUnmarshalerType = reflect.TypeOf([]encoding.TextUnmarshaler{}).Elem()
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
mailAddressType = reflect.TypeOf(mail.Address{})
|
||||
macType = reflect.TypeOf(net.HardwareAddr{})
|
||||
)
|
||||
|
||||
var (
|
||||
errNotSettable = errors.New("value is not settable")
|
||||
errPtrNotSettable = errors.New("value is a nil pointer and is not settable")
|
||||
)
|
||||
|
||||
// Parse assigns a value to v by parsing s.
|
||||
func Parse(dest interface{}, s string) error {
|
||||
return ParseValue(reflect.ValueOf(dest), s)
|
||||
}
|
||||
|
||||
// ParseValue assigns a value to v by parsing s.
|
||||
func ParseValue(v reflect.Value, s string) error {
|
||||
// If we have a nil pointer then allocate a new object
|
||||
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||
if !v.CanSet() {
|
||||
return errPtrNotSettable
|
||||
}
|
||||
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
|
||||
// If it implements encoding.TextUnmarshaler then use that
|
||||
if scalar, ok := v.Interface().(encoding.TextUnmarshaler); ok {
|
||||
return scalar.UnmarshalText([]byte(s))
|
||||
}
|
||||
// If it's a value instead of a pointer, check that we can unmarshal it
|
||||
// via TextUnmarshaler as well
|
||||
if v.CanAddr() {
|
||||
if scalar, ok := v.Addr().Interface().(encoding.TextUnmarshaler); ok {
|
||||
return scalar.UnmarshalText([]byte(s))
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a pointer then dereference it
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if !v.CanSet() {
|
||||
return errNotSettable
|
||||
}
|
||||
|
||||
// Switch on concrete type
|
||||
switch scalar := v.Interface(); scalar.(type) {
|
||||
case time.Duration:
|
||||
duration, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(reflect.ValueOf(duration))
|
||||
return nil
|
||||
case mail.Address:
|
||||
addr, err := mail.ParseAddress(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(reflect.ValueOf(*addr))
|
||||
return nil
|
||||
case net.HardwareAddr:
|
||||
ip, err := net.ParseMAC(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Set(reflect.ValueOf(ip))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Switch on kind so that we can handle derived types
|
||||
switch v.Kind() {
|
||||
case reflect.String:
|
||||
v.SetString(s)
|
||||
case reflect.Bool:
|
||||
x, err := strconv.ParseBool(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetBool(x)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x, err := strconv.ParseInt(s, 10, v.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetInt(x)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x, err := strconv.ParseUint(s, 10, v.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetUint(x)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
x, err := strconv.ParseFloat(s, v.Type().Bits())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.SetFloat(x)
|
||||
default:
|
||||
return fmt.Errorf("cannot parse into %v", v.Type())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanParse returns true if the type can be parsed from a string.
|
||||
func CanParse(t reflect.Type) bool {
|
||||
// If it implements encoding.TextUnmarshaler then use that
|
||||
if t.Implements(textUnmarshalerType) || reflect.PtrTo(t).Implements(textUnmarshalerType) {
|
||||
return true
|
||||
}
|
||||
|
||||
// If we have a pointer then dereference it
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
// Check for other special types
|
||||
switch t {
|
||||
case durationType, mailAddressType, macType:
|
||||
return true
|
||||
}
|
||||
|
||||
// Fall back to checking the kind
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
||||
reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
type flag uintptr
|
||||
|
||||
var (
|
||||
// flagRO indicates whether the value field of a reflect.Value
|
||||
// is read-only.
|
||||
flagRO flag
|
||||
|
||||
// flagAddr indicates whether the address of the reflect.Value's
|
||||
// value may be taken.
|
||||
flagAddr flag
|
||||
)
|
||||
|
||||
// flagKindMask holds the bits that make up the kind
|
||||
// part of the flags field. In all the supported versions,
|
||||
// it is in the lower 5 bits.
|
||||
const flagKindMask = flag(0x1f)
|
||||
|
||||
// Different versions of Go have used different
|
||||
// bit layouts for the flags type. This table
|
||||
// records the known combinations.
|
||||
var okFlags = []struct {
|
||||
ro, addr flag
|
||||
}{{
|
||||
// From Go 1.4 to 1.5
|
||||
ro: 1 << 5,
|
||||
addr: 1 << 7,
|
||||
}, {
|
||||
// Up to Go tip.
|
||||
ro: 1<<5 | 1<<6,
|
||||
addr: 1 << 8,
|
||||
}}
|
||||
|
||||
var flagValOffset = func() uintptr {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
return field.Offset
|
||||
}()
|
||||
|
||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||
func flagField(v *reflect.Value) *flag {
|
||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||
return v
|
||||
}
|
||||
flagFieldPtr := flagField(&v)
|
||||
*flagFieldPtr &^= flagRO
|
||||
*flagFieldPtr |= flagAddr
|
||||
return v
|
||||
}
|
||||
|
||||
// Sanity checks against future reflect package changes
|
||||
// to the type or semantics of the Value.flag field.
|
||||
func init() {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||
panic("reflect.Value flag field has changed kind")
|
||||
}
|
||||
type t0 int
|
||||
var t struct {
|
||||
A t0
|
||||
// t0 will have flagEmbedRO set.
|
||||
t0
|
||||
// a will have flagStickyRO set
|
||||
a t0
|
||||
}
|
||||
vA := reflect.ValueOf(t).FieldByName("A")
|
||||
va := reflect.ValueOf(t).FieldByName("a")
|
||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||
|
||||
// Infer flagRO from the difference between the flags
|
||||
// for the (otherwise identical) fields in t.
|
||||
flagPublic := *flagField(&vA)
|
||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||
flagRO = flagPublic ^ flagWithRO
|
||||
|
||||
// Infer flagAddr from the difference between a value
|
||||
// taken from a pointer and not.
|
||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||
flagNoPtr := *flagField(&vA)
|
||||
flagPtr := *flagField(&vPtrA)
|
||||
flagAddr = flagNoPtr ^ flagPtr
|
||||
|
||||
// Check that the inferred flags tally with one of the known versions.
|
||||
for _, f := range okFlags {
|
||||
if flagRO == f.ro && flagAddr == f.addr {
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("reflect.Value read-only flag has changed semantics")
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe !go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||
const supportedFlags = "0-+# "
|
||||
|
||||
// formatState implements the fmt.Formatter interface and contains information
|
||||
// about the state of a formatting operation. The NewFormatter function can
|
||||
// be used to get a new Formatter which can be used directly as arguments
|
||||
// in standard fmt package printing calls.
|
||||
type formatState struct {
|
||||
value interface{}
|
||||
fs fmt.State
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// buildDefaultFormat recreates the original format string without precision
|
||||
// and width information to pass in to fmt.Sprintf in the case of an
|
||||
// unrecognized type. Unless new types are added to the language, this
|
||||
// function won't ever be called.
|
||||
func (f *formatState) buildDefaultFormat() (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteRune('v')
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// constructOrigFormat recreates the original format string including precision
|
||||
// and width information to pass along to the standard fmt package. This allows
|
||||
// automatic deferral of all format strings this package doesn't support.
|
||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if width, ok := f.fs.Width(); ok {
|
||||
buf.WriteString(strconv.Itoa(width))
|
||||
}
|
||||
|
||||
if precision, ok := f.fs.Precision(); ok {
|
||||
buf.Write(precisionBytes)
|
||||
buf.WriteString(strconv.Itoa(precision))
|
||||
}
|
||||
|
||||
buf.WriteRune(verb)
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||
// ensures that types for values which have been unpacked from an interface
|
||||
// are displayed when the show types flag is also set.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface {
|
||||
f.ignoreNextType = false
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (f *formatState) formatPtr(v reflect.Value) {
|
||||
// Display nil if top level pointer is nil.
|
||||
showTypes := f.fs.Flag('#')
|
||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range f.pointers {
|
||||
if depth >= f.depth {
|
||||
delete(f.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to possibly show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by derferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
f.pointers[addr] = f.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type or indirection level depending on flags.
|
||||
if showTypes && !f.ignoreNextType {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
f.fs.Write([]byte(ve.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
} else {
|
||||
if nilFound || cycleFound {
|
||||
indirects += strings.Count(ve.Type().String(), "*")
|
||||
}
|
||||
f.fs.Write(openAngleBytes)
|
||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||
f.fs.Write(closeAngleBytes)
|
||||
}
|
||||
|
||||
// Display pointer information depending on flags.
|
||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||
f.fs.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
f.fs.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(f.fs, addr)
|
||||
}
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
f.ignoreNextType = true
|
||||
f.format(ve)
|
||||
}
|
||||
}
|
||||
|
||||
// format is the main workhorse for providing the Formatter interface. It
|
||||
// uses the passed reflect value to figure out what kind of object we are
|
||||
// dealing with and formats it appropriately. It is a recursive function,
|
||||
// however circular data structures are detected and handled properly.
|
||||
func (f *formatState) format(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
f.fs.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
f.formatPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write([]byte(v.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
f.ignoreNextType = false
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods
|
||||
// flag is enabled.
|
||||
if !f.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(f.fs, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(f.fs, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(f.fs, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(f.fs, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(f.fs, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(f.fs, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(f.fs, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f.fs.Write(openBracketBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.Index(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBracketBytes)
|
||||
|
||||
case reflect.String:
|
||||
f.fs.Write([]byte(v.String()))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
f.fs.Write(openMapBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
keys := v.MapKeys()
|
||||
if f.cs.SortKeys {
|
||||
sortValues(keys, f.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(key))
|
||||
f.fs.Write(colonBytes)
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.MapIndex(key)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeMapBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
numFields := v.NumField()
|
||||
f.fs.Write(openBraceBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
vtf := vt.Field(i)
|
||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||
f.fs.Write([]byte(vtf.Name))
|
||||
f.fs.Write(colonBytes)
|
||||
}
|
||||
f.format(f.unpackValue(v.Field(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(f.fs, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it if any get added.
|
||||
default:
|
||||
format := f.buildDefaultFormat()
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(f.fs, format, v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(f.fs, format, v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||
// details.
|
||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||
f.fs = fs
|
||||
|
||||
// Use standard formatting for verbs that are not v.
|
||||
if verb != 'v' {
|
||||
format := f.constructOrigFormat(verb)
|
||||
fmt.Fprintf(fs, format, f.value)
|
||||
return
|
||||
}
|
||||
|
||||
if f.value == nil {
|
||||
if fs.Flag('#') {
|
||||
fs.Write(interfaceBytes)
|
||||
}
|
||||
fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
f.format(reflect.ValueOf(f.value))
|
||||
}
|
||||
|
||||
// newFormatter is a helper function to consolidate the logic from the various
|
||||
// public methods which take varying config states.
|
||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||
fs := &formatState{value: v, cs: cs}
|
||||
fs.pointers = make(map[uintptr]int)
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
Printf, Println, or Fprintf.
|
||||
*/
|
||||
func NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(&Config, v)
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the formatted string as a value that satisfies error. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a default Formatter interface returned by NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a default spew Formatter interface.
|
||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = NewFormatter(arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2013, Patrick Mezard
|
||||
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.
|
||||
The names of its contributors may not 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
|
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,772 @@
|
|||
// Package difflib is a partial port of Python difflib module.
|
||||
//
|
||||
// It provides tools to compare sequences of strings and generate textual diffs.
|
||||
//
|
||||
// The following class and functions have been ported:
|
||||
//
|
||||
// - SequenceMatcher
|
||||
//
|
||||
// - unified_diff
|
||||
//
|
||||
// - context_diff
|
||||
//
|
||||
// Getting unified diffs was the main goal of the port. Keep in mind this code
|
||||
// is mostly suitable to output text differences in a human friendly way, there
|
||||
// are no guarantees generated diffs are consumable by patch(1).
|
||||
package difflib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func calculateRatio(matches, length int) float64 {
|
||||
if length > 0 {
|
||||
return 2.0 * float64(matches) / float64(length)
|
||||
}
|
||||
return 1.0
|
||||
}
|
||||
|
||||
type Match struct {
|
||||
A int
|
||||
B int
|
||||
Size int
|
||||
}
|
||||
|
||||
type OpCode struct {
|
||||
Tag byte
|
||||
I1 int
|
||||
I2 int
|
||||
J1 int
|
||||
J2 int
|
||||
}
|
||||
|
||||
// SequenceMatcher compares sequence of strings. The basic
|
||||
// algorithm predates, and is a little fancier than, an algorithm
|
||||
// published in the late 1980's by Ratcliff and Obershelp under the
|
||||
// hyperbolic name "gestalt pattern matching". The basic idea is to find
|
||||
// the longest contiguous matching subsequence that contains no "junk"
|
||||
// elements (R-O doesn't address junk). The same idea is then applied
|
||||
// recursively to the pieces of the sequences to the left and to the right
|
||||
// of the matching subsequence. This does not yield minimal edit
|
||||
// sequences, but does tend to yield matches that "look right" to people.
|
||||
//
|
||||
// SequenceMatcher tries to compute a "human-friendly diff" between two
|
||||
// sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the
|
||||
// longest *contiguous* & junk-free matching subsequence. That's what
|
||||
// catches peoples' eyes. The Windows(tm) windiff has another interesting
|
||||
// notion, pairing up elements that appear uniquely in each sequence.
|
||||
// That, and the method here, appear to yield more intuitive difference
|
||||
// reports than does diff. This method appears to be the least vulnerable
|
||||
// to synching up on blocks of "junk lines", though (like blank lines in
|
||||
// ordinary text files, or maybe "<P>" lines in HTML files). That may be
|
||||
// because this is the only method of the 3 that has a *concept* of
|
||||
// "junk" <wink>.
|
||||
//
|
||||
// Timing: Basic R-O is cubic time worst case and quadratic time expected
|
||||
// case. SequenceMatcher is quadratic time for the worst case and has
|
||||
// expected-case behavior dependent in a complicated way on how many
|
||||
// elements the sequences have in common; best case time is linear.
|
||||
type SequenceMatcher struct {
|
||||
a []string
|
||||
b []string
|
||||
b2j map[string][]int
|
||||
IsJunk func(string) bool
|
||||
autoJunk bool
|
||||
bJunk map[string]struct{}
|
||||
matchingBlocks []Match
|
||||
fullBCount map[string]int
|
||||
bPopular map[string]struct{}
|
||||
opCodes []OpCode
|
||||
}
|
||||
|
||||
func NewMatcher(a, b []string) *SequenceMatcher {
|
||||
m := SequenceMatcher{autoJunk: true}
|
||||
m.SetSeqs(a, b)
|
||||
return &m
|
||||
}
|
||||
|
||||
func NewMatcherWithJunk(a, b []string, autoJunk bool,
|
||||
isJunk func(string) bool) *SequenceMatcher {
|
||||
|
||||
m := SequenceMatcher{IsJunk: isJunk, autoJunk: autoJunk}
|
||||
m.SetSeqs(a, b)
|
||||
return &m
|
||||
}
|
||||
|
||||
// Set two sequences to be compared.
|
||||
func (m *SequenceMatcher) SetSeqs(a, b []string) {
|
||||
m.SetSeq1(a)
|
||||
m.SetSeq2(b)
|
||||
}
|
||||
|
||||
// Set the first sequence to be compared. The second sequence to be compared is
|
||||
// not changed.
|
||||
//
|
||||
// SequenceMatcher computes and caches detailed information about the second
|
||||
// sequence, so if you want to compare one sequence S against many sequences,
|
||||
// use .SetSeq2(s) once and call .SetSeq1(x) repeatedly for each of the other
|
||||
// sequences.
|
||||
//
|
||||
// See also SetSeqs() and SetSeq2().
|
||||
func (m *SequenceMatcher) SetSeq1(a []string) {
|
||||
if &a == &m.a {
|
||||
return
|
||||
}
|
||||
m.a = a
|
||||
m.matchingBlocks = nil
|
||||
m.opCodes = nil
|
||||
}
|
||||
|
||||
// Set the second sequence to be compared. The first sequence to be compared is
|
||||
// not changed.
|
||||
func (m *SequenceMatcher) SetSeq2(b []string) {
|
||||
if &b == &m.b {
|
||||
return
|
||||
}
|
||||
m.b = b
|
||||
m.matchingBlocks = nil
|
||||
m.opCodes = nil
|
||||
m.fullBCount = nil
|
||||
m.chainB()
|
||||
}
|
||||
|
||||
func (m *SequenceMatcher) chainB() {
|
||||
// Populate line -> index mapping
|
||||
b2j := map[string][]int{}
|
||||
for i, s := range m.b {
|
||||
indices := b2j[s]
|
||||
indices = append(indices, i)
|
||||
b2j[s] = indices
|
||||
}
|
||||
|
||||
// Purge junk elements
|
||||
m.bJunk = map[string]struct{}{}
|
||||
if m.IsJunk != nil {
|
||||
junk := m.bJunk
|
||||
for s, _ := range b2j {
|
||||
if m.IsJunk(s) {
|
||||
junk[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s, _ := range junk {
|
||||
delete(b2j, s)
|
||||
}
|
||||
}
|
||||
|
||||
// Purge remaining popular elements
|
||||
popular := map[string]struct{}{}
|
||||
n := len(m.b)
|
||||
if m.autoJunk && n >= 200 {
|
||||
ntest := n/100 + 1
|
||||
for s, indices := range b2j {
|
||||
if len(indices) > ntest {
|
||||
popular[s] = struct{}{}
|
||||
}
|
||||
}
|
||||
for s, _ := range popular {
|
||||
delete(b2j, s)
|
||||
}
|
||||
}
|
||||
m.bPopular = popular
|
||||
m.b2j = b2j
|
||||
}
|
||||
|
||||
func (m *SequenceMatcher) isBJunk(s string) bool {
|
||||
_, ok := m.bJunk[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Find longest matching block in a[alo:ahi] and b[blo:bhi].
|
||||
//
|
||||
// If IsJunk is not defined:
|
||||
//
|
||||
// Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where
|
||||
// alo <= i <= i+k <= ahi
|
||||
// blo <= j <= j+k <= bhi
|
||||
// and for all (i',j',k') meeting those conditions,
|
||||
// k >= k'
|
||||
// i <= i'
|
||||
// and if i == i', j <= j'
|
||||
//
|
||||
// In other words, of all maximal matching blocks, return one that
|
||||
// starts earliest in a, and of all those maximal matching blocks that
|
||||
// start earliest in a, return the one that starts earliest in b.
|
||||
//
|
||||
// If IsJunk is defined, first the longest matching block is
|
||||
// determined as above, but with the additional restriction that no
|
||||
// junk element appears in the block. Then that block is extended as
|
||||
// far as possible by matching (only) junk elements on both sides. So
|
||||
// the resulting block never matches on junk except as identical junk
|
||||
// happens to be adjacent to an "interesting" match.
|
||||
//
|
||||
// If no blocks match, return (alo, blo, 0).
|
||||
func (m *SequenceMatcher) findLongestMatch(alo, ahi, blo, bhi int) Match {
|
||||
// CAUTION: stripping common prefix or suffix would be incorrect.
|
||||
// E.g.,
|
||||
// ab
|
||||
// acab
|
||||
// Longest matching block is "ab", but if common prefix is
|
||||
// stripped, it's "a" (tied with "b"). UNIX(tm) diff does so
|
||||
// strip, so ends up claiming that ab is changed to acab by
|
||||
// inserting "ca" in the middle. That's minimal but unintuitive:
|
||||
// "it's obvious" that someone inserted "ac" at the front.
|
||||
// Windiff ends up at the same place as diff, but by pairing up
|
||||
// the unique 'b's and then matching the first two 'a's.
|
||||
besti, bestj, bestsize := alo, blo, 0
|
||||
|
||||
// find longest junk-free match
|
||||
// during an iteration of the loop, j2len[j] = length of longest
|
||||
// junk-free match ending with a[i-1] and b[j]
|
||||
j2len := map[int]int{}
|
||||
for i := alo; i != ahi; i++ {
|
||||
// look at all instances of a[i] in b; note that because
|
||||
// b2j has no junk keys, the loop is skipped if a[i] is junk
|
||||
newj2len := map[int]int{}
|
||||
for _, j := range m.b2j[m.a[i]] {
|
||||
// a[i] matches b[j]
|
||||
if j < blo {
|
||||
continue
|
||||
}
|
||||
if j >= bhi {
|
||||
break
|
||||
}
|
||||
k := j2len[j-1] + 1
|
||||
newj2len[j] = k
|
||||
if k > bestsize {
|
||||
besti, bestj, bestsize = i-k+1, j-k+1, k
|
||||
}
|
||||
}
|
||||
j2len = newj2len
|
||||
}
|
||||
|
||||
// Extend the best by non-junk elements on each end. In particular,
|
||||
// "popular" non-junk elements aren't in b2j, which greatly speeds
|
||||
// the inner loop above, but also means "the best" match so far
|
||||
// doesn't contain any junk *or* popular non-junk elements.
|
||||
for besti > alo && bestj > blo && !m.isBJunk(m.b[bestj-1]) &&
|
||||
m.a[besti-1] == m.b[bestj-1] {
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
}
|
||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||
!m.isBJunk(m.b[bestj+bestsize]) &&
|
||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||
bestsize += 1
|
||||
}
|
||||
|
||||
// Now that we have a wholly interesting match (albeit possibly
|
||||
// empty!), we may as well suck up the matching junk on each
|
||||
// side of it too. Can't think of a good reason not to, and it
|
||||
// saves post-processing the (possibly considerable) expense of
|
||||
// figuring out what to do with it. In the case of an empty
|
||||
// interesting match, this is clearly the right thing to do,
|
||||
// because no other kind of match is possible in the regions.
|
||||
for besti > alo && bestj > blo && m.isBJunk(m.b[bestj-1]) &&
|
||||
m.a[besti-1] == m.b[bestj-1] {
|
||||
besti, bestj, bestsize = besti-1, bestj-1, bestsize+1
|
||||
}
|
||||
for besti+bestsize < ahi && bestj+bestsize < bhi &&
|
||||
m.isBJunk(m.b[bestj+bestsize]) &&
|
||||
m.a[besti+bestsize] == m.b[bestj+bestsize] {
|
||||
bestsize += 1
|
||||
}
|
||||
|
||||
return Match{A: besti, B: bestj, Size: bestsize}
|
||||
}
|
||||
|
||||
// Return list of triples describing matching subsequences.
|
||||
//
|
||||
// Each triple is of the form (i, j, n), and means that
|
||||
// a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in
|
||||
// i and in j. It's also guaranteed that if (i, j, n) and (i', j', n') are
|
||||
// adjacent triples in the list, and the second is not the last triple in the
|
||||
// list, then i+n != i' or j+n != j'. IOW, adjacent triples never describe
|
||||
// adjacent equal blocks.
|
||||
//
|
||||
// The last triple is a dummy, (len(a), len(b), 0), and is the only
|
||||
// triple with n==0.
|
||||
func (m *SequenceMatcher) GetMatchingBlocks() []Match {
|
||||
if m.matchingBlocks != nil {
|
||||
return m.matchingBlocks
|
||||
}
|
||||
|
||||
var matchBlocks func(alo, ahi, blo, bhi int, matched []Match) []Match
|
||||
matchBlocks = func(alo, ahi, blo, bhi int, matched []Match) []Match {
|
||||
match := m.findLongestMatch(alo, ahi, blo, bhi)
|
||||
i, j, k := match.A, match.B, match.Size
|
||||
if match.Size > 0 {
|
||||
if alo < i && blo < j {
|
||||
matched = matchBlocks(alo, i, blo, j, matched)
|
||||
}
|
||||
matched = append(matched, match)
|
||||
if i+k < ahi && j+k < bhi {
|
||||
matched = matchBlocks(i+k, ahi, j+k, bhi, matched)
|
||||
}
|
||||
}
|
||||
return matched
|
||||
}
|
||||
matched := matchBlocks(0, len(m.a), 0, len(m.b), nil)
|
||||
|
||||
// It's possible that we have adjacent equal blocks in the
|
||||
// matching_blocks list now.
|
||||
nonAdjacent := []Match{}
|
||||
i1, j1, k1 := 0, 0, 0
|
||||
for _, b := range matched {
|
||||
// Is this block adjacent to i1, j1, k1?
|
||||
i2, j2, k2 := b.A, b.B, b.Size
|
||||
if i1+k1 == i2 && j1+k1 == j2 {
|
||||
// Yes, so collapse them -- this just increases the length of
|
||||
// the first block by the length of the second, and the first
|
||||
// block so lengthened remains the block to compare against.
|
||||
k1 += k2
|
||||
} else {
|
||||
// Not adjacent. Remember the first block (k1==0 means it's
|
||||
// the dummy we started with), and make the second block the
|
||||
// new block to compare against.
|
||||
if k1 > 0 {
|
||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||
}
|
||||
i1, j1, k1 = i2, j2, k2
|
||||
}
|
||||
}
|
||||
if k1 > 0 {
|
||||
nonAdjacent = append(nonAdjacent, Match{i1, j1, k1})
|
||||
}
|
||||
|
||||
nonAdjacent = append(nonAdjacent, Match{len(m.a), len(m.b), 0})
|
||||
m.matchingBlocks = nonAdjacent
|
||||
return m.matchingBlocks
|
||||
}
|
||||
|
||||
// Return list of 5-tuples describing how to turn a into b.
|
||||
//
|
||||
// Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple
|
||||
// has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the
|
||||
// tuple preceding it, and likewise for j1 == the previous j2.
|
||||
//
|
||||
// The tags are characters, with these meanings:
|
||||
//
|
||||
// 'r' (replace): a[i1:i2] should be replaced by b[j1:j2]
|
||||
//
|
||||
// 'd' (delete): a[i1:i2] should be deleted, j1==j2 in this case.
|
||||
//
|
||||
// 'i' (insert): b[j1:j2] should be inserted at a[i1:i1], i1==i2 in this case.
|
||||
//
|
||||
// 'e' (equal): a[i1:i2] == b[j1:j2]
|
||||
func (m *SequenceMatcher) GetOpCodes() []OpCode {
|
||||
if m.opCodes != nil {
|
||||
return m.opCodes
|
||||
}
|
||||
i, j := 0, 0
|
||||
matching := m.GetMatchingBlocks()
|
||||
opCodes := make([]OpCode, 0, len(matching))
|
||||
for _, m := range matching {
|
||||
// invariant: we've pumped out correct diffs to change
|
||||
// a[:i] into b[:j], and the next matching block is
|
||||
// a[ai:ai+size] == b[bj:bj+size]. So we need to pump
|
||||
// out a diff to change a[i:ai] into b[j:bj], pump out
|
||||
// the matching block, and move (i,j) beyond the match
|
||||
ai, bj, size := m.A, m.B, m.Size
|
||||
tag := byte(0)
|
||||
if i < ai && j < bj {
|
||||
tag = 'r'
|
||||
} else if i < ai {
|
||||
tag = 'd'
|
||||
} else if j < bj {
|
||||
tag = 'i'
|
||||
}
|
||||
if tag > 0 {
|
||||
opCodes = append(opCodes, OpCode{tag, i, ai, j, bj})
|
||||
}
|
||||
i, j = ai+size, bj+size
|
||||
// the list of matching blocks is terminated by a
|
||||
// sentinel with size 0
|
||||
if size > 0 {
|
||||
opCodes = append(opCodes, OpCode{'e', ai, i, bj, j})
|
||||
}
|
||||
}
|
||||
m.opCodes = opCodes
|
||||
return m.opCodes
|
||||
}
|
||||
|
||||
// Isolate change clusters by eliminating ranges with no changes.
|
||||
//
|
||||
// Return a generator of groups with up to n lines of context.
|
||||
// Each group is in the same format as returned by GetOpCodes().
|
||||
func (m *SequenceMatcher) GetGroupedOpCodes(n int) [][]OpCode {
|
||||
if n < 0 {
|
||||
n = 3
|
||||
}
|
||||
codes := m.GetOpCodes()
|
||||
if len(codes) == 0 {
|
||||
codes = []OpCode{OpCode{'e', 0, 1, 0, 1}}
|
||||
}
|
||||
// Fixup leading and trailing groups if they show no changes.
|
||||
if codes[0].Tag == 'e' {
|
||||
c := codes[0]
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
codes[0] = OpCode{c.Tag, max(i1, i2-n), i2, max(j1, j2-n), j2}
|
||||
}
|
||||
if codes[len(codes)-1].Tag == 'e' {
|
||||
c := codes[len(codes)-1]
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
codes[len(codes)-1] = OpCode{c.Tag, i1, min(i2, i1+n), j1, min(j2, j1+n)}
|
||||
}
|
||||
nn := n + n
|
||||
groups := [][]OpCode{}
|
||||
group := []OpCode{}
|
||||
for _, c := range codes {
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
// End the current group and start a new one whenever
|
||||
// there is a large range with no changes.
|
||||
if c.Tag == 'e' && i2-i1 > nn {
|
||||
group = append(group, OpCode{c.Tag, i1, min(i2, i1+n),
|
||||
j1, min(j2, j1+n)})
|
||||
groups = append(groups, group)
|
||||
group = []OpCode{}
|
||||
i1, j1 = max(i1, i2-n), max(j1, j2-n)
|
||||
}
|
||||
group = append(group, OpCode{c.Tag, i1, i2, j1, j2})
|
||||
}
|
||||
if len(group) > 0 && !(len(group) == 1 && group[0].Tag == 'e') {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
// Return a measure of the sequences' similarity (float in [0,1]).
|
||||
//
|
||||
// Where T is the total number of elements in both sequences, and
|
||||
// M is the number of matches, this is 2.0*M / T.
|
||||
// Note that this is 1 if the sequences are identical, and 0 if
|
||||
// they have nothing in common.
|
||||
//
|
||||
// .Ratio() is expensive to compute if you haven't already computed
|
||||
// .GetMatchingBlocks() or .GetOpCodes(), in which case you may
|
||||
// want to try .QuickRatio() or .RealQuickRation() first to get an
|
||||
// upper bound.
|
||||
func (m *SequenceMatcher) Ratio() float64 {
|
||||
matches := 0
|
||||
for _, m := range m.GetMatchingBlocks() {
|
||||
matches += m.Size
|
||||
}
|
||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||
}
|
||||
|
||||
// Return an upper bound on ratio() relatively quickly.
|
||||
//
|
||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||
// is faster to compute.
|
||||
func (m *SequenceMatcher) QuickRatio() float64 {
|
||||
// viewing a and b as multisets, set matches to the cardinality
|
||||
// of their intersection; this counts the number of matches
|
||||
// without regard to order, so is clearly an upper bound
|
||||
if m.fullBCount == nil {
|
||||
m.fullBCount = map[string]int{}
|
||||
for _, s := range m.b {
|
||||
m.fullBCount[s] = m.fullBCount[s] + 1
|
||||
}
|
||||
}
|
||||
|
||||
// avail[x] is the number of times x appears in 'b' less the
|
||||
// number of times we've seen it in 'a' so far ... kinda
|
||||
avail := map[string]int{}
|
||||
matches := 0
|
||||
for _, s := range m.a {
|
||||
n, ok := avail[s]
|
||||
if !ok {
|
||||
n = m.fullBCount[s]
|
||||
}
|
||||
avail[s] = n - 1
|
||||
if n > 0 {
|
||||
matches += 1
|
||||
}
|
||||
}
|
||||
return calculateRatio(matches, len(m.a)+len(m.b))
|
||||
}
|
||||
|
||||
// Return an upper bound on ratio() very quickly.
|
||||
//
|
||||
// This isn't defined beyond that it is an upper bound on .Ratio(), and
|
||||
// is faster to compute than either .Ratio() or .QuickRatio().
|
||||
func (m *SequenceMatcher) RealQuickRatio() float64 {
|
||||
la, lb := len(m.a), len(m.b)
|
||||
return calculateRatio(min(la, lb), la+lb)
|
||||
}
|
||||
|
||||
// Convert range to the "ed" format
|
||||
func formatRangeUnified(start, stop int) string {
|
||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||
beginning := start + 1 // lines start numbering with one
|
||||
length := stop - start
|
||||
if length == 1 {
|
||||
return fmt.Sprintf("%d", beginning)
|
||||
}
|
||||
if length == 0 {
|
||||
beginning -= 1 // empty ranges begin at line just before the range
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", beginning, length)
|
||||
}
|
||||
|
||||
// Unified diff parameters
|
||||
type UnifiedDiff struct {
|
||||
A []string // First sequence lines
|
||||
FromFile string // First file name
|
||||
FromDate string // First file time
|
||||
B []string // Second sequence lines
|
||||
ToFile string // Second file name
|
||||
ToDate string // Second file time
|
||||
Eol string // Headers end of line, defaults to LF
|
||||
Context int // Number of context lines
|
||||
}
|
||||
|
||||
// Compare two sequences of lines; generate the delta as a unified diff.
|
||||
//
|
||||
// Unified diffs are a compact way of showing line changes and a few
|
||||
// lines of context. The number of context lines is set by 'n' which
|
||||
// defaults to three.
|
||||
//
|
||||
// By default, the diff control lines (those with ---, +++, or @@) are
|
||||
// created with a trailing newline. This is helpful so that inputs
|
||||
// created from file.readlines() result in diffs that are suitable for
|
||||
// file.writelines() since both the inputs and outputs have trailing
|
||||
// newlines.
|
||||
//
|
||||
// For inputs that do not have trailing newlines, set the lineterm
|
||||
// argument to "" so that the output will be uniformly newline free.
|
||||
//
|
||||
// The unidiff format normally has a header for filenames and modification
|
||||
// times. Any or all of these may be specified using strings for
|
||||
// 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
|
||||
// The modification times are normally expressed in the ISO 8601 format.
|
||||
func WriteUnifiedDiff(writer io.Writer, diff UnifiedDiff) error {
|
||||
buf := bufio.NewWriter(writer)
|
||||
defer buf.Flush()
|
||||
wf := func(format string, args ...interface{}) error {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
return err
|
||||
}
|
||||
ws := func(s string) error {
|
||||
_, err := buf.WriteString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
if len(diff.Eol) == 0 {
|
||||
diff.Eol = "\n"
|
||||
}
|
||||
|
||||
started := false
|
||||
m := NewMatcher(diff.A, diff.B)
|
||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||
if !started {
|
||||
started = true
|
||||
fromDate := ""
|
||||
if len(diff.FromDate) > 0 {
|
||||
fromDate = "\t" + diff.FromDate
|
||||
}
|
||||
toDate := ""
|
||||
if len(diff.ToDate) > 0 {
|
||||
toDate = "\t" + diff.ToDate
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
err := wf("--- %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = wf("+++ %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
first, last := g[0], g[len(g)-1]
|
||||
range1 := formatRangeUnified(first.I1, last.I2)
|
||||
range2 := formatRangeUnified(first.J1, last.J2)
|
||||
if err := wf("@@ -%s +%s @@%s", range1, range2, diff.Eol); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range g {
|
||||
i1, i2, j1, j2 := c.I1, c.I2, c.J1, c.J2
|
||||
if c.Tag == 'e' {
|
||||
for _, line := range diff.A[i1:i2] {
|
||||
if err := ws(" " + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'd' {
|
||||
for _, line := range diff.A[i1:i2] {
|
||||
if err := ws("-" + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if c.Tag == 'r' || c.Tag == 'i' {
|
||||
for _, line := range diff.B[j1:j2] {
|
||||
if err := ws("+" + line); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Like WriteUnifiedDiff but returns the diff a string.
|
||||
func GetUnifiedDiffString(diff UnifiedDiff) (string, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteUnifiedDiff(w, diff)
|
||||
return string(w.Bytes()), err
|
||||
}
|
||||
|
||||
// Convert range to the "ed" format.
|
||||
func formatRangeContext(start, stop int) string {
|
||||
// Per the diff spec at http://www.unix.org/single_unix_specification/
|
||||
beginning := start + 1 // lines start numbering with one
|
||||
length := stop - start
|
||||
if length == 0 {
|
||||
beginning -= 1 // empty ranges begin at line just before the range
|
||||
}
|
||||
if length <= 1 {
|
||||
return fmt.Sprintf("%d", beginning)
|
||||
}
|
||||
return fmt.Sprintf("%d,%d", beginning, beginning+length-1)
|
||||
}
|
||||
|
||||
type ContextDiff UnifiedDiff
|
||||
|
||||
// Compare two sequences of lines; generate the delta as a context diff.
|
||||
//
|
||||
// Context diffs are a compact way of showing line changes and a few
|
||||
// lines of context. The number of context lines is set by diff.Context
|
||||
// which defaults to three.
|
||||
//
|
||||
// By default, the diff control lines (those with *** or ---) are
|
||||
// created with a trailing newline.
|
||||
//
|
||||
// For inputs that do not have trailing newlines, set the diff.Eol
|
||||
// argument to "" so that the output will be uniformly newline free.
|
||||
//
|
||||
// The context diff format normally has a header for filenames and
|
||||
// modification times. Any or all of these may be specified using
|
||||
// strings for diff.FromFile, diff.ToFile, diff.FromDate, diff.ToDate.
|
||||
// The modification times are normally expressed in the ISO 8601 format.
|
||||
// If not specified, the strings default to blanks.
|
||||
func WriteContextDiff(writer io.Writer, diff ContextDiff) error {
|
||||
buf := bufio.NewWriter(writer)
|
||||
defer buf.Flush()
|
||||
var diffErr error
|
||||
wf := func(format string, args ...interface{}) {
|
||||
_, err := buf.WriteString(fmt.Sprintf(format, args...))
|
||||
if diffErr == nil && err != nil {
|
||||
diffErr = err
|
||||
}
|
||||
}
|
||||
ws := func(s string) {
|
||||
_, err := buf.WriteString(s)
|
||||
if diffErr == nil && err != nil {
|
||||
diffErr = err
|
||||
}
|
||||
}
|
||||
|
||||
if len(diff.Eol) == 0 {
|
||||
diff.Eol = "\n"
|
||||
}
|
||||
|
||||
prefix := map[byte]string{
|
||||
'i': "+ ",
|
||||
'd': "- ",
|
||||
'r': "! ",
|
||||
'e': " ",
|
||||
}
|
||||
|
||||
started := false
|
||||
m := NewMatcher(diff.A, diff.B)
|
||||
for _, g := range m.GetGroupedOpCodes(diff.Context) {
|
||||
if !started {
|
||||
started = true
|
||||
fromDate := ""
|
||||
if len(diff.FromDate) > 0 {
|
||||
fromDate = "\t" + diff.FromDate
|
||||
}
|
||||
toDate := ""
|
||||
if len(diff.ToDate) > 0 {
|
||||
toDate = "\t" + diff.ToDate
|
||||
}
|
||||
if diff.FromFile != "" || diff.ToFile != "" {
|
||||
wf("*** %s%s%s", diff.FromFile, fromDate, diff.Eol)
|
||||
wf("--- %s%s%s", diff.ToFile, toDate, diff.Eol)
|
||||
}
|
||||
}
|
||||
|
||||
first, last := g[0], g[len(g)-1]
|
||||
ws("***************" + diff.Eol)
|
||||
|
||||
range1 := formatRangeContext(first.I1, last.I2)
|
||||
wf("*** %s ****%s", range1, diff.Eol)
|
||||
for _, c := range g {
|
||||
if c.Tag == 'r' || c.Tag == 'd' {
|
||||
for _, cc := range g {
|
||||
if cc.Tag == 'i' {
|
||||
continue
|
||||
}
|
||||
for _, line := range diff.A[cc.I1:cc.I2] {
|
||||
ws(prefix[cc.Tag] + line)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
range2 := formatRangeContext(first.J1, last.J2)
|
||||
wf("--- %s ----%s", range2, diff.Eol)
|
||||
for _, c := range g {
|
||||
if c.Tag == 'r' || c.Tag == 'i' {
|
||||
for _, cc := range g {
|
||||
if cc.Tag == 'd' {
|
||||
continue
|
||||
}
|
||||
for _, line := range diff.B[cc.J1:cc.J2] {
|
||||
ws(prefix[cc.Tag] + line)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return diffErr
|
||||
}
|
||||
|
||||
// Like WriteContextDiff but returns the diff a string.
|
||||
func GetContextDiffString(diff ContextDiff) (string, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := WriteContextDiff(w, diff)
|
||||
return string(w.Bytes()), err
|
||||
}
|
||||
|
||||
// Split a string on "\n" while preserving them. The output can be used
|
||||
// as input for UnifiedDiff and ContextDiff structures.
|
||||
func SplitLines(s string) []string {
|
||||
lines := strings.SplitAfter(s, "\n")
|
||||
lines[len(lines)-1] += "\n"
|
||||
return lines
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
Copyright (c) 2012 - 2013 Mat Ryer and Tyler Bunnell
|
||||
|
||||
Please consider promoting this project if you find it useful.
|
||||
|
||||
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.
|
|
@ -0,0 +1,484 @@
|
|||
/*
|
||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||
*/
|
||||
|
||||
package assert
|
||||
|
||||
import (
|
||||
http "net/http"
|
||||
url "net/url"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func Conditionf(t TestingT, comp Comparison, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Condition(t, comp, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Contains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func DirExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return DirExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ElementsMatch(t, listA, listB, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// assert.Emptyf(t, obj, "error message %s", "formatted")
|
||||
func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Empty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// assert.Equalf(t, 123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Equal(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted")
|
||||
func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// assert.EqualValuesf(t, uint32(123, "error message %s", "formatted"), int32(123))
|
||||
func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.Errorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Error(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123))
|
||||
func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Exactly(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FailNow(t, failureMessage, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// assert.Falsef(t, myBool, "error message %s", "formatted")
|
||||
func Falsef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return False(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FileExists(t, path, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyNotContains(t, handler, method, url, values, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPError(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPRedirect(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPSuccess(t, handler, method, url, values, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// assert.Implementsf(t, (*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Implements(t, interfaceObject, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// assert.InDeltaf(t, math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
||||
func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDelta(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaMapValues(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaSlice(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilon(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsType(t, expectedType, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// assert.Lenf(t, mySlice, 3, "error message %s", "formatted")
|
||||
func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Len(t, object, length, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// assert.Nilf(t, err, "error message %s", "formatted")
|
||||
func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Nil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if assert.NoErrorf(t, err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func NoErrorf(t TestingT, err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NoError(t, err, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotContains(t, s, contains, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if assert.NotEmptyf(t, obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEmpty(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqual(t, expected, actual, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// assert.NotNilf(t, err, "error message %s", "formatted")
|
||||
func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotNil(t, object, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func NotPanicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotPanics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// assert.NotRegexpf(t, regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted")
|
||||
func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotRegexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotSubset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotZero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func Panicsf(t TestingT, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Panics(t, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// assert.Regexpf(t, regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted")
|
||||
func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Regexp(t, rx, str, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Subset(t, list, subset, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// assert.Truef(t, myBool, "error message %s", "formatted")
|
||||
func Truef(t TestingT, value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return True(t, value, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinDuration(t, expected, actual, delta, append([]interface{}{msg}, args...)...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Zero(t, i, append([]interface{}{msg}, args...)...)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{{.CommentFormat}}
|
||||
func {{.DocInfo.Name}}f(t TestingT, {{.ParamsFormat}}) bool {
|
||||
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||
return {{.DocInfo.Name}}(t, {{.ForwardedParamsFormat}})
|
||||
}
|
|
@ -0,0 +1,956 @@
|
|||
/*
|
||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||
*/
|
||||
|
||||
package assert
|
||||
|
||||
import (
|
||||
http "net/http"
|
||||
url "net/url"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Condition uses a Comparison to assert a complex condition.
|
||||
func (a *Assertions) Condition(comp Comparison, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Condition(a.t, comp, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func (a *Assertions) Conditionf(comp Comparison, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Conditionf(a.t, comp, msg, args...)
|
||||
}
|
||||
|
||||
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Contains("Hello World", "World")
|
||||
// a.Contains(["Hello", "World"], "World")
|
||||
// a.Contains({"Hello": "World"}, "Hello")
|
||||
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Contains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Containsf("Hello World", "World", "error message %s", "formatted")
|
||||
// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Containsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return DirExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return DirExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2])
|
||||
func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ElementsMatch(a.t, listA, listB, msgAndArgs...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return ElementsMatchf(a.t, listA, listB, msg, args...)
|
||||
}
|
||||
|
||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// a.Empty(obj)
|
||||
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Empty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// a.Emptyf(obj, "error message %s", "formatted")
|
||||
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Emptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// Equal asserts that two objects are equal.
|
||||
//
|
||||
// a.Equal(123, 123)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Equal(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.EqualError(err, expectedErrorString)
|
||||
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualError(a.t, theError, errString, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
|
||||
func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualErrorf(a.t, theError, errString, msg, args...)
|
||||
}
|
||||
|
||||
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValues(uint32(123), int32(123))
|
||||
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualValues(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
|
||||
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return EqualValuesf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// a.Equalf(123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Equalf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Error(err) {
|
||||
// assert.Equal(t, expectedError, err)
|
||||
// }
|
||||
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Error(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Errorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Errorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// Exactly asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactly(int32(123), int64(123))
|
||||
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Exactly(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
|
||||
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Exactlyf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Fail reports a failure through
|
||||
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Fail(a.t, failureMessage, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FailNow fails test
|
||||
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FailNow(a.t, failureMessage, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FailNowf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Failf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// False asserts that the specified value is false.
|
||||
//
|
||||
// a.False(myBool)
|
||||
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return False(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// a.Falsef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Falsef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FileExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return FileExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPError(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPErrorf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return HTTPSuccessf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// Implements asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implements((*MyInterface)(nil), new(MyObject))
|
||||
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Implements(a.t, interfaceObject, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Implementsf(a.t, interfaceObject, object, msg, args...)
|
||||
}
|
||||
|
||||
// InDelta asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
|
||||
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDelta(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InDeltaSlice is the same as InDelta, except it compares two slices.
|
||||
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
||||
func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InDeltaf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
||||
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// IsType asserts that the specified objects are of the same type.
|
||||
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsType(a.t, expectedType, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return IsTypef(a.t, expectedType, object, msg, args...)
|
||||
}
|
||||
|
||||
// JSONEq asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return JSONEq(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return JSONEqf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Len asserts that the specified object has specific length.
|
||||
// Len also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// a.Len(mySlice, 3)
|
||||
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Len(a.t, object, length, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// a.Lenf(mySlice, 3, "error message %s", "formatted")
|
||||
func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Lenf(a.t, object, length, msg, args...)
|
||||
}
|
||||
|
||||
// Nil asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nil(err)
|
||||
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Nil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Nilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NoError asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoError(err) {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NoError(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoErrorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NoErrorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContains("Hello World", "Earth")
|
||||
// a.NotContains(["Hello", "World"], "Earth")
|
||||
// a.NotContains({"Hello": "World"}, "Earth")
|
||||
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotContains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotContainsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if a.NotEmpty(obj) {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEmpty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if a.NotEmptyf(obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEmptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotEqual asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqual(obj1, obj2)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqual(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotEqualf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// NotNil asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNil(err)
|
||||
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotNil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotNilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanics(func(){ RemainCalm() })
|
||||
func (a *Assertions) NotPanics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotPanics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func (a *Assertions) NotPanicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotPanicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// NotRegexp asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
|
||||
// a.NotRegexp("^start", "it's not starting")
|
||||
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotRegexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotRegexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// NotSubset asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
|
||||
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotSubset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotSubsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// NotZero asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotZero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return NotZerof(a.t, i, msg, args...)
|
||||
}
|
||||
|
||||
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panics(func(){ GoCrazy() })
|
||||
func (a *Assertions) Panics(f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Panics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
|
||||
func (a *Assertions) PanicsWithValue(expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return PanicsWithValue(a.t, expected, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) PanicsWithValuef(expected interface{}, f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return PanicsWithValuef(a.t, expected, f, msg, args...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Panicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// Regexp asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexp(regexp.MustCompile("start"), "it's starting")
|
||||
// a.Regexp("start...$", "it's not starting")
|
||||
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Regexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Regexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// Subset asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
|
||||
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Subset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Subsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// True asserts that the specified value is true.
|
||||
//
|
||||
// a.True(myBool)
|
||||
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return True(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// a.Truef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Truef(value bool, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Truef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// WithinDuration asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
|
||||
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return WithinDurationf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// Zero asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Zero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) bool {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
return Zerof(a.t, i, msg, args...)
|
||||
}
|
5
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
5
vendor/github.com/stretchr/testify/assert/assertion_forward.go.tmpl
generated
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
{{.CommentWithoutT "a"}}
|
||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) bool {
|
||||
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||
return {{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,45 @@
|
|||
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The following is a complete example using assert in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if you assert many times, use the format below:
|
||||
//
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
// assert := assert.New(t)
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// assert.Equal(a, b, "The two words should be the same.")
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
// Assertions allow you to easily write test code, and are global funcs in the `assert` package.
|
||||
// All assertion functions take, as the first argument, the `*testing.T` object provided by the
|
||||
// testing framework. This allows the assertion funcs to write the failings and other details to
|
||||
// the correct place.
|
||||
//
|
||||
// Every assertion function also takes an optional string message as the final argument,
|
||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||
package assert
|
|
@ -0,0 +1,10 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// AnError is an error instance useful for testing. If the code does not care
|
||||
// about error specifics, and only needs to return the error for example, this
|
||||
// error should be used to make the test code more readable.
|
||||
var AnError = errors.New("assert.AnError general error for testing")
|
|
@ -0,0 +1,16 @@
|
|||
package assert
|
||||
|
||||
// Assertions provides assertion methods around the
|
||||
// TestingT interface.
|
||||
type Assertions struct {
|
||||
t TestingT
|
||||
}
|
||||
|
||||
// New makes a new Assertions object for the specified TestingT.
|
||||
func New(t TestingT) *Assertions {
|
||||
return &Assertions{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_forward.go.tmpl -include-format-funcs
|
|
@ -0,0 +1,143 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// httpCode is a helper that returns HTTP code of the response. It returns -1 and
|
||||
// an error if building a new request fails.
|
||||
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
req.URL.RawQuery = values.Encode()
|
||||
handler(w, req)
|
||||
return w.Code, nil
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPSuccess(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isSuccessCode := code >= http.StatusOK && code <= http.StatusPartialContent
|
||||
if !isSuccessCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP success status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isSuccessCode
|
||||
}
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPRedirect(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isRedirectCode := code >= http.StatusMultipleChoices && code <= http.StatusTemporaryRedirect
|
||||
if !isRedirectCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP redirect status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isRedirectCode
|
||||
}
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPError(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
code, err := httpCode(handler, method, url, values)
|
||||
if err != nil {
|
||||
Fail(t, fmt.Sprintf("Failed to build test request, got error: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
isErrorCode := code >= http.StatusBadRequest
|
||||
if !isErrorCode {
|
||||
Fail(t, fmt.Sprintf("Expected HTTP error status code for %q but received %d", url+"?"+values.Encode(), code))
|
||||
}
|
||||
|
||||
return isErrorCode
|
||||
}
|
||||
|
||||
// HTTPBody is a helper that returns HTTP body of the response. It returns
|
||||
// empty string if building a new request fails.
|
||||
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
|
||||
w := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
handler(w, req)
|
||||
return w.Body.String()
|
||||
}
|
||||
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if !contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return contains
|
||||
}
|
||||
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) bool {
|
||||
if h, ok := t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
body := HTTPBody(handler, method, url, values)
|
||||
|
||||
contains := strings.Contains(body, fmt.Sprint(str))
|
||||
if contains {
|
||||
Fail(t, fmt.Sprintf("Expected response body for \"%s\" to NOT contain \"%s\" but found \"%s\"", url+"?"+values.Encode(), str, body))
|
||||
}
|
||||
|
||||
return !contains
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Package require implements the same assertions as the `assert` package but
|
||||
// stops test execution when a test fails.
|
||||
//
|
||||
// Example Usage
|
||||
//
|
||||
// The following is a complete example using require in a standard test function:
|
||||
// import (
|
||||
// "testing"
|
||||
// "github.com/stretchr/testify/require"
|
||||
// )
|
||||
//
|
||||
// func TestSomething(t *testing.T) {
|
||||
//
|
||||
// var a string = "Hello"
|
||||
// var b string = "Hello"
|
||||
//
|
||||
// require.Equal(t, a, b, "The two words should be the same.")
|
||||
//
|
||||
// }
|
||||
//
|
||||
// Assertions
|
||||
//
|
||||
// The `require` package have same global functions as in the `assert` package,
|
||||
// but instead of returning a boolean result they call `t.FailNow()`.
|
||||
//
|
||||
// Every assertion function also takes an optional string message as the final argument,
|
||||
// allowing custom error messages to be appended to the message the assertion method outputs.
|
||||
package require
|
16
vendor/github.com/stretchr/testify/require/forward_requirements.go
generated
vendored
Normal file
16
vendor/github.com/stretchr/testify/require/forward_requirements.go
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
package require
|
||||
|
||||
// Assertions provides assertion methods around the
|
||||
// TestingT interface.
|
||||
type Assertions struct {
|
||||
t TestingT
|
||||
}
|
||||
|
||||
// New makes a new Assertions object for the specified TestingT.
|
||||
func New(t TestingT) *Assertions {
|
||||
return &Assertions{
|
||||
t: t,
|
||||
}
|
||||
}
|
||||
|
||||
//go:generate go run ../_codegen/main.go -output-package=require -template=require_forward.go.tmpl -include-format-funcs
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
{{.Comment}}
|
||||
func {{.DocInfo.Name}}(t TestingT, {{.Params}}) {
|
||||
if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return }
|
||||
if h, ok := t.(tHelper); ok { h.Helper() }
|
||||
t.FailNow()
|
||||
}
|
|
@ -0,0 +1,957 @@
|
|||
/*
|
||||
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
|
||||
* THIS FILE MUST NOT BE EDITED BY HAND
|
||||
*/
|
||||
|
||||
package require
|
||||
|
||||
import (
|
||||
assert "github.com/stretchr/testify/assert"
|
||||
http "net/http"
|
||||
url "net/url"
|
||||
time "time"
|
||||
)
|
||||
|
||||
// Condition uses a Comparison to assert a complex condition.
|
||||
func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Condition(a.t, comp, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Conditionf uses a Comparison to assert a complex condition.
|
||||
func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Conditionf(a.t, comp, msg, args...)
|
||||
}
|
||||
|
||||
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Contains("Hello World", "World")
|
||||
// a.Contains(["Hello", "World"], "World")
|
||||
// a.Contains({"Hello": "World"}, "Hello")
|
||||
func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Contains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Containsf asserts that the specified string, list(array, slice...) or map contains the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.Containsf("Hello World", "World", "error message %s", "formatted")
|
||||
// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted")
|
||||
// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted")
|
||||
func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Containsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// DirExists checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
DirExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// DirExistsf checks whether a directory exists in the given path. It also fails if the path is a file rather a directory or there is an error checking whether it exists.
|
||||
func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
DirExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2])
|
||||
func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
ElementsMatch(a.t, listA, listB, msgAndArgs...)
|
||||
}
|
||||
|
||||
// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified
|
||||
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
||||
// the number of appearances of each of them in both lists should match.
|
||||
//
|
||||
// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted")
|
||||
func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
ElementsMatchf(a.t, listA, listB, msg, args...)
|
||||
}
|
||||
|
||||
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// a.Empty(obj)
|
||||
func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Empty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// a.Emptyf(obj, "error message %s", "formatted")
|
||||
func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Emptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// Equal asserts that two objects are equal.
|
||||
//
|
||||
// a.Equal(123, 123)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Equal(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.EqualError(err, expectedErrorString)
|
||||
func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualError(a.t, theError, errString, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualErrorf asserts that a function returned an error (i.e. not `nil`)
|
||||
// and that it is equal to the provided error.
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted")
|
||||
func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualErrorf(a.t, theError, errString, msg, args...)
|
||||
}
|
||||
|
||||
// EqualValues asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValues(uint32(123), int32(123))
|
||||
func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualValues(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// EqualValuesf asserts that two objects are equal or convertable to the same types
|
||||
// and equal.
|
||||
//
|
||||
// a.EqualValuesf(uint32(123, "error message %s", "formatted"), int32(123))
|
||||
func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
EqualValuesf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Equalf asserts that two objects are equal.
|
||||
//
|
||||
// a.Equalf(123, 123, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses). Function equality
|
||||
// cannot be determined and will always fail.
|
||||
func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Equalf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Error asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Error(err) {
|
||||
// assert.Equal(t, expectedError, err)
|
||||
// }
|
||||
func (a *Assertions) Error(err error, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Error(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Errorf asserts that a function returned an error (i.e. not `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.Errorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedErrorf, err)
|
||||
// }
|
||||
func (a *Assertions) Errorf(err error, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Errorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// Exactly asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactly(int32(123), int64(123))
|
||||
func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Exactly(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Exactlyf asserts that two objects are equal in value and type.
|
||||
//
|
||||
// a.Exactlyf(int32(123, "error message %s", "formatted"), int64(123))
|
||||
func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Exactlyf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Fail reports a failure through
|
||||
func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Fail(a.t, failureMessage, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FailNow fails test
|
||||
func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FailNow(a.t, failureMessage, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FailNowf fails test
|
||||
func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FailNowf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// Failf reports a failure through
|
||||
func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Failf(a.t, failureMessage, msg, args...)
|
||||
}
|
||||
|
||||
// False asserts that the specified value is false.
|
||||
//
|
||||
// a.False(myBool)
|
||||
func (a *Assertions) False(value bool, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
False(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Falsef asserts that the specified value is false.
|
||||
//
|
||||
// a.Falsef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Falsef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// FileExists checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FileExists(a.t, path, msgAndArgs...)
|
||||
}
|
||||
|
||||
// FileExistsf checks whether a file exists in the given path. It also fails if the path points to a directory or there is an error when trying to check the file.
|
||||
func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
FileExistsf(a.t, path, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPBodyContains asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyContainsf asserts that a specified handler returns a
|
||||
// body that contains a string.
|
||||
//
|
||||
// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContains asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPBodyNotContainsf asserts that a specified handler returns a
|
||||
// body that does not contain a string.
|
||||
//
|
||||
// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPError asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPError(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPErrorf asserts that a specified handler returns an error status code.
|
||||
//
|
||||
// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPErrorf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPRedirect asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPRedirectf asserts that a specified handler returns a redirect status code.
|
||||
//
|
||||
// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}}
|
||||
//
|
||||
// Returns whether the assertion was successful (true, "error message %s", "formatted") or not (false).
|
||||
func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPRedirectf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// HTTPSuccess asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil)
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...)
|
||||
}
|
||||
|
||||
// HTTPSuccessf asserts that a specified handler returns a success status code.
|
||||
//
|
||||
// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted")
|
||||
//
|
||||
// Returns whether the assertion was successful (true) or not (false).
|
||||
func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
HTTPSuccessf(a.t, handler, method, url, values, msg, args...)
|
||||
}
|
||||
|
||||
// Implements asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implements((*MyInterface)(nil), new(MyObject))
|
||||
func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Implements(a.t, interfaceObject, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Implementsf asserts that an object is implemented by the specified interface.
|
||||
//
|
||||
// a.Implementsf((*MyInterface, "error message %s", "formatted")(nil), new(MyObject))
|
||||
func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Implementsf(a.t, interfaceObject, object, msg, args...)
|
||||
}
|
||||
|
||||
// InDelta asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDelta(math.Pi, (22 / 7.0), 0.01)
|
||||
func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDelta(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
||||
func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InDeltaSlice is the same as InDelta, except it compares two slices.
|
||||
func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InDeltaSlicef is the same as InDelta, except it compares two slices.
|
||||
func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaSlicef(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InDeltaf asserts that the two numerals are within delta of each other.
|
||||
//
|
||||
// a.InDeltaf(math.Pi, (22 / 7.0, "error message %s", "formatted"), 0.01)
|
||||
func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InDeltaf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
||||
func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...)
|
||||
}
|
||||
|
||||
// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices.
|
||||
func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// InEpsilonf asserts that expected and actual have a relative error less than epsilon
|
||||
func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
|
||||
}
|
||||
|
||||
// IsType asserts that the specified objects are of the same type.
|
||||
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
IsType(a.t, expectedType, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// IsTypef asserts that the specified objects are of the same type.
|
||||
func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
IsTypef(a.t, expectedType, object, msg, args...)
|
||||
}
|
||||
|
||||
// JSONEq asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
||||
func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
JSONEq(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// JSONEqf asserts that two JSON strings are equivalent.
|
||||
//
|
||||
// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted")
|
||||
func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
JSONEqf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// Len asserts that the specified object has specific length.
|
||||
// Len also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// a.Len(mySlice, 3)
|
||||
func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Len(a.t, object, length, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Lenf asserts that the specified object has specific length.
|
||||
// Lenf also fails if the object has a type that len() not accept.
|
||||
//
|
||||
// a.Lenf(mySlice, 3, "error message %s", "formatted")
|
||||
func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Lenf(a.t, object, length, msg, args...)
|
||||
}
|
||||
|
||||
// Nil asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nil(err)
|
||||
func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Nil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Nilf asserts that the specified object is nil.
|
||||
//
|
||||
// a.Nilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Nilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NoError asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoError(err) {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NoError(a.t, err, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NoErrorf asserts that a function returned no error (i.e. `nil`).
|
||||
//
|
||||
// actualObj, err := SomeFunction()
|
||||
// if a.NoErrorf(err, "error message %s", "formatted") {
|
||||
// assert.Equal(t, expectedObj, actualObj)
|
||||
// }
|
||||
func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NoErrorf(a.t, err, msg, args...)
|
||||
}
|
||||
|
||||
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContains("Hello World", "Earth")
|
||||
// a.NotContains(["Hello", "World"], "Earth")
|
||||
// a.NotContains({"Hello": "World"}, "Earth")
|
||||
func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotContains(a.t, s, contains, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the
|
||||
// specified substring or element.
|
||||
//
|
||||
// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted")
|
||||
// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted")
|
||||
func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotContainsf(a.t, s, contains, msg, args...)
|
||||
}
|
||||
|
||||
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if a.NotEmpty(obj) {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEmpty(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
||||
// a slice or a channel with len == 0.
|
||||
//
|
||||
// if a.NotEmptyf(obj, "error message %s", "formatted") {
|
||||
// assert.Equal(t, "two", obj[1])
|
||||
// }
|
||||
func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEmptyf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotEqual asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqual(obj1, obj2)
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEqual(a.t, expected, actual, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotEqualf asserts that the specified values are NOT equal.
|
||||
//
|
||||
// a.NotEqualf(obj1, obj2, "error message %s", "formatted")
|
||||
//
|
||||
// Pointer variable equality is determined based on the equality of the
|
||||
// referenced values (as opposed to the memory addresses).
|
||||
func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotEqualf(a.t, expected, actual, msg, args...)
|
||||
}
|
||||
|
||||
// NotNil asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNil(err)
|
||||
func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotNil(a.t, object, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotNilf asserts that the specified object is not nil.
|
||||
//
|
||||
// a.NotNilf(err, "error message %s", "formatted")
|
||||
func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotNilf(a.t, object, msg, args...)
|
||||
}
|
||||
|
||||
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanics(func(){ RemainCalm() })
|
||||
func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotPanics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic.
|
||||
//
|
||||
// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted")
|
||||
func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotPanicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// NotRegexp asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexp(regexp.MustCompile("starts"), "it's starting")
|
||||
// a.NotRegexp("^start", "it's not starting")
|
||||
func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotRegexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotRegexpf asserts that a specified regexp does not match a string.
|
||||
//
|
||||
// a.NotRegexpf(regexp.MustCompile("starts", "error message %s", "formatted"), "it's starting")
|
||||
// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotRegexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// NotSubset asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]")
|
||||
func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotSubset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotSubsetf asserts that the specified list(array, slice...) contains not all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotSubsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// NotZero asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotZero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// NotZerof asserts that i is not the zero value for its type.
|
||||
func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
NotZerof(a.t, i, msg, args...)
|
||||
}
|
||||
|
||||
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panics(func(){ GoCrazy() })
|
||||
func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Panics(a.t, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValue("crazy error", func(){ GoCrazy() })
|
||||
func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
PanicsWithValue(a.t, expected, f, msgAndArgs...)
|
||||
}
|
||||
|
||||
// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that
|
||||
// the recovered panic value equals the expected panic value.
|
||||
//
|
||||
// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
PanicsWithValuef(a.t, expected, f, msg, args...)
|
||||
}
|
||||
|
||||
// Panicsf asserts that the code inside the specified PanicTestFunc panics.
|
||||
//
|
||||
// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted")
|
||||
func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Panicsf(a.t, f, msg, args...)
|
||||
}
|
||||
|
||||
// Regexp asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexp(regexp.MustCompile("start"), "it's starting")
|
||||
// a.Regexp("start...$", "it's not starting")
|
||||
func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Regexp(a.t, rx, str, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Regexpf asserts that a specified regexp matches a string.
|
||||
//
|
||||
// a.Regexpf(regexp.MustCompile("start", "error message %s", "formatted"), "it's starting")
|
||||
// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted")
|
||||
func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Regexpf(a.t, rx, str, msg, args...)
|
||||
}
|
||||
|
||||
// Subset asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]")
|
||||
func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Subset(a.t, list, subset, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Subsetf asserts that the specified list(array, slice...) contains all
|
||||
// elements given in the specified subset(array, slice...).
|
||||
//
|
||||
// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted")
|
||||
func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Subsetf(a.t, list, subset, msg, args...)
|
||||
}
|
||||
|
||||
// True asserts that the specified value is true.
|
||||
//
|
||||
// a.True(myBool)
|
||||
func (a *Assertions) True(value bool, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
True(a.t, value, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Truef asserts that the specified value is true.
|
||||
//
|
||||
// a.Truef(myBool, "error message %s", "formatted")
|
||||
func (a *Assertions) Truef(value bool, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Truef(a.t, value, msg, args...)
|
||||
}
|
||||
|
||||
// WithinDuration asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDuration(time.Now(), time.Now(), 10*time.Second)
|
||||
func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
WithinDuration(a.t, expected, actual, delta, msgAndArgs...)
|
||||
}
|
||||
|
||||
// WithinDurationf asserts that the two times are within duration delta of each other.
|
||||
//
|
||||
// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted")
|
||||
func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
WithinDurationf(a.t, expected, actual, delta, msg, args...)
|
||||
}
|
||||
|
||||
// Zero asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Zero(a.t, i, msgAndArgs...)
|
||||
}
|
||||
|
||||
// Zerof asserts that i is the zero value for its type.
|
||||
func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) {
|
||||
if h, ok := a.t.(tHelper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
Zerof(a.t, i, msg, args...)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{{.CommentWithoutT "a"}}
|
||||
func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) {
|
||||
if h, ok := a.t.(tHelper); ok { h.Helper() }
|
||||
{{.DocInfo.Name}}(a.t, {{.ForwardedParams}})
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package require
|
||||
|
||||
// TestingT is an interface wrapper around *testing.T
|
||||
type TestingT interface {
|
||||
Errorf(format string, args ...interface{})
|
||||
FailNow()
|
||||
}
|
||||
|
||||
type tHelper interface {
|
||||
Helper()
|
||||
}
|
||||
|
||||
// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
|
||||
// for table driven tests.
|
||||
type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{})
|
||||
|
||||
// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
|
||||
// for table driven tests.
|
||||
type ValueAssertionFunc func(TestingT, interface{}, ...interface{})
|
||||
|
||||
// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
|
||||
// for table driven tests.
|
||||
type BoolAssertionFunc func(TestingT, bool, ...interface{})
|
||||
|
||||
// ValuesAssertionFunc is a common function prototype when validating an error value. Can be useful
|
||||
// for table driven tests.
|
||||
type ErrorAssertionFunc func(TestingT, error, ...interface{})
|
||||
|
||||
//go:generate go run ../_codegen/main.go -output-package=require -template=require.go.tmpl -include-format-funcs
|
|
@ -0,0 +1,9 @@
|
|||
# github.com/alexflint/go-scalar v1.0.0
|
||||
github.com/alexflint/go-scalar
|
||||
# github.com/davecgh/go-spew v1.1.1
|
||||
github.com/davecgh/go-spew/spew
|
||||
# github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/pmezard/go-difflib/difflib
|
||||
# github.com/stretchr/testify v1.2.2
|
||||
github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/require
|
Loading…
Reference in New Issue