mirror of
https://github.com/gojp/goreportcard.git
synced 2026-01-28 22:39:05 +08:00
vendor more tools
This commit is contained in:
7
go.mod
7
go.mod
@@ -4,6 +4,10 @@ go 1.16
|
||||
|
||||
require (
|
||||
github.com/DataDog/zstd v1.4.8 // indirect
|
||||
github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214
|
||||
github.com/alecthomas/gometalinter v1.0.3
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 // indirect
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a // indirect
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/dgraph-io/badger/v2 v2.2007.2
|
||||
@@ -12,6 +16,8 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.3 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v0.9.0-pre1.0.20171201122222-661e31bf844d
|
||||
@@ -22,5 +28,6 @@ require (
|
||||
golang.org/x/net v0.0.0-20210331212208-0fccb6fa2b5c // indirect
|
||||
golang.org/x/sys v0.0.0-20210402192133-700132347e07 // indirect
|
||||
golang.org/x/tools v0.1.0
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
|
||||
honnef.co/go/tools v0.1.3
|
||||
)
|
||||
|
||||
14
go.sum
14
go.sum
@@ -5,6 +5,14 @@ github.com/DataDog/zstd v1.4.8 h1:Rpmta4xZ/MgZnriKNd24iZMhGpP5dvUcs/uqfBapKZY=
|
||||
github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214 h1:YI/8G3uLbYyowJeOPVL6BMKe2wbL54h0FdEKmncU6lU=
|
||||
github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214/go.mod h1:Ef5UOtJdJ5rVFObdOVsrNgKV/Wf4I+daTCSk8GTrHIk=
|
||||
github.com/alecthomas/gometalinter v1.0.3 h1:Y5sxwml3eb93scdwfdOOh5VDULOtOEuCMrOnRpRwn1w=
|
||||
github.com/alecthomas/gometalinter v1.0.3/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15 h1:AUNCr9CiJuwrRYS3XieqF+Z9B9gNxo/eANAJCF2eiN4=
|
||||
github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a h1:BtpsbiV638WQZwhA98cEZw2BsbnQJrbd0BI7tsy0W1c=
|
||||
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -40,6 +48,10 @@ github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254 h1:Nb2aRlC404yz7gQIfRZxX9/MLvQiqXyiBTJtgAy6yrI=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254/go.mod h1:M9mZEtGIsR1oDaZagNPNG9iq9n2HrhZ17dsXk73V3Lw=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
@@ -125,6 +137,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/alecthomas/gocyclo"
|
||||
_ "github.com/alecthomas/gometalinter"
|
||||
_ "github.com/client9/misspell/cmd/misspell"
|
||||
_ "github.com/gordonklaus/ineffassign"
|
||||
_ "golang.org/x/lint/golint"
|
||||
_ "honnef.co/go/tools/cmd/staticcheck"
|
||||
)
|
||||
|
||||
27
vendor/github.com/alecthomas/gocyclo/LICENSE
generated
vendored
Normal file
27
vendor/github.com/alecthomas/gocyclo/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2013 Frederik Zipp. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of the copyright owner nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
31
vendor/github.com/alecthomas/gocyclo/README.md
generated
vendored
Normal file
31
vendor/github.com/alecthomas/gocyclo/README.md
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
Gocyclo calculates cyclomatic complexities of functions in Go source code.
|
||||
|
||||
The cyclomatic complexity of a function is calculated according to the
|
||||
following rules:
|
||||
|
||||
1 is the base complexity of a function
|
||||
+1 for each 'if', 'for', 'case', '&&' or '||'
|
||||
|
||||
To install, run
|
||||
|
||||
$ go get github.com/fzipp/gocyclo
|
||||
|
||||
and put the resulting binary in one of your PATH directories if
|
||||
`$GOPATH/bin` isn't already in your PATH.
|
||||
|
||||
Usage:
|
||||
|
||||
$ gocyclo [<flag> ...] <Go file or directory> ...
|
||||
|
||||
Examples:
|
||||
|
||||
$ gocyclo .
|
||||
$ gocyclo main.go
|
||||
$ gocyclo -top 10 src/
|
||||
$ gocyclo -over 25 docker
|
||||
$ gocyclo -avg .
|
||||
|
||||
The output fields for each line are:
|
||||
|
||||
<complexity> <package> <function> <file:row:column>
|
||||
|
||||
222
vendor/github.com/alecthomas/gocyclo/gocyclo.go
generated
vendored
Normal file
222
vendor/github.com/alecthomas/gocyclo/gocyclo.go
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright 2013 Frederik Zipp. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Gocyclo calculates the cyclomatic complexities of functions and
|
||||
// methods in Go source code.
|
||||
//
|
||||
// Usage:
|
||||
// gocyclo [<flag> ...] <Go file or directory> ...
|
||||
//
|
||||
// Flags
|
||||
// -over N show functions with complexity > N only and
|
||||
// return exit code 1 if the output is non-empty
|
||||
// -top N show the top N most complex functions only
|
||||
// -avg show the average complexity
|
||||
//
|
||||
// The output fields for each line are:
|
||||
// <complexity> <package> <function> <file:row:column>
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const usageDoc = `Calculate cyclomatic complexities of Go functions.
|
||||
usage:
|
||||
gocyclo [<flag> ...] <Go file or directory> ...
|
||||
|
||||
Flags
|
||||
-over N show functions with complexity > N only and
|
||||
return exit code 1 if the set is non-empty
|
||||
-top N show the top N most complex functions only
|
||||
-avg show the average complexity over all functions,
|
||||
not depending on whether -over or -top are set
|
||||
|
||||
The output fields for each line are:
|
||||
<complexity> <package> <function> <file:row:column>
|
||||
`
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintf(os.Stderr, usageDoc)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
var (
|
||||
over = flag.Int("over", 0, "show functions with complexity > N only")
|
||||
top = flag.Int("top", -1, "show the top N most complex functions only")
|
||||
avg = flag.Bool("avg", false, "show the average complexity")
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.Usage = usage
|
||||
flag.Parse()
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
usage()
|
||||
}
|
||||
|
||||
stats := analyze(args)
|
||||
sort.Sort(byComplexity(stats))
|
||||
written := writeStats(os.Stdout, stats)
|
||||
|
||||
if *avg {
|
||||
showAverage(stats)
|
||||
}
|
||||
|
||||
if *over > 0 && written > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func analyze(paths []string) []stat {
|
||||
stats := make([]stat, 0)
|
||||
for _, path := range paths {
|
||||
if isDir(path) {
|
||||
stats = analyzeDir(path, stats)
|
||||
} else {
|
||||
stats = analyzeFile(path, stats)
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
func isDir(filename string) bool {
|
||||
fi, err := os.Stat(filename)
|
||||
return err == nil && fi.IsDir()
|
||||
}
|
||||
|
||||
func analyzeFile(fname string, stats []stat) []stat {
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, fname, nil, 0)
|
||||
if err != nil {
|
||||
exitError(err)
|
||||
}
|
||||
return buildStats(f, fset, stats)
|
||||
}
|
||||
|
||||
func analyzeDir(dirname string, stats []stat) []stat {
|
||||
files, _ := filepath.Glob(filepath.Join(dirname, "*.go"))
|
||||
for _, file := range files {
|
||||
stats = analyzeFile(file, stats)
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
func exitError(err error) {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func writeStats(w io.Writer, sortedStats []stat) int {
|
||||
for i, stat := range sortedStats {
|
||||
if i == *top {
|
||||
return i
|
||||
}
|
||||
if stat.Complexity <= *over {
|
||||
return i
|
||||
}
|
||||
fmt.Fprintln(w, stat)
|
||||
}
|
||||
return len(sortedStats)
|
||||
}
|
||||
|
||||
func showAverage(stats []stat) {
|
||||
fmt.Printf("Average: %.3g\n", average(stats))
|
||||
}
|
||||
|
||||
func average(stats []stat) float64 {
|
||||
total := 0
|
||||
for _, s := range stats {
|
||||
total += s.Complexity
|
||||
}
|
||||
return float64(total) / float64(len(stats))
|
||||
}
|
||||
|
||||
type stat struct {
|
||||
PkgName string
|
||||
FuncName string
|
||||
Complexity int
|
||||
Pos token.Position
|
||||
}
|
||||
|
||||
func (s stat) String() string {
|
||||
return fmt.Sprintf("%d %s %s %s", s.Complexity, s.PkgName, s.FuncName, s.Pos)
|
||||
}
|
||||
|
||||
type byComplexity []stat
|
||||
|
||||
func (s byComplexity) Len() int { return len(s) }
|
||||
func (s byComplexity) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
func (s byComplexity) Less(i, j int) bool {
|
||||
return s[i].Complexity >= s[j].Complexity
|
||||
}
|
||||
|
||||
func buildStats(f *ast.File, fset *token.FileSet, stats []stat) []stat {
|
||||
for _, decl := range f.Decls {
|
||||
if fn, ok := decl.(*ast.FuncDecl); ok {
|
||||
stats = append(stats, stat{
|
||||
PkgName: f.Name.Name,
|
||||
FuncName: funcName(fn),
|
||||
Complexity: complexity(fn),
|
||||
Pos: fset.Position(fn.Pos()),
|
||||
})
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
// funcName returns the name representation of a function or method:
|
||||
// "(Type).Name" for methods or simply "Name" for functions.
|
||||
func funcName(fn *ast.FuncDecl) string {
|
||||
if fn.Recv != nil {
|
||||
typ := fn.Recv.List[0].Type
|
||||
return fmt.Sprintf("(%s).%s", recvString(typ), fn.Name)
|
||||
}
|
||||
return fn.Name.Name
|
||||
}
|
||||
|
||||
// recvString returns a string representation of recv of the
|
||||
// form "T", "*T", or "BADRECV" (if not a proper receiver type).
|
||||
func recvString(recv ast.Expr) string {
|
||||
switch t := recv.(type) {
|
||||
case *ast.Ident:
|
||||
return t.Name
|
||||
case *ast.StarExpr:
|
||||
return "*" + recvString(t.X)
|
||||
}
|
||||
return "BADRECV"
|
||||
}
|
||||
|
||||
// complexity calculates the cyclomatic complexity of a function.
|
||||
func complexity(fn *ast.FuncDecl) int {
|
||||
v := complexityVisitor{}
|
||||
ast.Walk(&v, fn)
|
||||
return v.Complexity
|
||||
}
|
||||
|
||||
type complexityVisitor struct {
|
||||
// Complexity is the cyclomatic complexity
|
||||
Complexity int
|
||||
}
|
||||
|
||||
// Visit implements the ast.Visitor interface.
|
||||
func (v *complexityVisitor) Visit(n ast.Node) ast.Visitor {
|
||||
switch n := n.(type) {
|
||||
case *ast.FuncDecl, *ast.IfStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause:
|
||||
v.Complexity++
|
||||
case *ast.BinaryExpr:
|
||||
if n.Op == token.LAND || n.Op == token.LOR {
|
||||
v.Complexity++
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
2
vendor/github.com/alecthomas/gometalinter/.gitignore
generated
vendored
Normal file
2
vendor/github.com/alecthomas/gometalinter/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
vendor/bin
|
||||
vendor/pkg
|
||||
7
vendor/github.com/alecthomas/gometalinter/.travis.yml
generated
vendored
Normal file
7
vendor/github.com/alecthomas/gometalinter/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
sudo: false
|
||||
language: go
|
||||
install:
|
||||
- go get -t -v . ./regressiontests
|
||||
- gometalinter --install
|
||||
go: 1.7
|
||||
script: go test -v . ./regressiontests
|
||||
45
vendor/github.com/alecthomas/gometalinter/CONTRIBUTING.md
generated
vendored
Normal file
45
vendor/github.com/alecthomas/gometalinter/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
### Please only report errors with gometalinter itself
|
||||
|
||||
gometalinter relies on underlying linters to detect issues in source code.
|
||||
If your issue seems to be related to an underlying linter, please report an
|
||||
issue against that linter rather than gometalinter. For a full list of linters
|
||||
and their repositories please see the [README](README.md).
|
||||
|
||||
### Before you report an issue
|
||||
|
||||
Sometimes gometalinter will not report issues that you think it should. There
|
||||
are three things to try in that case:
|
||||
|
||||
#### 1. Update to the latest build of gometalinter and all linters
|
||||
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
gometalinter --install
|
||||
|
||||
If you're lucky, this will fix the problem.
|
||||
|
||||
#### 2. Analyse the debug output
|
||||
|
||||
If that doesn't help, the problem may be elsewhere (in no particular order):
|
||||
|
||||
1. Upstream linter has changed its output or semantics.
|
||||
2. gometalinter is not invoking the tool correctly.
|
||||
3. gometalinter regular expression matches are not correct for a linter.
|
||||
4. Linter is exceeding the deadline.
|
||||
|
||||
To find out what's going on run in debug mode:
|
||||
|
||||
gometalinter --debug
|
||||
|
||||
This will show all output from the linters and should indicate why it is
|
||||
failing.
|
||||
|
||||
#### 3. Run linters manually
|
||||
|
||||
The output of `gometalinter --debug` should show the exact commands gometalinter
|
||||
is running. Run these commands from the command line to determine if the linter
|
||||
or gometaliner is at fault.
|
||||
|
||||
#### 4. Report an issue.
|
||||
|
||||
Failing all else, if the problem looks like a bug please file an issue and
|
||||
include the output of `gometalinter --debug`
|
||||
19
vendor/github.com/alecthomas/gometalinter/COPYING
generated
vendored
Normal file
19
vendor/github.com/alecthomas/gometalinter/COPYING
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2012 Alec Thomas
|
||||
|
||||
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.
|
||||
226
vendor/github.com/alecthomas/gometalinter/README.md
generated
vendored
Normal file
226
vendor/github.com/alecthomas/gometalinter/README.md
generated
vendored
Normal file
@@ -0,0 +1,226 @@
|
||||
# Go Meta Linter [](https://travis-ci.org/alecthomas/gometalinter)
|
||||
|
||||
The number of tools for statically checking Go source for errors and warnings
|
||||
is impressive.
|
||||
|
||||
This is a tool that concurrently runs a whole bunch of those linters and
|
||||
normalises their output to a standard format:
|
||||
|
||||
<file>:<line>:[<column>]: <message> (<linter>)
|
||||
|
||||
eg.
|
||||
|
||||
stutter.go:9::warning: unused global variable unusedGlobal (varcheck)
|
||||
stutter.go:12:6:warning: exported type MyStruct should have comment or be unexported (golint)
|
||||
|
||||
It is intended for use with editor/IDE integration.
|
||||
|
||||
## Editor integration
|
||||
|
||||
- [SublimeLinter plugin](https://github.com/alecthomas/SublimeLinter-contrib-gometalinter).
|
||||
- [vim-go](https://github.com/fatih/vim-go) with the `:GoMetaLinter` command.
|
||||
- [syntastic (vim)](https://github.com/scrooloose/syntastic/wiki/Go:---gometalinter) `let g:syntastic_go_checkers = ['gometalinter']`
|
||||
- [Atom linter plugin](https://atom.io/packages/gometalinter-linter).
|
||||
- [Emacs Flycheck checker](https://github.com/favadi/flycheck-gometalinter).
|
||||
- [Go for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=lukehoban.Go).
|
||||
|
||||
## Supported linters
|
||||
|
||||
- [go vet](https://golang.org/cmd/vet/) - Reports potential errors that otherwise compile.
|
||||
- [go vet --shadow](https://golang.org/cmd/vet/#hdr-Shadowed_variables) - Reports variables that may have been unintentionally shadowed.
|
||||
- [gotype](https://golang.org/x/tools/cmd/gotype) - Syntactic and semantic analysis similar to the Go compiler.
|
||||
- [deadcode](https://github.com/tsenart/deadcode) - Finds unused code.
|
||||
- [gocyclo](https://github.com/alecthomas/gocyclo) - Computes the cyclomatic complexity of functions.
|
||||
- [golint](https://github.com/golang/lint) - Google's (mostly stylistic) linter.
|
||||
- [varcheck](https://github.com/opennota/check) - Find unused global variables and constants.
|
||||
- [structcheck](https://github.com/opennota/check) - Find unused struct fields.
|
||||
- [aligncheck](https://github.com/opennota/check) - Warn about un-optimally aligned structures.
|
||||
- [errcheck](https://github.com/kisielk/errcheck) - Check that error return values are used.
|
||||
- [dupl](https://github.com/mibk/dupl) - Reports potentially duplicated code.
|
||||
- [ineffassign](https://github.com/gordonklaus/ineffassign/blob/master/list) - Detect when assignments to *existing* variables are not used.
|
||||
- [interfacer](https://github.com/mvdan/interfacer) - Suggest narrower interfaces that can be used.
|
||||
- [unconvert](https://github.com/mdempsky/unconvert) - Detect redundant type conversions.
|
||||
- [goconst](https://github.com/jgautheron/goconst) - Finds repeated strings that could be replaced by a constant.
|
||||
- [gosimple](https://github.com/dominikh/go-simple) - Report simplifications in code.
|
||||
- [staticcheck](https://github.com/dominikh/go-staticcheck) - Check inputs to functions for correctness
|
||||
|
||||
Disabled by default (enable with `--enable=<linter>`):
|
||||
|
||||
- [testify](https://github.com/stretchr/testify) - Show location of failed testify assertions.
|
||||
- [test](http://golang.org/pkg/testing/) - Show location of test failures from the stdlib testing module.
|
||||
- [gofmt -s](https://golang.org/cmd/gofmt/) - Checks if the code is properly formatted and could not be further simplified.
|
||||
- [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) - Checks missing or unreferenced package imports.
|
||||
- [lll](https://github.com/walle/lll) - Report long lines (see `--line-length=N`).
|
||||
- [misspell](https://github.com/client9/misspell) - Finds commonly misspelled English words.
|
||||
- [unused](https://github.com/dominikh/go-unused) - Find unused variables.
|
||||
|
||||
Additional linters can be added through the command line with `--linter=NAME:COMMAND:PATTERN` (see [below](#details)).
|
||||
|
||||
## Installing
|
||||
|
||||
There are two options for installing gometalinter.
|
||||
|
||||
1. Install a stable version, eg. `go get -u gopkg.in/alecthomas/gometalinter.v1`.
|
||||
I will generally only tag a new stable version when it has passed the Travis
|
||||
regression tests. The downside is that the binary will be called `gometalinter.v1`.
|
||||
2. Install from HEAD with: `go get -u github.com/alecthomas/gometalinter`.
|
||||
This has the downside that changes to gometalinter may break.
|
||||
|
||||
## Quickstart
|
||||
|
||||
Install gometalinter:
|
||||
|
||||
```
|
||||
$ go get -u github.com/alecthomas/gometalinter
|
||||
```
|
||||
|
||||
Install all known linters:
|
||||
|
||||
```
|
||||
$ gometalinter --install
|
||||
Installing:
|
||||
structcheck
|
||||
aligncheck
|
||||
deadcode
|
||||
gocyclo
|
||||
ineffassign
|
||||
dupl
|
||||
golint
|
||||
gotype
|
||||
goimports
|
||||
errcheck
|
||||
varcheck
|
||||
interfacer
|
||||
goconst
|
||||
gosimple
|
||||
staticcheck
|
||||
unused
|
||||
misspell
|
||||
lll
|
||||
```
|
||||
|
||||
Run it:
|
||||
|
||||
```
|
||||
$ cd example
|
||||
$ gometalinter ./...
|
||||
stutter.go:13::warning: unused struct field MyStruct.Unused (structcheck)
|
||||
stutter.go:9::warning: unused global variable unusedGlobal (varcheck)
|
||||
stutter.go:12:6:warning: exported type MyStruct should have comment or be unexported (golint)
|
||||
stutter.go:16:6:warning: exported type PublicUndocumented should have comment or be unexported (golint)
|
||||
stutter.go:8:1:warning: unusedGlobal is unused (deadcode)
|
||||
stutter.go:12:1:warning: MyStruct is unused (deadcode)
|
||||
stutter.go:16:1:warning: PublicUndocumented is unused (deadcode)
|
||||
stutter.go:20:1:warning: duplicateDefer is unused (deadcode)
|
||||
stutter.go:21:15:warning: error return value not checked (defer a.Close()) (errcheck)
|
||||
stutter.go:22:15:warning: error return value not checked (defer a.Close()) (errcheck)
|
||||
stutter.go:27:6:warning: error return value not checked (doit() // test for errcheck) (errcheck)
|
||||
stutter.go:29::error: unreachable code (vet)
|
||||
stutter.go:26::error: missing argument for Printf("%d"): format reads arg 1, have only 0 args (vet)
|
||||
```
|
||||
|
||||
|
||||
Gometalinter also supports the commonly seen `<path>/...` recursive path
|
||||
format. Note that this can be *very* slow, and you may need to increase the linter `--deadline` to allow linters to complete.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Configuration file
|
||||
|
||||
gometalinter can load flags from a file with the syntax `@<file>`.
|
||||
|
||||
### Exit status
|
||||
|
||||
gometalinter sets two bits of the exit status to indicate different issues:
|
||||
|
||||
| Bit | Meaning
|
||||
|-----|----------
|
||||
| 0 | A linter generated an issue.
|
||||
| 1 | An underlying error occurred; eg. a linter failed to execute. In this situation a warning will also be displayed.
|
||||
|
||||
eg. linter only = 1, underlying only = 2, linter + underlying = 3
|
||||
|
||||
### What's the best way to use `gometalinter` in CI?
|
||||
|
||||
There are two main problems running in a CI:
|
||||
|
||||
1. <s>Linters break, causing `gometalinter --install --update` to error</s> (this is no longer an issue as all linters are vendored).
|
||||
2. `gometalinter` adds a new linter.
|
||||
|
||||
There is no great solution to 1, but for 2, the best option is to disable all linters, then explicitly enable the ones you want:
|
||||
|
||||
gometalinter --disable-all --enable=errcheck --enable=vet --enable=vetshadow ...
|
||||
|
||||
### How do I make `gometalinter` work with Go 1.5 vendoring?
|
||||
|
||||
`gometalinter` has a `--vendor` flag that just sets `GO15VENDOREXPERIMENT=1`, however the
|
||||
underlying tools must support it. Ensure that all of the linters are up to date and built with Go 1.5
|
||||
(`gometalinter --install --force`) then run `gometalinter --vendor .`. That should be it.
|
||||
|
||||
### Why does `gometalinter --install` install a fork of gocyclo?
|
||||
|
||||
I forked `gocyclo` because the upstream behaviour is to recursively check all
|
||||
subdirectories even when just a single directory is specified. This made it
|
||||
unusably slow when vendoring. The recursive behaviour can be achieved with
|
||||
gometalinter by explicitly specifying `<path>/...`. There is a
|
||||
[pull request](https://github.com/fzipp/gocyclo/pull/1) open.
|
||||
|
||||
### Gometalinter is not working
|
||||
|
||||
That's more of a statement than a question, but okay.
|
||||
|
||||
Sometimes gometalinter will not report issues that you think it should. There
|
||||
are three things to try in that case:
|
||||
|
||||
#### 1. Update to the latest build of gometalinter and all linters
|
||||
|
||||
go get -u github.com/alecthomas/gometalinter
|
||||
gometalinter --install
|
||||
|
||||
If you're lucky, this will fix the problem.
|
||||
|
||||
#### 2. Analyse the debug output
|
||||
|
||||
If that doesn't help, the problem may be elsewhere (in no particular order):
|
||||
|
||||
1. Upstream linter has changed its output or semantics.
|
||||
2. gometalinter is not invoking the tool correctly.
|
||||
3. gometalinter regular expression matches are not correct for a linter.
|
||||
4. Linter is exceeding the deadline.
|
||||
|
||||
To find out what's going on run in debug mode:
|
||||
|
||||
gometalinter --debug
|
||||
|
||||
This will show all output from the linters and should indicate why it is
|
||||
failing.
|
||||
|
||||
#### 3. Report an issue.
|
||||
|
||||
Failing all else, if the problem looks like a bug please file an issue and
|
||||
include the output of `gometalinter --debug`.
|
||||
|
||||
## Details
|
||||
|
||||
Additional linters can be configured via the command line:
|
||||
|
||||
```
|
||||
$ gometalinter --linter='vet:go tool vet -printfuncs=Infof,Debugf,Warningf,Errorf {path}:PATH:LINE:MESSAGE' .
|
||||
stutter.go:21:15:warning: error return value not checked (defer a.Close()) (errcheck)
|
||||
stutter.go:22:15:warning: error return value not checked (defer a.Close()) (errcheck)
|
||||
stutter.go:27:6:warning: error return value not checked (doit() // test for errcheck) (errcheck)
|
||||
stutter.go:9::warning: unused global variable unusedGlobal (varcheck)
|
||||
stutter.go:13::warning: unused struct field MyStruct.Unused (structcheck)
|
||||
stutter.go:12:6:warning: exported type MyStruct should have comment or be unexported (golint)
|
||||
stutter.go:16:6:warning: exported type PublicUndocumented should have comment or be unexported (deadcode)
|
||||
```
|
||||
|
||||
## Checkstyle XML format
|
||||
|
||||
`gometalinter` supports [checkstyle](http://checkstyle.sourceforge.net/)
|
||||
compatible XML output format. It is tiggered with `--checkstyle` flag:
|
||||
|
||||
gometalinter --checkstyle
|
||||
|
||||
Checkstyle format can be used to integrate gometalinter with Jenkins CI with the
|
||||
help of [Checkstyle Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Checkstyle+Plugin).
|
||||
61
vendor/github.com/alecthomas/gometalinter/checkstyle.go
generated
vendored
Normal file
61
vendor/github.com/alecthomas/gometalinter/checkstyle.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
type checkstyleOutput struct {
|
||||
XMLName xml.Name `xml:"checkstyle"`
|
||||
Version string `xml:"version,attr"`
|
||||
Files []*checkstyleFile `xml:"file"`
|
||||
}
|
||||
|
||||
type checkstyleFile struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Errors []*checkstyleError `xml:"error"`
|
||||
}
|
||||
|
||||
type checkstyleError struct {
|
||||
Column int `xml:"column,attr"`
|
||||
Line int `xml:"line,attr"`
|
||||
Message string `xml:"message,attr"`
|
||||
Severity string `xml:"severity,attr"`
|
||||
Source string `xml:"source,attr"`
|
||||
}
|
||||
|
||||
func outputToCheckstyle(issues chan *Issue) int {
|
||||
var lastFile *checkstyleFile
|
||||
out := checkstyleOutput{
|
||||
Version: "5.0",
|
||||
}
|
||||
status := 0
|
||||
for issue := range issues {
|
||||
if lastFile != nil && lastFile.Name != issue.Path {
|
||||
out.Files = append(out.Files, lastFile)
|
||||
lastFile = nil
|
||||
}
|
||||
if lastFile == nil {
|
||||
lastFile = &checkstyleFile{
|
||||
Name: issue.Path,
|
||||
}
|
||||
}
|
||||
lastFile.Errors = append(lastFile.Errors, &checkstyleError{
|
||||
Column: issue.Col,
|
||||
Line: issue.Line,
|
||||
Message: issue.Message,
|
||||
Severity: string(issue.Severity),
|
||||
Source: issue.Linter.Name,
|
||||
})
|
||||
status = 1
|
||||
}
|
||||
if lastFile != nil {
|
||||
out.Files = append(out.Files, lastFile)
|
||||
}
|
||||
d, err := xml.Marshal(&out)
|
||||
kingpin.FatalIfError(err, "")
|
||||
fmt.Printf("%s%s\n", xml.Header, d)
|
||||
return status
|
||||
}
|
||||
875
vendor/github.com/alecthomas/gometalinter/main.go
generated
vendored
Normal file
875
vendor/github.com/alecthomas/gometalinter/main.go
generated
vendored
Normal file
@@ -0,0 +1,875 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/shlex"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
type Severity string
|
||||
|
||||
// Linter message severity levels.
|
||||
const (
|
||||
Warning Severity = "warning"
|
||||
Error Severity = "error"
|
||||
)
|
||||
|
||||
type Linter struct {
|
||||
Name string `json:"name"`
|
||||
Command string `json:"command"`
|
||||
CompositeCommand string `json:"composite_command,omitempty"`
|
||||
Pattern string `json:"pattern"`
|
||||
InstallFrom string `json:"install_from"`
|
||||
SeverityOverride Severity `json:"severity,omitempty"`
|
||||
MessageOverride string `json:"message_override,omitempty"`
|
||||
|
||||
regex *regexp.Regexp
|
||||
}
|
||||
|
||||
func (l *Linter) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(l.Name)
|
||||
}
|
||||
|
||||
func (l *Linter) String() string {
|
||||
return l.Name
|
||||
}
|
||||
|
||||
func LinterFromName(name string) *Linter {
|
||||
s := lintersFlag[name]
|
||||
parts := strings.SplitN(s, ":", 2)
|
||||
pattern := parts[1]
|
||||
if p, ok := predefinedPatterns[pattern]; ok {
|
||||
pattern = p
|
||||
}
|
||||
re, err := regexp.Compile("(?m:" + pattern + ")")
|
||||
kingpin.FatalIfError(err, "invalid regex for %q", name)
|
||||
return &Linter{
|
||||
Name: name,
|
||||
Command: s[0:strings.Index(s, ":")],
|
||||
Pattern: pattern,
|
||||
InstallFrom: installMap[name],
|
||||
SeverityOverride: Severity(linterSeverityFlag[name]),
|
||||
MessageOverride: linterMessageOverrideFlag[name],
|
||||
regex: re,
|
||||
}
|
||||
}
|
||||
|
||||
type sortedIssues struct {
|
||||
issues []*Issue
|
||||
order []string
|
||||
}
|
||||
|
||||
func (s *sortedIssues) Len() int { return len(s.issues) }
|
||||
func (s *sortedIssues) Swap(i, j int) { s.issues[i], s.issues[j] = s.issues[j], s.issues[i] }
|
||||
func (s *sortedIssues) Less(i, j int) bool {
|
||||
l, r := s.issues[i], s.issues[j]
|
||||
for _, key := range s.order {
|
||||
switch key {
|
||||
case "path":
|
||||
if l.Path >= r.Path {
|
||||
return false
|
||||
}
|
||||
case "line":
|
||||
if l.Line >= r.Line {
|
||||
return false
|
||||
}
|
||||
case "column":
|
||||
if l.Col >= r.Col {
|
||||
return false
|
||||
}
|
||||
case "severity":
|
||||
if l.Severity >= r.Severity {
|
||||
return false
|
||||
}
|
||||
case "message":
|
||||
if l.Message >= r.Message {
|
||||
return false
|
||||
}
|
||||
case "linter":
|
||||
if l.Linter.Name >= r.Linter.Name {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
// Locations to look for vendored linters.
|
||||
vendoredSearchPaths = [][]string{
|
||||
{"github.com", "alecthomas", "gometalinter", "vendor"},
|
||||
{"gopkg.in", "alecthomas", "gometalinter.v1", "vendor"},
|
||||
}
|
||||
predefinedPatterns = map[string]string{
|
||||
"PATH:LINE:COL:MESSAGE": `^(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`,
|
||||
"PATH:LINE:MESSAGE": `^(?P<path>.*?\.go):(?P<line>\d+):\s*(?P<message>.*)$`,
|
||||
}
|
||||
lintersFlag = map[string]string{
|
||||
"aligncheck": `aligncheck {path}:^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.+)$`,
|
||||
"deadcode": `deadcode {path}:^deadcode: (?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`,
|
||||
"dupl": `dupl -plumbing -threshold {duplthreshold} {path}/*.go:^(?P<path>.*?\.go):(?P<line>\d+)-\d+:\s*(?P<message>.*)$`,
|
||||
"errcheck": `errcheck -abspath {path}:PATH:LINE:COL:MESSAGE`,
|
||||
"gas": `gas -fmt=csv {path}/*.go:^(?P<path>.*?\.go),(?P<line>\d+),(?P<message>[^,]+,[^,]+,[^,]+)`,
|
||||
"goconst": `goconst -min-occurrences {min_occurrences} -min-length {min_const_length} {path}:PATH:LINE:COL:MESSAGE`,
|
||||
"gocyclo": `gocyclo -over {mincyclo} {path}:^(?P<cyclo>\d+)\s+\S+\s(?P<function>\S+)\s+(?P<path>.*?\.go):(?P<line>\d+):(\d+)$`,
|
||||
"gofmt": `gofmt -l -s {path}/*.go:^(?P<path>.*?\.go)$`,
|
||||
"goimports": `goimports -l {path}/*.go:^(?P<path>.*?\.go)$`,
|
||||
"golint": "golint -min_confidence {min_confidence} {path}:PATH:LINE:COL:MESSAGE",
|
||||
"gosimple": "gosimple {path}:PATH:LINE:COL:MESSAGE",
|
||||
"gotype": "gotype -e {tests=-a} {path}:PATH:LINE:COL:MESSAGE",
|
||||
"ineffassign": `ineffassign -n {path}:PATH:LINE:COL:MESSAGE`,
|
||||
"interfacer": `interfacer {path}:PATH:LINE:COL:MESSAGE`,
|
||||
"lll": `lll -g -l {maxlinelength} {path}/*.go:PATH:LINE:MESSAGE`,
|
||||
"misspell": "misspell -j 1 {path}/*.go:PATH:LINE:COL:MESSAGE",
|
||||
"staticcheck": "staticcheck {path}:PATH:LINE:COL:MESSAGE",
|
||||
"structcheck": `structcheck {tests=-t} {path}:^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.+)$`,
|
||||
"test": `go test {path}:^--- FAIL: .*$\s+(?P<path>.*?\.go):(?P<line>\d+): (?P<message>.*)$`,
|
||||
"testify": `go test {path}:Location:\s+(?P<path>.*?\.go):(?P<line>\d+)$\s+Error:\s+(?P<message>[^\n]+)`,
|
||||
"unconvert": "unconvert {path}:PATH:LINE:COL:MESSAGE",
|
||||
"unused": `unused {path}:PATH:LINE:COL:MESSAGE`,
|
||||
"varcheck": `varcheck {path}:^(?:[^:]+: )?(?P<path>.*?\.go):(?P<line>\d+):(?P<col>\d+):\s*(?P<message>.*)$`,
|
||||
"vet": "go tool vet {path}/*.go:PATH:LINE:MESSAGE",
|
||||
"vetshadow": "go tool vet --shadow {path}/*.go:PATH:LINE:MESSAGE",
|
||||
}
|
||||
disabledLinters = []string{"testify", "test", "gofmt", "goimports", "lll", "misspell", "unused"}
|
||||
enabledLinters = []string{}
|
||||
linterMessageOverrideFlag = map[string]string{
|
||||
"errcheck": "error return value not checked ({message})",
|
||||
"gocyclo": "cyclomatic complexity {cyclo} of function {function}() is high (> {mincyclo})",
|
||||
"gofmt": "file is not gofmted with -s",
|
||||
"goimports": "file is not goimported",
|
||||
"structcheck": "unused struct field {message}",
|
||||
"varcheck": "unused global variable {message}",
|
||||
}
|
||||
linterSeverityFlag = map[string]string{
|
||||
"gotype": "error",
|
||||
"test": "error",
|
||||
"testify": "error",
|
||||
"vet": "error",
|
||||
}
|
||||
installMap = map[string]string{
|
||||
"aligncheck": "github.com/opennota/check/cmd/aligncheck",
|
||||
"deadcode": "github.com/tsenart/deadcode",
|
||||
"dupl": "github.com/mibk/dupl",
|
||||
"errcheck": "github.com/kisielk/errcheck",
|
||||
"gas": "github.com/HewlettPackard/gas",
|
||||
"goconst": "github.com/jgautheron/goconst/cmd/goconst",
|
||||
"gocyclo": "github.com/alecthomas/gocyclo",
|
||||
"goimports": "golang.org/x/tools/cmd/goimports",
|
||||
"golint": "github.com/golang/lint/golint",
|
||||
"gosimple": "honnef.co/go/simple/cmd/gosimple",
|
||||
"gotype": "golang.org/x/tools/cmd/gotype",
|
||||
"ineffassign": "github.com/gordonklaus/ineffassign",
|
||||
"interfacer": "github.com/mvdan/interfacer/cmd/interfacer",
|
||||
"lll": "github.com/walle/lll/cmd/lll",
|
||||
"misspell": "github.com/client9/misspell/cmd/misspell",
|
||||
"staticcheck": "honnef.co/go/staticcheck/cmd/staticcheck",
|
||||
"structcheck": "github.com/opennota/check/cmd/structcheck",
|
||||
"unconvert": "github.com/mdempsky/unconvert",
|
||||
"unused": "honnef.co/go/unused/cmd/unused",
|
||||
"varcheck": "github.com/opennota/check/cmd/varcheck",
|
||||
}
|
||||
acceptsEllipsis = map[string]bool{
|
||||
"aligncheck": true,
|
||||
"errcheck": true,
|
||||
"golint": true,
|
||||
"gosimple": true,
|
||||
"interfacer": true,
|
||||
"staticcheck": true,
|
||||
"structcheck": true,
|
||||
"test": true,
|
||||
"varcheck": true,
|
||||
"unconvert": true,
|
||||
}
|
||||
slowLinters = []string{"structcheck", "varcheck", "errcheck", "aligncheck", "testify", "test", "interfacer", "unconvert", "deadcode"}
|
||||
sortKeys = []string{"none", "path", "line", "column", "severity", "message", "linter"}
|
||||
|
||||
pathsArg = kingpin.Arg("path", "Directory to lint. Defaults to \".\". <path>/... will recurse.").Strings()
|
||||
vendoredLintersFlag = kingpin.Flag("vendored-linters", "Use vendored linters (recommended).").Default("true").Bool()
|
||||
fastFlag = kingpin.Flag("fast", "Only run fast linters.").Bool()
|
||||
installFlag = kingpin.Flag("install", "Attempt to install all known linters.").Short('i').Bool()
|
||||
updateFlag = kingpin.Flag("update", "Pass -u to go tool when installing.").Short('u').Bool()
|
||||
forceFlag = kingpin.Flag("force", "Pass -f to go tool when installing.").Short('f').Bool()
|
||||
debugFlag = kingpin.Flag("debug", "Display messages for failed linters, etc.").Short('d').Bool()
|
||||
concurrencyFlag = kingpin.Flag("concurrency", "Number of concurrent linters to run.").Default("16").Short('j').Int()
|
||||
excludeFlag = kingpin.Flag("exclude", "Exclude messages matching these regular expressions.").Short('e').PlaceHolder("REGEXP").Strings()
|
||||
includeFlag = kingpin.Flag("include", "Include messages matching these regular expressions.").Short('I').PlaceHolder("REGEXP").Strings()
|
||||
skipFlag = kingpin.Flag("skip", "Skip directories with this name when expanding '...'.").Short('s').PlaceHolder("DIR...").Strings()
|
||||
vendorFlag = kingpin.Flag("vendor", "Enable vendoring support (skips 'vendor' directories and sets GO15VENDOREXPERIMENT=1).").Bool()
|
||||
cycloFlag = kingpin.Flag("cyclo-over", "Report functions with cyclomatic complexity over N (using gocyclo).").Default("10").Int()
|
||||
lineLengthFlag = kingpin.Flag("line-length", "Report lines longer than N (using lll).").Default("80").Int()
|
||||
minConfidence = kingpin.Flag("min-confidence", "Minimum confidence interval to pass to golint.").Default(".80").Float()
|
||||
minOccurrences = kingpin.Flag("min-occurrences", "Minimum occurrences to pass to goconst.").Default("3").Int()
|
||||
minConstLength = kingpin.Flag("min-const-length", "Minimumum constant length.").Default("3").Int()
|
||||
duplThresholdFlag = kingpin.Flag("dupl-threshold", "Minimum token sequence as a clone for dupl.").Default("50").Int()
|
||||
sortFlag = kingpin.Flag("sort", fmt.Sprintf("Sort output by any of %s.", strings.Join(sortKeys, ", "))).Default("none").Enums(sortKeys...)
|
||||
testFlag = kingpin.Flag("tests", "Include test files for linters that support this option").Short('t').Bool()
|
||||
deadlineFlag = kingpin.Flag("deadline", "Cancel linters if they have not completed within this duration.").Default("5s").Duration()
|
||||
errorsFlag = kingpin.Flag("errors", "Only show errors.").Bool()
|
||||
jsonFlag = kingpin.Flag("json", "Generate structured JSON rather than standard line-based output.").Bool()
|
||||
checkstyleFlag = kingpin.Flag("checkstyle", "Generate checkstyle XML rather than standard line-based output.").Bool()
|
||||
enableGCFlag = kingpin.Flag("enable-gc", "Enable GC for linters (useful on large repositories).").Bool()
|
||||
)
|
||||
|
||||
func disableAllLinters(*kingpin.ParseContext) error {
|
||||
disabledLinters = []string{}
|
||||
for linter := range lintersFlag {
|
||||
disabledLinters = append(disabledLinters, linter)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func enableAllLinters(*kingpin.ParseContext) error {
|
||||
disabledLinters = []string{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
kingpin.Flag("disable", fmt.Sprintf("List of linters to disable (%s).", strings.Join(disabledLinters, ","))).PlaceHolder("LINTER").Short('D').StringsVar(&disabledLinters)
|
||||
kingpin.Flag("enable", "Enable previously disabled linters.").PlaceHolder("LINTER").Short('E').StringsVar(&enabledLinters)
|
||||
kingpin.Flag("linter", "Specify a linter.").PlaceHolder("NAME:COMMAND:PATTERN").StringMapVar(&lintersFlag)
|
||||
kingpin.Flag("message-overrides", "Override message from linter. {message} will be expanded to the original message.").PlaceHolder("LINTER:MESSAGE").StringMapVar(&linterMessageOverrideFlag)
|
||||
kingpin.Flag("severity", "Map of linter severities.").PlaceHolder("LINTER:SEVERITY").StringMapVar(&linterSeverityFlag)
|
||||
kingpin.Flag("disable-all", "Disable all linters.").Action(disableAllLinters).Bool()
|
||||
kingpin.Flag("enable-all", "Enable all linters.").Action(enableAllLinters).Bool()
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
Linter *Linter `json:"linter"`
|
||||
Severity Severity `json:"severity"`
|
||||
Path string `json:"path"`
|
||||
Line int `json:"line"`
|
||||
Col int `json:"col"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
func (i *Issue) String() string {
|
||||
col := ""
|
||||
if i.Col != 0 {
|
||||
col = fmt.Sprintf("%d", i.Col)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d:%s:%s: %s (%s)", strings.TrimSpace(i.Path), i.Line, col, i.Severity, strings.TrimSpace(i.Message), i.Linter)
|
||||
}
|
||||
|
||||
func debug(format string, args ...interface{}) {
|
||||
if *debugFlag {
|
||||
fmt.Fprintf(os.Stderr, "DEBUG: "+format+"\n", args...)
|
||||
}
|
||||
}
|
||||
|
||||
func warning(format string, args ...interface{}) {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: "+format+"\n", args...)
|
||||
}
|
||||
|
||||
func formatLinters() string {
|
||||
w := bytes.NewBuffer(nil)
|
||||
for name := range lintersFlag {
|
||||
linter := LinterFromName(name)
|
||||
install := "(" + linter.InstallFrom + ")"
|
||||
if install == "()" {
|
||||
install = ""
|
||||
}
|
||||
fmt.Fprintf(w, " %s %s\n %s\n %s\n", name, install, linter.Command, linter.Pattern)
|
||||
}
|
||||
return w.String()
|
||||
}
|
||||
|
||||
func formatSeverity() string {
|
||||
w := bytes.NewBuffer(nil)
|
||||
for name, severity := range linterSeverityFlag {
|
||||
fmt.Fprintf(w, " %s -> %s\n", name, severity)
|
||||
}
|
||||
return w.String()
|
||||
}
|
||||
|
||||
type Vars map[string]string
|
||||
|
||||
func (v Vars) Copy() Vars {
|
||||
out := Vars{}
|
||||
for k, v := range v {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (v Vars) Replace(s string) string {
|
||||
for k, v := range v {
|
||||
prefix := regexp.MustCompile(fmt.Sprintf("{%s=([^}]*)}", k))
|
||||
if v != "" {
|
||||
s = prefix.ReplaceAllString(s, "$1")
|
||||
} else {
|
||||
s = prefix.ReplaceAllString(s, "")
|
||||
}
|
||||
s = strings.Replace(s, fmt.Sprintf("{%s}", k), v, -1)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Linters are by their very nature, short lived, so disable GC.
|
||||
// Reduced (user) linting time on kingpin from 0.97s to 0.64s.
|
||||
kingpin.CommandLine.Help = fmt.Sprintf(`Aggregate and normalise the output of a whole bunch of Go linters.
|
||||
|
||||
Default linters:
|
||||
|
||||
%s
|
||||
|
||||
Severity override map (default is "warning"):
|
||||
|
||||
%s
|
||||
`, formatLinters(), formatSeverity())
|
||||
kingpin.Parse()
|
||||
if !*enableGCFlag {
|
||||
_ = os.Setenv("GOGC", "off")
|
||||
}
|
||||
if *vendoredLintersFlag && *installFlag && *updateFlag {
|
||||
warning(`Linters are now vendored by default, --update ignored. The original
|
||||
behaviour can be re-enabled with --no-vendored-linters.
|
||||
|
||||
To request an update for a vendored linter file an issue at:
|
||||
https://github.com/alecthomas/gometalinter/issues/new
|
||||
`)
|
||||
*updateFlag = false
|
||||
}
|
||||
// Force sorting by path if checkstyle mode is selected
|
||||
// !jsonFlag check is required to handle:
|
||||
// gometalinter --json --checkstyle --sort=severity
|
||||
if *checkstyleFlag && !*jsonFlag {
|
||||
*sortFlag = []string{"path"}
|
||||
}
|
||||
|
||||
configureEnvironment()
|
||||
// Default to skipping "vendor" directory if GO15VENDOREXPERIMENT=1 is enabled.
|
||||
// TODO(alec): This will probably need to be enabled by default at a later time.
|
||||
if os.Getenv("GO15VENDOREXPERIMENT") == "1" || *vendorFlag {
|
||||
os.Setenv("GO15VENDOREXPERIMENT", "1")
|
||||
*skipFlag = append(*skipFlag, "vendor")
|
||||
*vendorFlag = true
|
||||
}
|
||||
var exclude *regexp.Regexp
|
||||
if len(*excludeFlag) > 0 {
|
||||
exclude = regexp.MustCompile(strings.Join(*excludeFlag, "|"))
|
||||
}
|
||||
|
||||
var include *regexp.Regexp
|
||||
if len(*includeFlag) > 0 {
|
||||
include = regexp.MustCompile(strings.Join(*includeFlag, "|"))
|
||||
}
|
||||
|
||||
if *installFlag {
|
||||
installLinters()
|
||||
return
|
||||
}
|
||||
|
||||
runtime.GOMAXPROCS(*concurrencyFlag)
|
||||
|
||||
start := time.Now()
|
||||
paths := expandPaths(*pathsArg, *skipFlag)
|
||||
|
||||
linters := lintersFromFlags()
|
||||
status := 0
|
||||
issues, errch := runLinters(linters, paths, *pathsArg, *concurrencyFlag, exclude, include)
|
||||
if *jsonFlag {
|
||||
status |= outputToJSON(issues)
|
||||
} else if *checkstyleFlag {
|
||||
status |= outputToCheckstyle(issues)
|
||||
} else {
|
||||
status |= outputToConsole(issues)
|
||||
}
|
||||
for err := range errch {
|
||||
warning("%s", err)
|
||||
status |= 2
|
||||
}
|
||||
elapsed := time.Since(start)
|
||||
debug("total elapsed time %s", elapsed)
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
func outputToConsole(issues chan *Issue) int {
|
||||
status := 0
|
||||
for issue := range issues {
|
||||
if *errorsFlag && issue.Severity != Error {
|
||||
continue
|
||||
}
|
||||
fmt.Println(issue.String())
|
||||
status = 1
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func outputToJSON(issues chan *Issue) int {
|
||||
fmt.Println("[")
|
||||
status := 0
|
||||
for issue := range issues {
|
||||
if *errorsFlag && issue.Severity != Error {
|
||||
continue
|
||||
}
|
||||
if status != 0 {
|
||||
fmt.Printf(",\n")
|
||||
}
|
||||
d, err := json.Marshal(issue)
|
||||
kingpin.FatalIfError(err, "")
|
||||
fmt.Printf(" %s", d)
|
||||
status = 1
|
||||
}
|
||||
fmt.Printf("\n]\n")
|
||||
return status
|
||||
}
|
||||
|
||||
func runLinters(linters map[string]*Linter, paths, ellipsisPaths []string, concurrency int, exclude *regexp.Regexp, include *regexp.Regexp) (chan *Issue, chan error) {
|
||||
errch := make(chan error, len(linters)*(len(paths)+len(ellipsisPaths)))
|
||||
concurrencych := make(chan bool, *concurrencyFlag)
|
||||
incomingIssues := make(chan *Issue, 1000000)
|
||||
processedIssues := maybeSortIssues(incomingIssues)
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, linter := range linters {
|
||||
// Recreated in each loop because it is mutated by executeLinter().
|
||||
vars := Vars{
|
||||
"duplthreshold": fmt.Sprintf("%d", *duplThresholdFlag),
|
||||
"mincyclo": fmt.Sprintf("%d", *cycloFlag),
|
||||
"maxlinelength": fmt.Sprintf("%d", *lineLengthFlag),
|
||||
"min_confidence": fmt.Sprintf("%f", *minConfidence),
|
||||
"min_occurrences": fmt.Sprintf("%d", *minOccurrences),
|
||||
"min_const_length": fmt.Sprintf("%d", *minConstLength),
|
||||
"tests": "",
|
||||
}
|
||||
if *testFlag {
|
||||
vars["tests"] = "-t"
|
||||
}
|
||||
linterPaths := paths
|
||||
// Most linters don't exclude vendor paths when recursing, so we don't use ... paths.
|
||||
if acceptsEllipsis[linter.Name] && !*vendorFlag && len(ellipsisPaths) > 0 {
|
||||
linterPaths = ellipsisPaths
|
||||
}
|
||||
for _, path := range linterPaths {
|
||||
wg.Add(1)
|
||||
deadline := time.After(*deadlineFlag)
|
||||
state := &linterState{
|
||||
Linter: linter,
|
||||
issues: incomingIssues,
|
||||
path: path,
|
||||
vars: vars.Copy(),
|
||||
exclude: exclude,
|
||||
include: include,
|
||||
deadline: deadline,
|
||||
}
|
||||
go func() {
|
||||
concurrencych <- true
|
||||
err := executeLinter(state)
|
||||
if err != nil {
|
||||
errch <- err
|
||||
}
|
||||
<-concurrencych
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(incomingIssues)
|
||||
close(errch)
|
||||
}()
|
||||
return processedIssues, errch
|
||||
}
|
||||
|
||||
func expandPaths(paths, skip []string) []string {
|
||||
if len(paths) == 0 {
|
||||
paths = []string{"."}
|
||||
}
|
||||
skipMap := map[string]bool{}
|
||||
for _, name := range skip {
|
||||
skipMap[name] = true
|
||||
}
|
||||
dirs := map[string]bool{}
|
||||
for _, path := range paths {
|
||||
if strings.HasSuffix(path, "/...") {
|
||||
root := filepath.Dir(path)
|
||||
_ = filepath.Walk(root, func(p string, i os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
warning("invalid path %q: %s", p, err)
|
||||
return err
|
||||
}
|
||||
|
||||
base := filepath.Base(p)
|
||||
skip := skipMap[base] || skipMap[p] || (strings.ContainsAny(base[0:1], "_.") && base != "." && base != "..")
|
||||
if i.IsDir() {
|
||||
if skip {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
} else if !skip && strings.HasSuffix(p, ".go") {
|
||||
dirs[filepath.Clean(filepath.Dir(p))] = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
dirs[filepath.Clean(path)] = true
|
||||
}
|
||||
}
|
||||
out := make([]string, 0, len(dirs))
|
||||
for d := range dirs {
|
||||
out = append(out, d)
|
||||
}
|
||||
sort.Strings(out)
|
||||
for _, d := range out {
|
||||
debug("linting path %s", d)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func makeInstallCommand(linters ...string) []string {
|
||||
cmd := []string{"get"}
|
||||
if *vendoredLintersFlag {
|
||||
cmd = []string{"install"}
|
||||
}
|
||||
if *debugFlag {
|
||||
cmd = append(cmd, "-v")
|
||||
}
|
||||
if *updateFlag {
|
||||
cmd = append(cmd, "-u")
|
||||
}
|
||||
if *forceFlag {
|
||||
cmd = append(cmd, "-f")
|
||||
}
|
||||
cmd = append(cmd, linters...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func installLintersWithOneCommand(targets []string) error {
|
||||
cmd := makeInstallCommand(targets...)
|
||||
debug("go %s", strings.Join(cmd, " "))
|
||||
c := exec.Command("go", cmd...)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
return c.Run()
|
||||
}
|
||||
|
||||
func installLintersIndividually(targets []string) {
|
||||
failed := []string{}
|
||||
for _, target := range targets {
|
||||
cmd := makeInstallCommand(target)
|
||||
debug("go %s", strings.Join(cmd, " "))
|
||||
c := exec.Command("go", cmd...)
|
||||
c.Stdout = os.Stdout
|
||||
c.Stderr = os.Stderr
|
||||
if err := c.Run(); err != nil {
|
||||
warning("failed to install %s: %s", target, err)
|
||||
failed = append(failed, target)
|
||||
}
|
||||
}
|
||||
if len(failed) > 0 {
|
||||
kingpin.Fatalf("failed to install the following linters: %s", strings.Join(failed, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
func installLinters() {
|
||||
names := make([]string, 0, len(installMap))
|
||||
targets := make([]string, 0, len(installMap))
|
||||
for name, target := range installMap {
|
||||
names = append(names, name)
|
||||
targets = append(targets, target)
|
||||
}
|
||||
namesStr := strings.Join(names, "\n ")
|
||||
fmt.Printf("Installing:\n %s\n", namesStr)
|
||||
err := installLintersWithOneCommand(targets)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
warning("failed to install one or more linters: %s (installing individually)", err)
|
||||
installLintersIndividually(targets)
|
||||
}
|
||||
|
||||
func maybeSortIssues(issues chan *Issue) chan *Issue {
|
||||
if reflect.DeepEqual([]string{"none"}, *sortFlag) {
|
||||
return issues
|
||||
}
|
||||
out := make(chan *Issue, 1000000)
|
||||
sorted := &sortedIssues{
|
||||
issues: []*Issue{},
|
||||
order: *sortFlag,
|
||||
}
|
||||
go func() {
|
||||
for issue := range issues {
|
||||
sorted.issues = append(sorted.issues, issue)
|
||||
}
|
||||
sort.Sort(sorted)
|
||||
for _, issue := range sorted.issues {
|
||||
out <- issue
|
||||
}
|
||||
close(out)
|
||||
}()
|
||||
return out
|
||||
}
|
||||
|
||||
type linterState struct {
|
||||
*Linter
|
||||
path string
|
||||
issues chan *Issue
|
||||
vars Vars
|
||||
exclude *regexp.Regexp
|
||||
include *regexp.Regexp
|
||||
deadline <-chan time.Time
|
||||
}
|
||||
|
||||
func (l *linterState) InterpolatedCommand() string {
|
||||
vars := l.vars.Copy()
|
||||
if l.ShouldChdir() {
|
||||
vars["path"] = "."
|
||||
} else {
|
||||
vars["path"] = l.path
|
||||
}
|
||||
return vars.Replace(l.Command)
|
||||
}
|
||||
|
||||
func (l *linterState) ShouldChdir() bool {
|
||||
return *vendorFlag || !strings.HasSuffix(l.path, "/...") || !strings.Contains(l.Command, "{path}")
|
||||
}
|
||||
|
||||
func parseCommand(dir, command string) (string, []string, error) {
|
||||
args, err := shlex.Split(command)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if len(args) == 0 {
|
||||
return "", nil, fmt.Errorf("invalid command %q", command)
|
||||
}
|
||||
exe, err := exec.LookPath(args[0])
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
out := []string{}
|
||||
for _, arg := range args[1:] {
|
||||
if strings.Contains(arg, "*") {
|
||||
pattern := filepath.Join(dir, arg)
|
||||
globbed, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
for i, g := range globbed {
|
||||
if strings.HasPrefix(g, dir+string(filepath.Separator)) {
|
||||
globbed[i] = g[len(dir)+1:]
|
||||
}
|
||||
}
|
||||
out = append(out, globbed...)
|
||||
} else {
|
||||
out = append(out, arg)
|
||||
}
|
||||
}
|
||||
return exe, out, nil
|
||||
}
|
||||
|
||||
func executeLinter(state *linterState) error {
|
||||
debug("linting with %s: %s (on %s)", state.Name, state.Command, state.path)
|
||||
|
||||
start := time.Now()
|
||||
command := state.InterpolatedCommand()
|
||||
exe, args, err := parseCommand(state.path, command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
debug("executing %s %q", exe, args)
|
||||
buf := bytes.NewBuffer(nil)
|
||||
cmd := exec.Command(exe, args...)
|
||||
if state.ShouldChdir() {
|
||||
cmd.Dir = state.path
|
||||
}
|
||||
cmd.Stdout = buf
|
||||
cmd.Stderr = buf
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute linter %s: %s", command, err)
|
||||
}
|
||||
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
err = cmd.Wait()
|
||||
done <- true
|
||||
}()
|
||||
|
||||
// Wait for process to complete or deadline to expire.
|
||||
select {
|
||||
case <-done:
|
||||
|
||||
case <-state.deadline:
|
||||
err = fmt.Errorf("deadline exceeded by linter %s on %s (try increasing --deadline)",
|
||||
state.Name, state.path)
|
||||
kerr := cmd.Process.Kill()
|
||||
if kerr != nil {
|
||||
warning("failed to kill %s: %s", state.Name, kerr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
debug("warning: %s returned %s", command, err)
|
||||
}
|
||||
|
||||
processOutput(state, buf.Bytes())
|
||||
elapsed := time.Now().Sub(start)
|
||||
debug("%s linter took %s", state.Name, elapsed)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *linterState) fixPath(path string) string {
|
||||
lpath := strings.TrimSuffix(l.path, "...")
|
||||
labspath, _ := filepath.Abs(lpath)
|
||||
if !filepath.IsAbs(path) {
|
||||
path, _ = filepath.Abs(filepath.Join(labspath, path))
|
||||
}
|
||||
if strings.HasPrefix(path, labspath) {
|
||||
return filepath.Join(lpath, strings.TrimPrefix(path, labspath))
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func lintersFromFlags() map[string]*Linter {
|
||||
out := map[string]*Linter{}
|
||||
for name := range lintersFlag {
|
||||
out[name] = LinterFromName(name)
|
||||
}
|
||||
enabled := make([]string, len(enabledLinters))
|
||||
copy(enabled, enabledLinters)
|
||||
disabled := make([]string, len(disabledLinters))
|
||||
copy(disabled, disabledLinters)
|
||||
|
||||
// Disable slow linters.
|
||||
if *fastFlag {
|
||||
disabled = append(disabled, slowLinters...)
|
||||
}
|
||||
|
||||
disable := map[string]bool{}
|
||||
for _, linter := range disabled {
|
||||
disable[linter] = true
|
||||
}
|
||||
for _, linter := range enabled {
|
||||
delete(disable, linter)
|
||||
}
|
||||
|
||||
for linter := range disable {
|
||||
delete(out, linter)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func processOutput(state *linterState, out []byte) {
|
||||
re := state.regex
|
||||
all := re.FindAllSubmatchIndex(out, -1)
|
||||
debug("%s hits %d: %s", state.Name, len(all), state.Pattern)
|
||||
for _, indices := range all {
|
||||
group := [][]byte{}
|
||||
for i := 0; i < len(indices); i += 2 {
|
||||
fragment := out[indices[i]:indices[i+1]]
|
||||
group = append(group, fragment)
|
||||
}
|
||||
|
||||
issue := &Issue{Line: 1}
|
||||
issue.Linter = LinterFromName(state.Name)
|
||||
for i, name := range re.SubexpNames() {
|
||||
part := string(group[i])
|
||||
if name != "" {
|
||||
state.vars[name] = part
|
||||
}
|
||||
switch name {
|
||||
case "path":
|
||||
issue.Path = state.fixPath(part)
|
||||
|
||||
case "line":
|
||||
n, err := strconv.ParseInt(part, 10, 32)
|
||||
kingpin.FatalIfError(err, "line matched invalid integer")
|
||||
issue.Line = int(n)
|
||||
|
||||
case "col":
|
||||
n, err := strconv.ParseInt(part, 10, 32)
|
||||
kingpin.FatalIfError(err, "col matched invalid integer")
|
||||
issue.Col = int(n)
|
||||
|
||||
case "message":
|
||||
issue.Message = part
|
||||
|
||||
case "":
|
||||
}
|
||||
}
|
||||
if m, ok := linterMessageOverrideFlag[state.Name]; ok {
|
||||
issue.Message = state.vars.Replace(m)
|
||||
}
|
||||
if sev, ok := linterSeverityFlag[state.Name]; ok {
|
||||
issue.Severity = Severity(sev)
|
||||
} else {
|
||||
issue.Severity = "warning"
|
||||
}
|
||||
if state.exclude != nil && state.exclude.MatchString(issue.String()) {
|
||||
continue
|
||||
}
|
||||
if state.include != nil && !state.include.MatchString(issue.String()) {
|
||||
continue
|
||||
}
|
||||
state.issues <- issue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func findVendoredLinters() string {
|
||||
gopaths := strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator))
|
||||
for _, home := range vendoredSearchPaths {
|
||||
for _, p := range gopaths {
|
||||
joined := append([]string{p, "src"}, home...)
|
||||
vendorRoot := filepath.Join(joined...)
|
||||
if _, err := os.Stat(vendorRoot); err == nil {
|
||||
return vendorRoot
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
|
||||
}
|
||||
|
||||
// Add all "bin" directories from GOPATH to PATH, as well as GOBIN if set.
|
||||
func configureEnvironment() {
|
||||
gopaths := strings.Split(os.Getenv("GOPATH"), string(os.PathListSeparator))
|
||||
paths := strings.Split(os.Getenv("PATH"), string(os.PathListSeparator))
|
||||
gobin := os.Getenv("GOBIN")
|
||||
|
||||
if *vendoredLintersFlag && *installFlag {
|
||||
vendorRoot := findVendoredLinters()
|
||||
if vendorRoot == "" {
|
||||
kingpin.Fatalf("could not find vendored linters in %s", os.Getenv("GOPATH"))
|
||||
}
|
||||
debug("found vendored linters at %s, updating environment", vendorRoot)
|
||||
if gobin == "" {
|
||||
gobin = filepath.Join(gopaths[0], "bin")
|
||||
}
|
||||
// "go install" panics when one GOPATH element is beneath another, so we just set
|
||||
// our vendor root instead.
|
||||
gopaths = []string{vendorRoot}
|
||||
}
|
||||
|
||||
for _, p := range gopaths {
|
||||
paths = append(paths, filepath.Join(p, "bin"))
|
||||
}
|
||||
if gobin != "" {
|
||||
paths = append([]string{gobin}, paths...)
|
||||
}
|
||||
|
||||
path := strings.Join(paths, string(os.PathListSeparator))
|
||||
gopath := strings.Join(gopaths, string(os.PathListSeparator))
|
||||
os.Setenv("PATH", path)
|
||||
debug("PATH=%s", os.Getenv("PATH"))
|
||||
os.Setenv("GOPATH", gopath)
|
||||
debug("GOPATH=%s", os.Getenv("GOPATH"))
|
||||
os.Setenv("GOBIN", gobin)
|
||||
debug("GOBIN=%s", os.Getenv("GOBIN"))
|
||||
}
|
||||
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
Normal file
27
vendor/github.com/alecthomas/template/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
Normal file
25
vendor/github.com/alecthomas/template/README.md
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Go's `text/template` package with newline elision
|
||||
|
||||
This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline.
|
||||
|
||||
eg.
|
||||
|
||||
```
|
||||
{{if true}}\
|
||||
hello
|
||||
{{end}}\
|
||||
```
|
||||
|
||||
Will result in:
|
||||
|
||||
```
|
||||
hello\n
|
||||
```
|
||||
|
||||
Rather than:
|
||||
|
||||
```
|
||||
\n
|
||||
hello\n
|
||||
\n
|
||||
```
|
||||
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
Normal file
406
vendor/github.com/alecthomas/template/doc.go
generated
vendored
Normal file
@@ -0,0 +1,406 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package template implements data-driven templates for generating textual output.
|
||||
|
||||
To generate HTML output, see package html/template, which has the same interface
|
||||
as this package but automatically secures HTML output against certain attacks.
|
||||
|
||||
Templates are executed by applying them to a data structure. Annotations in the
|
||||
template refer to elements of the data structure (typically a field of a struct
|
||||
or a key in a map) to control execution and derive values to be displayed.
|
||||
Execution of the template walks the structure and sets the cursor, represented
|
||||
by a period '.' and called "dot", to the value at the current location in the
|
||||
structure as execution proceeds.
|
||||
|
||||
The input text for a template is UTF-8-encoded text in any format.
|
||||
"Actions"--data evaluations or control structures--are delimited by
|
||||
"{{" and "}}"; all text outside actions is copied to the output unchanged.
|
||||
Actions may not span newlines, although comments can.
|
||||
|
||||
Once parsed, a template may be executed safely in parallel.
|
||||
|
||||
Here is a trivial example that prints "17 items are made of wool".
|
||||
|
||||
type Inventory struct {
|
||||
Material string
|
||||
Count uint
|
||||
}
|
||||
sweaters := Inventory{"wool", 17}
|
||||
tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}")
|
||||
if err != nil { panic(err) }
|
||||
err = tmpl.Execute(os.Stdout, sweaters)
|
||||
if err != nil { panic(err) }
|
||||
|
||||
More intricate examples appear below.
|
||||
|
||||
Actions
|
||||
|
||||
Here is the list of actions. "Arguments" and "pipelines" are evaluations of
|
||||
data, defined in detail below.
|
||||
|
||||
*/
|
||||
// {{/* a comment */}}
|
||||
// A comment; discarded. May contain newlines.
|
||||
// Comments do not nest and must start and end at the
|
||||
// delimiters, as shown here.
|
||||
/*
|
||||
|
||||
{{pipeline}}
|
||||
The default textual representation of the value of the pipeline
|
||||
is copied to the output.
|
||||
|
||||
{{if pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, T1 is executed. The empty values are false, 0, any
|
||||
nil pointer or interface value, and any array, slice, map, or
|
||||
string of length zero.
|
||||
Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, T0 is executed;
|
||||
otherwise, T1 is executed. Dot is unaffected.
|
||||
|
||||
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
|
||||
To simplify the appearance of if-else chains, the else action
|
||||
of an if may include another if directly; the effect is exactly
|
||||
the same as writing
|
||||
{{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}}
|
||||
|
||||
{{range pipeline}} T1 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, nothing is output;
|
||||
otherwise, dot is set to the successive elements of the array,
|
||||
slice, or map and T1 is executed. If the value is a map and the
|
||||
keys are of basic type with a defined order ("comparable"), the
|
||||
elements will be visited in sorted key order.
|
||||
|
||||
{{range pipeline}} T1 {{else}} T0 {{end}}
|
||||
The value of the pipeline must be an array, slice, map, or channel.
|
||||
If the value of the pipeline has length zero, dot is unaffected and
|
||||
T0 is executed; otherwise, dot is set to the successive elements
|
||||
of the array, slice, or map and T1 is executed.
|
||||
|
||||
{{template "name"}}
|
||||
The template with the specified name is executed with nil data.
|
||||
|
||||
{{template "name" pipeline}}
|
||||
The template with the specified name is executed with dot set
|
||||
to the value of the pipeline.
|
||||
|
||||
{{with pipeline}} T1 {{end}}
|
||||
If the value of the pipeline is empty, no output is generated;
|
||||
otherwise, dot is set to the value of the pipeline and T1 is
|
||||
executed.
|
||||
|
||||
{{with pipeline}} T1 {{else}} T0 {{end}}
|
||||
If the value of the pipeline is empty, dot is unaffected and T0
|
||||
is executed; otherwise, dot is set to the value of the pipeline
|
||||
and T1 is executed.
|
||||
|
||||
Arguments
|
||||
|
||||
An argument is a simple value, denoted by one of the following.
|
||||
|
||||
- A boolean, string, character, integer, floating-point, imaginary
|
||||
or complex constant in Go syntax. These behave like Go's untyped
|
||||
constants, although raw strings may not span newlines.
|
||||
- The keyword nil, representing an untyped Go nil.
|
||||
- The character '.' (period):
|
||||
.
|
||||
The result is the value of dot.
|
||||
- A variable name, which is a (possibly empty) alphanumeric string
|
||||
preceded by a dollar sign, such as
|
||||
$piOver2
|
||||
or
|
||||
$
|
||||
The result is the value of the variable.
|
||||
Variables are described below.
|
||||
- The name of a field of the data, which must be a struct, preceded
|
||||
by a period, such as
|
||||
.Field
|
||||
The result is the value of the field. Field invocations may be
|
||||
chained:
|
||||
.Field1.Field2
|
||||
Fields can also be evaluated on variables, including chaining:
|
||||
$x.Field1.Field2
|
||||
- The name of a key of the data, which must be a map, preceded
|
||||
by a period, such as
|
||||
.Key
|
||||
The result is the map element value indexed by the key.
|
||||
Key invocations may be chained and combined with fields to any
|
||||
depth:
|
||||
.Field1.Key1.Field2.Key2
|
||||
Although the key must be an alphanumeric identifier, unlike with
|
||||
field names they do not need to start with an upper case letter.
|
||||
Keys can also be evaluated on variables, including chaining:
|
||||
$x.key1.key2
|
||||
- The name of a niladic method of the data, preceded by a period,
|
||||
such as
|
||||
.Method
|
||||
The result is the value of invoking the method with dot as the
|
||||
receiver, dot.Method(). Such a method must have one return value (of
|
||||
any type) or two return values, the second of which is an error.
|
||||
If it has two and the returned error is non-nil, execution terminates
|
||||
and an error is returned to the caller as the value of Execute.
|
||||
Method invocations may be chained and combined with fields and keys
|
||||
to any depth:
|
||||
.Field1.Key1.Method1.Field2.Key2.Method2
|
||||
Methods can also be evaluated on variables, including chaining:
|
||||
$x.Method1.Field
|
||||
- The name of a niladic function, such as
|
||||
fun
|
||||
The result is the value of invoking the function, fun(). The return
|
||||
types and values behave as in methods. Functions and function
|
||||
names are described below.
|
||||
- A parenthesized instance of one the above, for grouping. The result
|
||||
may be accessed by a field or map key invocation.
|
||||
print (.F1 arg1) (.F2 arg2)
|
||||
(.StructValuedMethod "arg").Field
|
||||
|
||||
Arguments may evaluate to any type; if they are pointers the implementation
|
||||
automatically indirects to the base type when required.
|
||||
If an evaluation yields a function value, such as a function-valued
|
||||
field of a struct, the function is not invoked automatically, but it
|
||||
can be used as a truth value for an if action and the like. To invoke
|
||||
it, use the call function, defined below.
|
||||
|
||||
A pipeline is a possibly chained sequence of "commands". A command is a simple
|
||||
value (argument) or a function or method call, possibly with multiple arguments:
|
||||
|
||||
Argument
|
||||
The result is the value of evaluating the argument.
|
||||
.Method [Argument...]
|
||||
The method can be alone or the last element of a chain but,
|
||||
unlike methods in the middle of a chain, it can take arguments.
|
||||
The result is the value of calling the method with the
|
||||
arguments:
|
||||
dot.Method(Argument1, etc.)
|
||||
functionName [Argument...]
|
||||
The result is the value of calling the function associated
|
||||
with the name:
|
||||
function(Argument1, etc.)
|
||||
Functions and function names are described below.
|
||||
|
||||
Pipelines
|
||||
|
||||
A pipeline may be "chained" by separating a sequence of commands with pipeline
|
||||
characters '|'. In a chained pipeline, the result of the each command is
|
||||
passed as the last argument of the following command. The output of the final
|
||||
command in the pipeline is the value of the pipeline.
|
||||
|
||||
The output of a command will be either one value or two values, the second of
|
||||
which has type error. If that second value is present and evaluates to
|
||||
non-nil, execution terminates and the error is returned to the caller of
|
||||
Execute.
|
||||
|
||||
Variables
|
||||
|
||||
A pipeline inside an action may initialize a variable to capture the result.
|
||||
The initialization has syntax
|
||||
|
||||
$variable := pipeline
|
||||
|
||||
where $variable is the name of the variable. An action that declares a
|
||||
variable produces no output.
|
||||
|
||||
If a "range" action initializes a variable, the variable is set to the
|
||||
successive elements of the iteration. Also, a "range" may declare two
|
||||
variables, separated by a comma:
|
||||
|
||||
range $index, $element := pipeline
|
||||
|
||||
in which case $index and $element are set to the successive values of the
|
||||
array/slice index or map key and element, respectively. Note that if there is
|
||||
only one variable, it is assigned the element; this is opposite to the
|
||||
convention in Go range clauses.
|
||||
|
||||
A variable's scope extends to the "end" action of the control structure ("if",
|
||||
"with", or "range") in which it is declared, or to the end of the template if
|
||||
there is no such control structure. A template invocation does not inherit
|
||||
variables from the point of its invocation.
|
||||
|
||||
When execution begins, $ is set to the data argument passed to Execute, that is,
|
||||
to the starting value of dot.
|
||||
|
||||
Examples
|
||||
|
||||
Here are some example one-line templates demonstrating pipelines and variables.
|
||||
All produce the quoted word "output":
|
||||
|
||||
{{"\"output\""}}
|
||||
A string constant.
|
||||
{{`"output"`}}
|
||||
A raw string constant.
|
||||
{{printf "%q" "output"}}
|
||||
A function call.
|
||||
{{"output" | printf "%q"}}
|
||||
A function call whose final argument comes from the previous
|
||||
command.
|
||||
{{printf "%q" (print "out" "put")}}
|
||||
A parenthesized argument.
|
||||
{{"put" | printf "%s%s" "out" | printf "%q"}}
|
||||
A more elaborate call.
|
||||
{{"output" | printf "%s" | printf "%q"}}
|
||||
A longer chain.
|
||||
{{with "output"}}{{printf "%q" .}}{{end}}
|
||||
A with action using dot.
|
||||
{{with $x := "output" | printf "%q"}}{{$x}}{{end}}
|
||||
A with action that creates and uses a variable.
|
||||
{{with $x := "output"}}{{printf "%q" $x}}{{end}}
|
||||
A with action that uses the variable in another action.
|
||||
{{with $x := "output"}}{{$x | printf "%q"}}{{end}}
|
||||
The same, but pipelined.
|
||||
|
||||
Functions
|
||||
|
||||
During execution functions are found in two function maps: first in the
|
||||
template, then in the global function map. By default, no functions are defined
|
||||
in the template but the Funcs method can be used to add them.
|
||||
|
||||
Predefined global functions are named as follows.
|
||||
|
||||
and
|
||||
Returns the boolean AND of its arguments by returning the
|
||||
first empty argument or the last argument, that is,
|
||||
"and x y" behaves as "if x then y else x". All the
|
||||
arguments are evaluated.
|
||||
call
|
||||
Returns the result of calling the first argument, which
|
||||
must be a function, with the remaining arguments as parameters.
|
||||
Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where
|
||||
Y is a func-valued field, map entry, or the like.
|
||||
The first argument must be the result of an evaluation
|
||||
that yields a value of function type (as distinct from
|
||||
a predefined function such as print). The function must
|
||||
return either one or two result values, the second of which
|
||||
is of type error. If the arguments don't match the function
|
||||
or the returned error value is non-nil, execution stops.
|
||||
html
|
||||
Returns the escaped HTML equivalent of the textual
|
||||
representation of its arguments.
|
||||
index
|
||||
Returns the result of indexing its first argument by the
|
||||
following arguments. Thus "index x 1 2 3" is, in Go syntax,
|
||||
x[1][2][3]. Each indexed item must be a map, slice, or array.
|
||||
js
|
||||
Returns the escaped JavaScript equivalent of the textual
|
||||
representation of its arguments.
|
||||
len
|
||||
Returns the integer length of its argument.
|
||||
not
|
||||
Returns the boolean negation of its single argument.
|
||||
or
|
||||
Returns the boolean OR of its arguments by returning the
|
||||
first non-empty argument or the last argument, that is,
|
||||
"or x y" behaves as "if x then x else y". All the
|
||||
arguments are evaluated.
|
||||
print
|
||||
An alias for fmt.Sprint
|
||||
printf
|
||||
An alias for fmt.Sprintf
|
||||
println
|
||||
An alias for fmt.Sprintln
|
||||
urlquery
|
||||
Returns the escaped value of the textual representation of
|
||||
its arguments in a form suitable for embedding in a URL query.
|
||||
|
||||
The boolean functions take any zero value to be false and a non-zero
|
||||
value to be true.
|
||||
|
||||
There is also a set of binary comparison operators defined as
|
||||
functions:
|
||||
|
||||
eq
|
||||
Returns the boolean truth of arg1 == arg2
|
||||
ne
|
||||
Returns the boolean truth of arg1 != arg2
|
||||
lt
|
||||
Returns the boolean truth of arg1 < arg2
|
||||
le
|
||||
Returns the boolean truth of arg1 <= arg2
|
||||
gt
|
||||
Returns the boolean truth of arg1 > arg2
|
||||
ge
|
||||
Returns the boolean truth of arg1 >= arg2
|
||||
|
||||
For simpler multi-way equality tests, eq (only) accepts two or more
|
||||
arguments and compares the second and subsequent to the first,
|
||||
returning in effect
|
||||
|
||||
arg1==arg2 || arg1==arg3 || arg1==arg4 ...
|
||||
|
||||
(Unlike with || in Go, however, eq is a function call and all the
|
||||
arguments will be evaluated.)
|
||||
|
||||
The comparison functions work on basic types only (or named basic
|
||||
types, such as "type Celsius float32"). They implement the Go rules
|
||||
for comparison of values, except that size and exact type are
|
||||
ignored, so any integer value, signed or unsigned, may be compared
|
||||
with any other integer value. (The arithmetic value is compared,
|
||||
not the bit pattern, so all negative integers are less than all
|
||||
unsigned integers.) However, as usual, one may not compare an int
|
||||
with a float32 and so on.
|
||||
|
||||
Associated templates
|
||||
|
||||
Each template is named by a string specified when it is created. Also, each
|
||||
template is associated with zero or more other templates that it may invoke by
|
||||
name; such associations are transitive and form a name space of templates.
|
||||
|
||||
A template may use a template invocation to instantiate another associated
|
||||
template; see the explanation of the "template" action above. The name must be
|
||||
that of a template associated with the template that contains the invocation.
|
||||
|
||||
Nested template definitions
|
||||
|
||||
When parsing a template, another template may be defined and associated with the
|
||||
template being parsed. Template definitions must appear at the top level of the
|
||||
template, much like global variables in a Go program.
|
||||
|
||||
The syntax of such definitions is to surround each template declaration with a
|
||||
"define" and "end" action.
|
||||
|
||||
The define action names the template being created by providing a string
|
||||
constant. Here is a simple example:
|
||||
|
||||
`{{define "T1"}}ONE{{end}}
|
||||
{{define "T2"}}TWO{{end}}
|
||||
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
|
||||
{{template "T3"}}`
|
||||
|
||||
This defines two templates, T1 and T2, and a third T3 that invokes the other two
|
||||
when it is executed. Finally it invokes T3. If executed this template will
|
||||
produce the text
|
||||
|
||||
ONE TWO
|
||||
|
||||
By construction, a template may reside in only one association. If it's
|
||||
necessary to have a template addressable from multiple associations, the
|
||||
template definition must be parsed multiple times to create distinct *Template
|
||||
values, or must be copied with the Clone or AddParseTree method.
|
||||
|
||||
Parse may be called multiple times to assemble the various associated templates;
|
||||
see the ParseFiles and ParseGlob functions and methods for simple ways to parse
|
||||
related templates stored in files.
|
||||
|
||||
A template may be executed directly or through ExecuteTemplate, which executes
|
||||
an associated template identified by name. To invoke our example above, we
|
||||
might write,
|
||||
|
||||
err := tmpl.Execute(os.Stdout, "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
or to invoke a particular template explicitly by name,
|
||||
|
||||
err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed")
|
||||
if err != nil {
|
||||
log.Fatalf("execution failed: %s", err)
|
||||
}
|
||||
|
||||
*/
|
||||
package template
|
||||
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
Normal file
845
vendor/github.com/alecthomas/template/exec.go
generated
vendored
Normal file
@@ -0,0 +1,845 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
// state represents the state of an execution. It's not part of the
|
||||
// template so that multiple executions of the same template
|
||||
// can execute in parallel.
|
||||
type state struct {
|
||||
tmpl *Template
|
||||
wr io.Writer
|
||||
node parse.Node // current node, for errors
|
||||
vars []variable // push-down stack of variable values.
|
||||
}
|
||||
|
||||
// variable holds the dynamic value of a variable such as $, $x etc.
|
||||
type variable struct {
|
||||
name string
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
// push pushes a new variable on the stack.
|
||||
func (s *state) push(name string, value reflect.Value) {
|
||||
s.vars = append(s.vars, variable{name, value})
|
||||
}
|
||||
|
||||
// mark returns the length of the variable stack.
|
||||
func (s *state) mark() int {
|
||||
return len(s.vars)
|
||||
}
|
||||
|
||||
// pop pops the variable stack up to the mark.
|
||||
func (s *state) pop(mark int) {
|
||||
s.vars = s.vars[0:mark]
|
||||
}
|
||||
|
||||
// setVar overwrites the top-nth variable on the stack. Used by range iterations.
|
||||
func (s *state) setVar(n int, value reflect.Value) {
|
||||
s.vars[len(s.vars)-n].value = value
|
||||
}
|
||||
|
||||
// varValue returns the value of the named variable.
|
||||
func (s *state) varValue(name string) reflect.Value {
|
||||
for i := s.mark() - 1; i >= 0; i-- {
|
||||
if s.vars[i].name == name {
|
||||
return s.vars[i].value
|
||||
}
|
||||
}
|
||||
s.errorf("undefined variable: %s", name)
|
||||
return zero
|
||||
}
|
||||
|
||||
var zero reflect.Value
|
||||
|
||||
// at marks the state to be on node n, for error reporting.
|
||||
func (s *state) at(node parse.Node) {
|
||||
s.node = node
|
||||
}
|
||||
|
||||
// doublePercent returns the string with %'s replaced by %%, if necessary,
|
||||
// so it can be used safely inside a Printf format string.
|
||||
func doublePercent(str string) string {
|
||||
if strings.Contains(str, "%") {
|
||||
str = strings.Replace(str, "%", "%%", -1)
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (s *state) errorf(format string, args ...interface{}) {
|
||||
name := doublePercent(s.tmpl.Name())
|
||||
if s.node == nil {
|
||||
format = fmt.Sprintf("template: %s: %s", name, format)
|
||||
} else {
|
||||
location, context := s.tmpl.ErrorContext(s.node)
|
||||
format = fmt.Sprintf("template: %s: executing %q at <%s>: %s", location, name, doublePercent(context), format)
|
||||
}
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// errRecover is the handler that turns panics into returns from the top
|
||||
// level of Parse.
|
||||
func errRecover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
switch err := e.(type) {
|
||||
case runtime.Error:
|
||||
panic(e)
|
||||
case error:
|
||||
*errp = err
|
||||
default:
|
||||
panic(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExecuteTemplate applies the template associated with t that has the given name
|
||||
// to the specified data object and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel.
|
||||
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
||||
tmpl := t.tmpl[name]
|
||||
if tmpl == nil {
|
||||
return fmt.Errorf("template: no template %q associated with template %q", name, t.name)
|
||||
}
|
||||
return tmpl.Execute(wr, data)
|
||||
}
|
||||
|
||||
// Execute applies a parsed template to the specified data object,
|
||||
// and writes the output to wr.
|
||||
// If an error occurs executing the template or writing its output,
|
||||
// execution stops, but partial results may already have been written to
|
||||
// the output writer.
|
||||
// A template may be executed safely in parallel.
|
||||
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
||||
defer errRecover(&err)
|
||||
value := reflect.ValueOf(data)
|
||||
state := &state{
|
||||
tmpl: t,
|
||||
wr: wr,
|
||||
vars: []variable{{"$", value}},
|
||||
}
|
||||
t.init()
|
||||
if t.Tree == nil || t.Root == nil {
|
||||
var b bytes.Buffer
|
||||
for name, tmpl := range t.tmpl {
|
||||
if tmpl.Tree == nil || tmpl.Root == nil {
|
||||
continue
|
||||
}
|
||||
if b.Len() > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
fmt.Fprintf(&b, "%q", name)
|
||||
}
|
||||
var s string
|
||||
if b.Len() > 0 {
|
||||
s = "; defined templates are: " + b.String()
|
||||
}
|
||||
state.errorf("%q is an incomplete or empty template%s", t.Name(), s)
|
||||
}
|
||||
state.walk(value, t.Root)
|
||||
return
|
||||
}
|
||||
|
||||
// Walk functions step through the major pieces of the template structure,
|
||||
// generating output as they go.
|
||||
func (s *state) walk(dot reflect.Value, node parse.Node) {
|
||||
s.at(node)
|
||||
switch node := node.(type) {
|
||||
case *parse.ActionNode:
|
||||
// Do not pop variables so they persist until next end.
|
||||
// Also, if the action declares variables, don't print the result.
|
||||
val := s.evalPipeline(dot, node.Pipe)
|
||||
if len(node.Pipe.Decl) == 0 {
|
||||
s.printValue(node, val)
|
||||
}
|
||||
case *parse.IfNode:
|
||||
s.walkIfOrWith(parse.NodeIf, dot, node.Pipe, node.List, node.ElseList)
|
||||
case *parse.ListNode:
|
||||
for _, node := range node.Nodes {
|
||||
s.walk(dot, node)
|
||||
}
|
||||
case *parse.RangeNode:
|
||||
s.walkRange(dot, node)
|
||||
case *parse.TemplateNode:
|
||||
s.walkTemplate(dot, node)
|
||||
case *parse.TextNode:
|
||||
if _, err := s.wr.Write(node.Text); err != nil {
|
||||
s.errorf("%s", err)
|
||||
}
|
||||
case *parse.WithNode:
|
||||
s.walkIfOrWith(parse.NodeWith, dot, node.Pipe, node.List, node.ElseList)
|
||||
default:
|
||||
s.errorf("unknown node: %s", node)
|
||||
}
|
||||
}
|
||||
|
||||
// walkIfOrWith walks an 'if' or 'with' node. The two control structures
|
||||
// are identical in behavior except that 'with' sets dot.
|
||||
func (s *state) walkIfOrWith(typ parse.NodeType, dot reflect.Value, pipe *parse.PipeNode, list, elseList *parse.ListNode) {
|
||||
defer s.pop(s.mark())
|
||||
val := s.evalPipeline(dot, pipe)
|
||||
truth, ok := isTrue(val)
|
||||
if !ok {
|
||||
s.errorf("if/with can't use %v", val)
|
||||
}
|
||||
if truth {
|
||||
if typ == parse.NodeWith {
|
||||
s.walk(val, list)
|
||||
} else {
|
||||
s.walk(dot, list)
|
||||
}
|
||||
} else if elseList != nil {
|
||||
s.walk(dot, elseList)
|
||||
}
|
||||
}
|
||||
|
||||
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
||||
// and whether the value has a meaningful truth value.
|
||||
func isTrue(val reflect.Value) (truth, ok bool) {
|
||||
if !val.IsValid() {
|
||||
// Something like var x interface{}, never set. It's a form of nil.
|
||||
return false, true
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
truth = val.Len() > 0
|
||||
case reflect.Bool:
|
||||
truth = val.Bool()
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
truth = val.Complex() != 0
|
||||
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
|
||||
truth = !val.IsNil()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
truth = val.Int() != 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
truth = val.Float() != 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
truth = val.Uint() != 0
|
||||
case reflect.Struct:
|
||||
truth = true // Struct values are always true.
|
||||
default:
|
||||
return
|
||||
}
|
||||
return truth, true
|
||||
}
|
||||
|
||||
func (s *state) walkRange(dot reflect.Value, r *parse.RangeNode) {
|
||||
s.at(r)
|
||||
defer s.pop(s.mark())
|
||||
val, _ := indirect(s.evalPipeline(dot, r.Pipe))
|
||||
// mark top of stack before any variables in the body are pushed.
|
||||
mark := s.mark()
|
||||
oneIteration := func(index, elem reflect.Value) {
|
||||
// Set top var (lexically the second if there are two) to the element.
|
||||
if len(r.Pipe.Decl) > 0 {
|
||||
s.setVar(1, elem)
|
||||
}
|
||||
// Set next var (lexically the first if there are two) to the index.
|
||||
if len(r.Pipe.Decl) > 1 {
|
||||
s.setVar(2, index)
|
||||
}
|
||||
s.walk(elem, r.List)
|
||||
s.pop(mark)
|
||||
}
|
||||
switch val.Kind() {
|
||||
case reflect.Array, reflect.Slice:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for i := 0; i < val.Len(); i++ {
|
||||
oneIteration(reflect.ValueOf(i), val.Index(i))
|
||||
}
|
||||
return
|
||||
case reflect.Map:
|
||||
if val.Len() == 0 {
|
||||
break
|
||||
}
|
||||
for _, key := range sortKeys(val.MapKeys()) {
|
||||
oneIteration(key, val.MapIndex(key))
|
||||
}
|
||||
return
|
||||
case reflect.Chan:
|
||||
if val.IsNil() {
|
||||
break
|
||||
}
|
||||
i := 0
|
||||
for ; ; i++ {
|
||||
elem, ok := val.Recv()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
oneIteration(reflect.ValueOf(i), elem)
|
||||
}
|
||||
if i == 0 {
|
||||
break
|
||||
}
|
||||
return
|
||||
case reflect.Invalid:
|
||||
break // An invalid value is likely a nil map, etc. and acts like an empty map.
|
||||
default:
|
||||
s.errorf("range can't iterate over %v", val)
|
||||
}
|
||||
if r.ElseList != nil {
|
||||
s.walk(dot, r.ElseList)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) walkTemplate(dot reflect.Value, t *parse.TemplateNode) {
|
||||
s.at(t)
|
||||
tmpl := s.tmpl.tmpl[t.Name]
|
||||
if tmpl == nil {
|
||||
s.errorf("template %q not defined", t.Name)
|
||||
}
|
||||
// Variables declared by the pipeline persist.
|
||||
dot = s.evalPipeline(dot, t.Pipe)
|
||||
newState := *s
|
||||
newState.tmpl = tmpl
|
||||
// No dynamic scoping: template invocations inherit no variables.
|
||||
newState.vars = []variable{{"$", dot}}
|
||||
newState.walk(dot, tmpl.Root)
|
||||
}
|
||||
|
||||
// Eval functions evaluate pipelines, commands, and their elements and extract
|
||||
// values from the data structure by examining fields, calling methods, and so on.
|
||||
// The printing of those values happens only through walk functions.
|
||||
|
||||
// evalPipeline returns the value acquired by evaluating a pipeline. If the
|
||||
// pipeline has a variable declaration, the variable will be pushed on the
|
||||
// stack. Callers should therefore pop the stack after they are finished
|
||||
// executing commands depending on the pipeline value.
|
||||
func (s *state) evalPipeline(dot reflect.Value, pipe *parse.PipeNode) (value reflect.Value) {
|
||||
if pipe == nil {
|
||||
return
|
||||
}
|
||||
s.at(pipe)
|
||||
for _, cmd := range pipe.Cmds {
|
||||
value = s.evalCommand(dot, cmd, value) // previous value is this one's final arg.
|
||||
// If the object has type interface{}, dig down one level to the thing inside.
|
||||
if value.Kind() == reflect.Interface && value.Type().NumMethod() == 0 {
|
||||
value = reflect.ValueOf(value.Interface()) // lovely!
|
||||
}
|
||||
}
|
||||
for _, variable := range pipe.Decl {
|
||||
s.push(variable.Ident[0], value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) notAFunction(args []parse.Node, final reflect.Value) {
|
||||
if len(args) > 1 || final.IsValid() {
|
||||
s.errorf("can't give argument to non-function %s", args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (s *state) evalCommand(dot reflect.Value, cmd *parse.CommandNode, final reflect.Value) reflect.Value {
|
||||
firstWord := cmd.Args[0]
|
||||
switch n := firstWord.(type) {
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, cmd.Args, final)
|
||||
case *parse.ChainNode:
|
||||
return s.evalChainNode(dot, n, cmd.Args, final)
|
||||
case *parse.IdentifierNode:
|
||||
// Must be a function.
|
||||
return s.evalFunction(dot, n, cmd, cmd.Args, final)
|
||||
case *parse.PipeNode:
|
||||
// Parenthesized pipeline. The arguments are all inside the pipeline; final is ignored.
|
||||
return s.evalPipeline(dot, n)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, cmd.Args, final)
|
||||
}
|
||||
s.at(firstWord)
|
||||
s.notAFunction(cmd.Args, final)
|
||||
switch word := firstWord.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(word.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.NilNode:
|
||||
s.errorf("nil is not a command")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(word)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(word.Text)
|
||||
}
|
||||
s.errorf("can't evaluate command %q", firstWord)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// idealConstant is called to return the value of a number in a context where
|
||||
// we don't know the type. In that case, the syntax of the number tells us
|
||||
// its type, and we use Go rules to resolve. Note there is no such thing as
|
||||
// a uint ideal constant in this situation - the value must be of int type.
|
||||
func (s *state) idealConstant(constant *parse.NumberNode) reflect.Value {
|
||||
// These are ideal constants but we don't know the type
|
||||
// and we have no context. (If it was a method argument,
|
||||
// we'd know what we need.) The syntax guides us to some extent.
|
||||
s.at(constant)
|
||||
switch {
|
||||
case constant.IsComplex:
|
||||
return reflect.ValueOf(constant.Complex128) // incontrovertible.
|
||||
case constant.IsFloat && !isHexConstant(constant.Text) && strings.IndexAny(constant.Text, ".eE") >= 0:
|
||||
return reflect.ValueOf(constant.Float64)
|
||||
case constant.IsInt:
|
||||
n := int(constant.Int64)
|
||||
if int64(n) != constant.Int64 {
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return reflect.ValueOf(n)
|
||||
case constant.IsUint:
|
||||
s.errorf("%s overflows int", constant.Text)
|
||||
}
|
||||
return zero
|
||||
}
|
||||
|
||||
func isHexConstant(s string) bool {
|
||||
return len(s) > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')
|
||||
}
|
||||
|
||||
func (s *state) evalFieldNode(dot reflect.Value, field *parse.FieldNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(field)
|
||||
return s.evalFieldChain(dot, dot, field, field.Ident, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalChainNode(dot reflect.Value, chain *parse.ChainNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(chain)
|
||||
// (pipe).Field1.Field2 has pipe as .Node, fields as .Field. Eval the pipeline, then the fields.
|
||||
pipe := s.evalArg(dot, nil, chain.Node)
|
||||
if len(chain.Field) == 0 {
|
||||
s.errorf("internal error: no fields in evalChainNode")
|
||||
}
|
||||
return s.evalFieldChain(dot, pipe, chain, chain.Field, args, final)
|
||||
}
|
||||
|
||||
func (s *state) evalVariableNode(dot reflect.Value, variable *parse.VariableNode, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
// $x.Field has $x as the first ident, Field as the second. Eval the var, then the fields.
|
||||
s.at(variable)
|
||||
value := s.varValue(variable.Ident[0])
|
||||
if len(variable.Ident) == 1 {
|
||||
s.notAFunction(args, final)
|
||||
return value
|
||||
}
|
||||
return s.evalFieldChain(dot, value, variable, variable.Ident[1:], args, final)
|
||||
}
|
||||
|
||||
// evalFieldChain evaluates .X.Y.Z possibly followed by arguments.
|
||||
// dot is the environment in which to evaluate arguments, while
|
||||
// receiver is the value being walked along the chain.
|
||||
func (s *state) evalFieldChain(dot, receiver reflect.Value, node parse.Node, ident []string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
n := len(ident)
|
||||
for i := 0; i < n-1; i++ {
|
||||
receiver = s.evalField(dot, ident[i], node, nil, zero, receiver)
|
||||
}
|
||||
// Now if it's a method, it gets the arguments.
|
||||
return s.evalField(dot, ident[n-1], node, args, final, receiver)
|
||||
}
|
||||
|
||||
func (s *state) evalFunction(dot reflect.Value, node *parse.IdentifierNode, cmd parse.Node, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
s.at(node)
|
||||
name := node.Ident
|
||||
function, ok := findFunction(name, s.tmpl)
|
||||
if !ok {
|
||||
s.errorf("%q is not a defined function", name)
|
||||
}
|
||||
return s.evalCall(dot, function, cmd, name, args, final)
|
||||
}
|
||||
|
||||
// evalField evaluates an expression like (.Field) or (.Field arg1 arg2).
|
||||
// The 'final' argument represents the return value from the preceding
|
||||
// value of the pipeline, if any.
|
||||
func (s *state) evalField(dot reflect.Value, fieldName string, node parse.Node, args []parse.Node, final, receiver reflect.Value) reflect.Value {
|
||||
if !receiver.IsValid() {
|
||||
return zero
|
||||
}
|
||||
typ := receiver.Type()
|
||||
receiver, _ = indirect(receiver)
|
||||
// Unless it's an interface, need to get to a value of type *T to guarantee
|
||||
// we see all methods of T and *T.
|
||||
ptr := receiver
|
||||
if ptr.Kind() != reflect.Interface && ptr.CanAddr() {
|
||||
ptr = ptr.Addr()
|
||||
}
|
||||
if method := ptr.MethodByName(fieldName); method.IsValid() {
|
||||
return s.evalCall(dot, method, node, fieldName, args, final)
|
||||
}
|
||||
hasArgs := len(args) > 1 || final.IsValid()
|
||||
// It's not a method; must be a field of a struct or an element of a map. The receiver must not be nil.
|
||||
receiver, isNil := indirect(receiver)
|
||||
if isNil {
|
||||
s.errorf("nil pointer evaluating %s.%s", typ, fieldName)
|
||||
}
|
||||
switch receiver.Kind() {
|
||||
case reflect.Struct:
|
||||
tField, ok := receiver.Type().FieldByName(fieldName)
|
||||
if ok {
|
||||
field := receiver.FieldByIndex(tField.Index)
|
||||
if tField.PkgPath != "" { // field is unexported
|
||||
s.errorf("%s is an unexported field of struct type %s", fieldName, typ)
|
||||
}
|
||||
// If it's a function, we must call it.
|
||||
if hasArgs {
|
||||
s.errorf("%s has arguments but cannot be invoked as function", fieldName)
|
||||
}
|
||||
return field
|
||||
}
|
||||
s.errorf("%s is not a field of struct type %s", fieldName, typ)
|
||||
case reflect.Map:
|
||||
// If it's a map, attempt to use the field name as a key.
|
||||
nameVal := reflect.ValueOf(fieldName)
|
||||
if nameVal.Type().AssignableTo(receiver.Type().Key()) {
|
||||
if hasArgs {
|
||||
s.errorf("%s is not a method but has arguments", fieldName)
|
||||
}
|
||||
return receiver.MapIndex(nameVal)
|
||||
}
|
||||
}
|
||||
s.errorf("can't evaluate field %s in type %s", fieldName, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
var (
|
||||
errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
|
||||
)
|
||||
|
||||
// evalCall executes a function or method call. If it's a method, fun already has the receiver bound, so
|
||||
// it looks just like a function call. The arg list, if non-nil, includes (in the manner of the shell), arg[0]
|
||||
// as the function itself.
|
||||
func (s *state) evalCall(dot, fun reflect.Value, node parse.Node, name string, args []parse.Node, final reflect.Value) reflect.Value {
|
||||
if args != nil {
|
||||
args = args[1:] // Zeroth arg is function name/node; not passed to function.
|
||||
}
|
||||
typ := fun.Type()
|
||||
numIn := len(args)
|
||||
if final.IsValid() {
|
||||
numIn++
|
||||
}
|
||||
numFixed := len(args)
|
||||
if typ.IsVariadic() {
|
||||
numFixed = typ.NumIn() - 1 // last arg is the variadic one.
|
||||
if numIn < numFixed {
|
||||
s.errorf("wrong number of args for %s: want at least %d got %d", name, typ.NumIn()-1, len(args))
|
||||
}
|
||||
} else if numIn < typ.NumIn()-1 || !typ.IsVariadic() && numIn != typ.NumIn() {
|
||||
s.errorf("wrong number of args for %s: want %d got %d", name, typ.NumIn(), len(args))
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
// TODO: This could still be a confusing error; maybe goodFunc should provide info.
|
||||
s.errorf("can't call method/function %q with %d results", name, typ.NumOut())
|
||||
}
|
||||
// Build the arg list.
|
||||
argv := make([]reflect.Value, numIn)
|
||||
// Args must be evaluated. Fixed args first.
|
||||
i := 0
|
||||
for ; i < numFixed && i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, typ.In(i), args[i])
|
||||
}
|
||||
// Now the ... args.
|
||||
if typ.IsVariadic() {
|
||||
argType := typ.In(typ.NumIn() - 1).Elem() // Argument is a slice.
|
||||
for ; i < len(args); i++ {
|
||||
argv[i] = s.evalArg(dot, argType, args[i])
|
||||
}
|
||||
}
|
||||
// Add final value if necessary.
|
||||
if final.IsValid() {
|
||||
t := typ.In(typ.NumIn() - 1)
|
||||
if typ.IsVariadic() {
|
||||
t = t.Elem()
|
||||
}
|
||||
argv[i] = s.validateType(final, t)
|
||||
}
|
||||
result := fun.Call(argv)
|
||||
// If we have an error that is not nil, stop execution and return that error to the caller.
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
s.at(node)
|
||||
s.errorf("error calling %s: %s", name, result[1].Interface().(error))
|
||||
}
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
|
||||
func canBeNil(typ reflect.Type) bool {
|
||||
switch typ.Kind() {
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// validateType guarantees that the value is valid and assignable to the type.
|
||||
func (s *state) validateType(value reflect.Value, typ reflect.Type) reflect.Value {
|
||||
if !value.IsValid() {
|
||||
if typ == nil || canBeNil(typ) {
|
||||
// An untyped nil interface{}. Accept as a proper nil value.
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("invalid value; expected %s", typ)
|
||||
}
|
||||
if typ != nil && !value.Type().AssignableTo(typ) {
|
||||
if value.Kind() == reflect.Interface && !value.IsNil() {
|
||||
value = value.Elem()
|
||||
if value.Type().AssignableTo(typ) {
|
||||
return value
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
// Does one dereference or indirection work? We could do more, as we
|
||||
// do with method receivers, but that gets messy and method receivers
|
||||
// are much more constrained, so it makes more sense there than here.
|
||||
// Besides, one is almost always all you need.
|
||||
switch {
|
||||
case value.Kind() == reflect.Ptr && value.Type().Elem().AssignableTo(typ):
|
||||
value = value.Elem()
|
||||
if !value.IsValid() {
|
||||
s.errorf("dereference of nil pointer of type %s", typ)
|
||||
}
|
||||
case reflect.PtrTo(value.Type()).AssignableTo(typ) && value.CanAddr():
|
||||
value = value.Addr()
|
||||
default:
|
||||
s.errorf("wrong type for value; expected %s; got %s", typ, value.Type())
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func (s *state) evalArg(dot reflect.Value, typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch arg := n.(type) {
|
||||
case *parse.DotNode:
|
||||
return s.validateType(dot, typ)
|
||||
case *parse.NilNode:
|
||||
if canBeNil(typ) {
|
||||
return reflect.Zero(typ)
|
||||
}
|
||||
s.errorf("cannot assign nil to %s", typ)
|
||||
case *parse.FieldNode:
|
||||
return s.validateType(s.evalFieldNode(dot, arg, []parse.Node{n}, zero), typ)
|
||||
case *parse.VariableNode:
|
||||
return s.validateType(s.evalVariableNode(dot, arg, nil, zero), typ)
|
||||
case *parse.PipeNode:
|
||||
return s.validateType(s.evalPipeline(dot, arg), typ)
|
||||
case *parse.IdentifierNode:
|
||||
return s.evalFunction(dot, arg, arg, nil, zero)
|
||||
case *parse.ChainNode:
|
||||
return s.validateType(s.evalChainNode(dot, arg, nil, zero), typ)
|
||||
}
|
||||
switch typ.Kind() {
|
||||
case reflect.Bool:
|
||||
return s.evalBool(typ, n)
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return s.evalComplex(typ, n)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return s.evalFloat(typ, n)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return s.evalInteger(typ, n)
|
||||
case reflect.Interface:
|
||||
if typ.NumMethod() == 0 {
|
||||
return s.evalEmptyInterface(dot, n)
|
||||
}
|
||||
case reflect.String:
|
||||
return s.evalString(typ, n)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return s.evalUnsignedInteger(typ, n)
|
||||
}
|
||||
s.errorf("can't handle %s for arg of type %s", n, typ)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalBool(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.BoolNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetBool(n.True)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected bool; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalString(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.StringNode); ok {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetString(n.Text)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected string; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsInt {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetInt(n.Int64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalUnsignedInteger(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsUint {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetUint(n.Uint64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected unsigned integer; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalFloat(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsFloat {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetFloat(n.Float64)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected float; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalComplex(typ reflect.Type, n parse.Node) reflect.Value {
|
||||
if n, ok := n.(*parse.NumberNode); ok && n.IsComplex {
|
||||
value := reflect.New(typ).Elem()
|
||||
value.SetComplex(n.Complex128)
|
||||
return value
|
||||
}
|
||||
s.errorf("expected complex; found %s", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (s *state) evalEmptyInterface(dot reflect.Value, n parse.Node) reflect.Value {
|
||||
s.at(n)
|
||||
switch n := n.(type) {
|
||||
case *parse.BoolNode:
|
||||
return reflect.ValueOf(n.True)
|
||||
case *parse.DotNode:
|
||||
return dot
|
||||
case *parse.FieldNode:
|
||||
return s.evalFieldNode(dot, n, nil, zero)
|
||||
case *parse.IdentifierNode:
|
||||
return s.evalFunction(dot, n, n, nil, zero)
|
||||
case *parse.NilNode:
|
||||
// NilNode is handled in evalArg, the only place that calls here.
|
||||
s.errorf("evalEmptyInterface: nil (can't happen)")
|
||||
case *parse.NumberNode:
|
||||
return s.idealConstant(n)
|
||||
case *parse.StringNode:
|
||||
return reflect.ValueOf(n.Text)
|
||||
case *parse.VariableNode:
|
||||
return s.evalVariableNode(dot, n, nil, zero)
|
||||
case *parse.PipeNode:
|
||||
return s.evalPipeline(dot, n)
|
||||
}
|
||||
s.errorf("can't handle assignment of %s to empty interface argument", n)
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
|
||||
// We indirect through pointers and empty interfaces (only) because
|
||||
// non-empty interfaces have methods we might need.
|
||||
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
|
||||
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
|
||||
if v.IsNil() {
|
||||
return v, true
|
||||
}
|
||||
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return v, false
|
||||
}
|
||||
|
||||
// printValue writes the textual representation of the value to the output of
|
||||
// the template.
|
||||
func (s *state) printValue(n parse.Node, v reflect.Value) {
|
||||
s.at(n)
|
||||
iface, ok := printableValue(v)
|
||||
if !ok {
|
||||
s.errorf("can't print %s of type %s", n, v.Type())
|
||||
}
|
||||
fmt.Fprint(s.wr, iface)
|
||||
}
|
||||
|
||||
// printableValue returns the, possibly indirected, interface value inside v that
|
||||
// is best for a call to formatted printer.
|
||||
func printableValue(v reflect.Value) (interface{}, bool) {
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v, _ = indirect(v) // fmt.Fprint handles nil.
|
||||
}
|
||||
if !v.IsValid() {
|
||||
return "<no value>", true
|
||||
}
|
||||
|
||||
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
|
||||
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
|
||||
v = v.Addr()
|
||||
} else {
|
||||
switch v.Kind() {
|
||||
case reflect.Chan, reflect.Func:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return v.Interface(), true
|
||||
}
|
||||
|
||||
// Types to help sort the keys in a map for reproducible output.
|
||||
|
||||
type rvs []reflect.Value
|
||||
|
||||
func (x rvs) Len() int { return len(x) }
|
||||
func (x rvs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
||||
|
||||
type rvInts struct{ rvs }
|
||||
|
||||
func (x rvInts) Less(i, j int) bool { return x.rvs[i].Int() < x.rvs[j].Int() }
|
||||
|
||||
type rvUints struct{ rvs }
|
||||
|
||||
func (x rvUints) Less(i, j int) bool { return x.rvs[i].Uint() < x.rvs[j].Uint() }
|
||||
|
||||
type rvFloats struct{ rvs }
|
||||
|
||||
func (x rvFloats) Less(i, j int) bool { return x.rvs[i].Float() < x.rvs[j].Float() }
|
||||
|
||||
type rvStrings struct{ rvs }
|
||||
|
||||
func (x rvStrings) Less(i, j int) bool { return x.rvs[i].String() < x.rvs[j].String() }
|
||||
|
||||
// sortKeys sorts (if it can) the slice of reflect.Values, which is a slice of map keys.
|
||||
func sortKeys(v []reflect.Value) []reflect.Value {
|
||||
if len(v) <= 1 {
|
||||
return v
|
||||
}
|
||||
switch v[0].Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
sort.Sort(rvFloats{v})
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
sort.Sort(rvInts{v})
|
||||
case reflect.String:
|
||||
sort.Sort(rvStrings{v})
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
sort.Sort(rvUints{v})
|
||||
}
|
||||
return v
|
||||
}
|
||||
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
Normal file
598
vendor/github.com/alecthomas/template/funcs.go
generated
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// FuncMap is the type of the map defining the mapping from names to functions.
|
||||
// Each function must have either a single return value, or two return values of
|
||||
// which the second has type error. In that case, if the second (error)
|
||||
// return value evaluates to non-nil during execution, execution terminates and
|
||||
// Execute returns that error.
|
||||
type FuncMap map[string]interface{}
|
||||
|
||||
var builtins = FuncMap{
|
||||
"and": and,
|
||||
"call": call,
|
||||
"html": HTMLEscaper,
|
||||
"index": index,
|
||||
"js": JSEscaper,
|
||||
"len": length,
|
||||
"not": not,
|
||||
"or": or,
|
||||
"print": fmt.Sprint,
|
||||
"printf": fmt.Sprintf,
|
||||
"println": fmt.Sprintln,
|
||||
"urlquery": URLQueryEscaper,
|
||||
|
||||
// Comparisons
|
||||
"eq": eq, // ==
|
||||
"ge": ge, // >=
|
||||
"gt": gt, // >
|
||||
"le": le, // <=
|
||||
"lt": lt, // <
|
||||
"ne": ne, // !=
|
||||
}
|
||||
|
||||
var builtinFuncs = createValueFuncs(builtins)
|
||||
|
||||
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
|
||||
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
|
||||
m := make(map[string]reflect.Value)
|
||||
addValueFuncs(m, funcMap)
|
||||
return m
|
||||
}
|
||||
|
||||
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
|
||||
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
v := reflect.ValueOf(fn)
|
||||
if v.Kind() != reflect.Func {
|
||||
panic("value for " + name + " not a function")
|
||||
}
|
||||
if !goodFunc(v.Type()) {
|
||||
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
|
||||
}
|
||||
out[name] = v
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncs adds to values the functions in funcs. It does no checking of the input -
|
||||
// call addValueFuncs first.
|
||||
func addFuncs(out, in FuncMap) {
|
||||
for name, fn := range in {
|
||||
out[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
// goodFunc checks that the function or method has the right result signature.
|
||||
func goodFunc(typ reflect.Type) bool {
|
||||
// We allow functions with 1 result or 2 results where the second is an error.
|
||||
switch {
|
||||
case typ.NumOut() == 1:
|
||||
return true
|
||||
case typ.NumOut() == 2 && typ.Out(1) == errorType:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// findFunction looks for a function in the template, and global map.
|
||||
func findFunction(name string, tmpl *Template) (reflect.Value, bool) {
|
||||
if tmpl != nil && tmpl.common != nil {
|
||||
if fn := tmpl.execFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
}
|
||||
if fn := builtinFuncs[name]; fn.IsValid() {
|
||||
return fn, true
|
||||
}
|
||||
return reflect.Value{}, false
|
||||
}
|
||||
|
||||
// Indexing.
|
||||
|
||||
// index returns the result of indexing its first argument by the following
|
||||
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
|
||||
// indexed item must be a map, slice, or array.
|
||||
func index(item interface{}, indices ...interface{}) (interface{}, error) {
|
||||
v := reflect.ValueOf(item)
|
||||
for _, i := range indices {
|
||||
index := reflect.ValueOf(i)
|
||||
var isNil bool
|
||||
if v, isNil = indirect(v); isNil {
|
||||
return nil, fmt.Errorf("index of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
var x int64
|
||||
switch index.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
x = index.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
x = int64(index.Uint())
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
|
||||
}
|
||||
if x < 0 || x >= int64(v.Len()) {
|
||||
return nil, fmt.Errorf("index out of range: %d", x)
|
||||
}
|
||||
v = v.Index(int(x))
|
||||
case reflect.Map:
|
||||
if !index.IsValid() {
|
||||
index = reflect.Zero(v.Type().Key())
|
||||
}
|
||||
if !index.Type().AssignableTo(v.Type().Key()) {
|
||||
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
|
||||
}
|
||||
if x := v.MapIndex(index); x.IsValid() {
|
||||
v = x
|
||||
} else {
|
||||
v = reflect.Zero(v.Type().Elem())
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("can't index item of type %s", v.Type())
|
||||
}
|
||||
}
|
||||
return v.Interface(), nil
|
||||
}
|
||||
|
||||
// Length
|
||||
|
||||
// length returns the length of the item, with an error if it has no defined length.
|
||||
func length(item interface{}) (int, error) {
|
||||
v, isNil := indirect(reflect.ValueOf(item))
|
||||
if isNil {
|
||||
return 0, fmt.Errorf("len of nil pointer")
|
||||
}
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len(), nil
|
||||
}
|
||||
return 0, fmt.Errorf("len of type %s", v.Type())
|
||||
}
|
||||
|
||||
// Function invocation
|
||||
|
||||
// call returns the result of evaluating the first argument as a function.
|
||||
// The function must return 1 result, or 2 results, the second of which is an error.
|
||||
func call(fn interface{}, args ...interface{}) (interface{}, error) {
|
||||
v := reflect.ValueOf(fn)
|
||||
typ := v.Type()
|
||||
if typ.Kind() != reflect.Func {
|
||||
return nil, fmt.Errorf("non-function of type %s", typ)
|
||||
}
|
||||
if !goodFunc(typ) {
|
||||
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
|
||||
}
|
||||
numIn := typ.NumIn()
|
||||
var dddType reflect.Type
|
||||
if typ.IsVariadic() {
|
||||
if len(args) < numIn-1 {
|
||||
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
|
||||
}
|
||||
dddType = typ.In(numIn - 1).Elem()
|
||||
} else {
|
||||
if len(args) != numIn {
|
||||
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
|
||||
}
|
||||
}
|
||||
argv := make([]reflect.Value, len(args))
|
||||
for i, arg := range args {
|
||||
value := reflect.ValueOf(arg)
|
||||
// Compute the expected type. Clumsy because of variadics.
|
||||
var argType reflect.Type
|
||||
if !typ.IsVariadic() || i < numIn-1 {
|
||||
argType = typ.In(i)
|
||||
} else {
|
||||
argType = dddType
|
||||
}
|
||||
if !value.IsValid() && canBeNil(argType) {
|
||||
value = reflect.Zero(argType)
|
||||
}
|
||||
if !value.Type().AssignableTo(argType) {
|
||||
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
|
||||
}
|
||||
argv[i] = value
|
||||
}
|
||||
result := v.Call(argv)
|
||||
if len(result) == 2 && !result[1].IsNil() {
|
||||
return result[0].Interface(), result[1].Interface().(error)
|
||||
}
|
||||
return result[0].Interface(), nil
|
||||
}
|
||||
|
||||
// Boolean logic.
|
||||
|
||||
func truth(a interface{}) bool {
|
||||
t, _ := isTrue(reflect.ValueOf(a))
|
||||
return t
|
||||
}
|
||||
|
||||
// and computes the Boolean AND of its arguments, returning
|
||||
// the first false argument it encounters, or the last argument.
|
||||
func and(arg0 interface{}, args ...interface{}) interface{} {
|
||||
if !truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if !truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// or computes the Boolean OR of its arguments, returning
|
||||
// the first true argument it encounters, or the last argument.
|
||||
func or(arg0 interface{}, args ...interface{}) interface{} {
|
||||
if truth(arg0) {
|
||||
return arg0
|
||||
}
|
||||
for i := range args {
|
||||
arg0 = args[i]
|
||||
if truth(arg0) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return arg0
|
||||
}
|
||||
|
||||
// not returns the Boolean negation of its argument.
|
||||
func not(arg interface{}) (truth bool) {
|
||||
truth, _ = isTrue(reflect.ValueOf(arg))
|
||||
return !truth
|
||||
}
|
||||
|
||||
// Comparison.
|
||||
|
||||
// TODO: Perhaps allow comparison between signed and unsigned integers.
|
||||
|
||||
var (
|
||||
errBadComparisonType = errors.New("invalid type for comparison")
|
||||
errBadComparison = errors.New("incompatible types for comparison")
|
||||
errNoComparison = errors.New("missing argument for comparison")
|
||||
)
|
||||
|
||||
type kind int
|
||||
|
||||
const (
|
||||
invalidKind kind = iota
|
||||
boolKind
|
||||
complexKind
|
||||
intKind
|
||||
floatKind
|
||||
integerKind
|
||||
stringKind
|
||||
uintKind
|
||||
)
|
||||
|
||||
func basicKind(v reflect.Value) (kind, error) {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return boolKind, nil
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return intKind, nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return uintKind, nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return floatKind, nil
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
return complexKind, nil
|
||||
case reflect.String:
|
||||
return stringKind, nil
|
||||
}
|
||||
return invalidKind, errBadComparisonType
|
||||
}
|
||||
|
||||
// eq evaluates the comparison a == b || a == c || ...
|
||||
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(arg2) == 0 {
|
||||
return false, errNoComparison
|
||||
}
|
||||
for _, arg := range arg2 {
|
||||
v2 := reflect.ValueOf(arg)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind:
|
||||
truth = v1.Bool() == v2.Bool()
|
||||
case complexKind:
|
||||
truth = v1.Complex() == v2.Complex()
|
||||
case floatKind:
|
||||
truth = v1.Float() == v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() == v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() == v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() == v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
if truth {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// ne evaluates the comparison a != b.
|
||||
func ne(arg1, arg2 interface{}) (bool, error) {
|
||||
// != is the inverse of ==.
|
||||
equal, err := eq(arg1, arg2)
|
||||
return !equal, err
|
||||
}
|
||||
|
||||
// lt evaluates the comparison a < b.
|
||||
func lt(arg1, arg2 interface{}) (bool, error) {
|
||||
v1 := reflect.ValueOf(arg1)
|
||||
k1, err := basicKind(v1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
v2 := reflect.ValueOf(arg2)
|
||||
k2, err := basicKind(v2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
truth := false
|
||||
if k1 != k2 {
|
||||
// Special case: Can compare integer values regardless of type's sign.
|
||||
switch {
|
||||
case k1 == intKind && k2 == uintKind:
|
||||
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
|
||||
case k1 == uintKind && k2 == intKind:
|
||||
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
|
||||
default:
|
||||
return false, errBadComparison
|
||||
}
|
||||
} else {
|
||||
switch k1 {
|
||||
case boolKind, complexKind:
|
||||
return false, errBadComparisonType
|
||||
case floatKind:
|
||||
truth = v1.Float() < v2.Float()
|
||||
case intKind:
|
||||
truth = v1.Int() < v2.Int()
|
||||
case stringKind:
|
||||
truth = v1.String() < v2.String()
|
||||
case uintKind:
|
||||
truth = v1.Uint() < v2.Uint()
|
||||
default:
|
||||
panic("invalid kind")
|
||||
}
|
||||
}
|
||||
return truth, nil
|
||||
}
|
||||
|
||||
// le evaluates the comparison <= b.
|
||||
func le(arg1, arg2 interface{}) (bool, error) {
|
||||
// <= is < or ==.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if lessThan || err != nil {
|
||||
return lessThan, err
|
||||
}
|
||||
return eq(arg1, arg2)
|
||||
}
|
||||
|
||||
// gt evaluates the comparison a > b.
|
||||
func gt(arg1, arg2 interface{}) (bool, error) {
|
||||
// > is the inverse of <=.
|
||||
lessOrEqual, err := le(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessOrEqual, nil
|
||||
}
|
||||
|
||||
// ge evaluates the comparison a >= b.
|
||||
func ge(arg1, arg2 interface{}) (bool, error) {
|
||||
// >= is the inverse of <.
|
||||
lessThan, err := lt(arg1, arg2)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return !lessThan, nil
|
||||
}
|
||||
|
||||
// HTML escaping.
|
||||
|
||||
var (
|
||||
htmlQuot = []byte(""") // shorter than """
|
||||
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||
htmlAmp = []byte("&")
|
||||
htmlLt = []byte("<")
|
||||
htmlGt = []byte(">")
|
||||
)
|
||||
|
||||
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
||||
func HTMLEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i, c := range b {
|
||||
var html []byte
|
||||
switch c {
|
||||
case '"':
|
||||
html = htmlQuot
|
||||
case '\'':
|
||||
html = htmlApos
|
||||
case '&':
|
||||
html = htmlAmp
|
||||
case '<':
|
||||
html = htmlLt
|
||||
case '>':
|
||||
html = htmlGt
|
||||
default:
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
w.Write(html)
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
||||
func HTMLEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if strings.IndexAny(s, `'"&<>`) < 0 {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
HTMLEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func HTMLEscaper(args ...interface{}) string {
|
||||
return HTMLEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// JavaScript escaping.
|
||||
|
||||
var (
|
||||
jsLowUni = []byte(`\u00`)
|
||||
hex = []byte("0123456789ABCDEF")
|
||||
|
||||
jsBackslash = []byte(`\\`)
|
||||
jsApos = []byte(`\'`)
|
||||
jsQuot = []byte(`\"`)
|
||||
jsLt = []byte(`\x3C`)
|
||||
jsGt = []byte(`\x3E`)
|
||||
)
|
||||
|
||||
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
||||
func JSEscape(w io.Writer, b []byte) {
|
||||
last := 0
|
||||
for i := 0; i < len(b); i++ {
|
||||
c := b[i]
|
||||
|
||||
if !jsIsSpecial(rune(c)) {
|
||||
// fast path: nothing to do
|
||||
continue
|
||||
}
|
||||
w.Write(b[last:i])
|
||||
|
||||
if c < utf8.RuneSelf {
|
||||
// Quotes, slashes and angle brackets get quoted.
|
||||
// Control characters get written as \u00XX.
|
||||
switch c {
|
||||
case '\\':
|
||||
w.Write(jsBackslash)
|
||||
case '\'':
|
||||
w.Write(jsApos)
|
||||
case '"':
|
||||
w.Write(jsQuot)
|
||||
case '<':
|
||||
w.Write(jsLt)
|
||||
case '>':
|
||||
w.Write(jsGt)
|
||||
default:
|
||||
w.Write(jsLowUni)
|
||||
t, b := c>>4, c&0x0f
|
||||
w.Write(hex[t : t+1])
|
||||
w.Write(hex[b : b+1])
|
||||
}
|
||||
} else {
|
||||
// Unicode rune.
|
||||
r, size := utf8.DecodeRune(b[i:])
|
||||
if unicode.IsPrint(r) {
|
||||
w.Write(b[i : i+size])
|
||||
} else {
|
||||
fmt.Fprintf(w, "\\u%04X", r)
|
||||
}
|
||||
i += size - 1
|
||||
}
|
||||
last = i + 1
|
||||
}
|
||||
w.Write(b[last:])
|
||||
}
|
||||
|
||||
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
||||
func JSEscapeString(s string) string {
|
||||
// Avoid allocation if we can.
|
||||
if strings.IndexFunc(s, jsIsSpecial) < 0 {
|
||||
return s
|
||||
}
|
||||
var b bytes.Buffer
|
||||
JSEscape(&b, []byte(s))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func jsIsSpecial(r rune) bool {
|
||||
switch r {
|
||||
case '\\', '\'', '"', '<', '>':
|
||||
return true
|
||||
}
|
||||
return r < ' ' || utf8.RuneSelf <= r
|
||||
}
|
||||
|
||||
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
||||
// representation of its arguments.
|
||||
func JSEscaper(args ...interface{}) string {
|
||||
return JSEscapeString(evalArgs(args))
|
||||
}
|
||||
|
||||
// URLQueryEscaper returns the escaped value of the textual representation of
|
||||
// its arguments in a form suitable for embedding in a URL query.
|
||||
func URLQueryEscaper(args ...interface{}) string {
|
||||
return url.QueryEscape(evalArgs(args))
|
||||
}
|
||||
|
||||
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
|
||||
// fmt.Sprint(args...)
|
||||
// except that each argument is indirected (if a pointer), as required,
|
||||
// using the same rules as the default string evaluation during template
|
||||
// execution.
|
||||
func evalArgs(args []interface{}) string {
|
||||
ok := false
|
||||
var s string
|
||||
// Fast path for simple common case.
|
||||
if len(args) == 1 {
|
||||
s, ok = args[0].(string)
|
||||
}
|
||||
if !ok {
|
||||
for i, arg := range args {
|
||||
a, ok := printableValue(reflect.ValueOf(arg))
|
||||
if ok {
|
||||
args[i] = a
|
||||
} // else left fmt do its thing
|
||||
}
|
||||
s = fmt.Sprint(args...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
1
vendor/github.com/alecthomas/template/go.mod
generated
vendored
Normal file
1
vendor/github.com/alecthomas/template/go.mod
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module github.com/alecthomas/template
|
||||
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
Normal file
108
vendor/github.com/alecthomas/template/helper.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Helper functions to make constructing templates easier.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Functions and methods to parse templates.
|
||||
|
||||
// Must is a helper that wraps a call to a function returning (*Template, error)
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
// var t = template.Must(template.New("name").Parse("text"))
|
||||
func Must(t *Template, err error) *Template {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ParseFiles creates a new Template and parses the template definitions from
|
||||
// the named files. The returned template's name will have the (base) name and
|
||||
// (parsed) contents of the first file. There must be at least one file.
|
||||
// If an error occurs, parsing stops and the returned *Template is nil.
|
||||
func ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(nil, filenames...)
|
||||
}
|
||||
|
||||
// ParseFiles parses the named files and associates the resulting templates with
|
||||
// t. If an error occurs, parsing stops and the returned template is nil;
|
||||
// otherwise it is t. There must be at least one file.
|
||||
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
|
||||
// parseFiles is the helper for the method and function. If the argument
|
||||
// template is nil, it is created from the first file.
|
||||
func parseFiles(t *Template, filenames ...string) (*Template, error) {
|
||||
if len(filenames) == 0 {
|
||||
// Not really a problem, but be consistent.
|
||||
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
||||
}
|
||||
for _, filename := range filenames {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := string(b)
|
||||
name := filepath.Base(filename)
|
||||
// First template becomes return value if not already defined,
|
||||
// and we use that one for subsequent New calls to associate
|
||||
// all the templates together. Also, if this file has the same name
|
||||
// as t, this file becomes the contents of t, so
|
||||
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
||||
// works. Otherwise we create a new template associated with t.
|
||||
var tmpl *Template
|
||||
if t == nil {
|
||||
t = New(name)
|
||||
}
|
||||
if name == t.Name() {
|
||||
tmpl = t
|
||||
} else {
|
||||
tmpl = t.New(name)
|
||||
}
|
||||
_, err = tmpl.Parse(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ParseGlob creates a new Template and parses the template definitions from the
|
||||
// files identified by the pattern, which must match at least one file. The
|
||||
// returned template will have the (base) name and (parsed) contents of the
|
||||
// first file matched by the pattern. ParseGlob is equivalent to calling
|
||||
// ParseFiles with the list of files matched by the pattern.
|
||||
func ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(nil, pattern)
|
||||
}
|
||||
|
||||
// ParseGlob parses the template definitions in the files identified by the
|
||||
// pattern and associates the resulting templates with t. The pattern is
|
||||
// processed by filepath.Glob and must match at least one file. ParseGlob is
|
||||
// equivalent to calling t.ParseFiles with the list of files matched by the
|
||||
// pattern.
|
||||
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
||||
return parseGlob(t, pattern)
|
||||
}
|
||||
|
||||
// parseGlob is the implementation of the function and method ParseGlob.
|
||||
func parseGlob(t *Template, pattern string) (*Template, error) {
|
||||
filenames, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(filenames) == 0 {
|
||||
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
||||
}
|
||||
return parseFiles(t, filenames...)
|
||||
}
|
||||
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
Normal file
556
vendor/github.com/alecthomas/template/parse/lex.go
generated
vendored
Normal file
@@ -0,0 +1,556 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// item represents a token or text string returned from the scanner.
|
||||
type item struct {
|
||||
typ itemType // The type of this item.
|
||||
pos Pos // The starting position, in bytes, of this item in the input string.
|
||||
val string // The value of this item.
|
||||
}
|
||||
|
||||
func (i item) String() string {
|
||||
switch {
|
||||
case i.typ == itemEOF:
|
||||
return "EOF"
|
||||
case i.typ == itemError:
|
||||
return i.val
|
||||
case i.typ > itemKeyword:
|
||||
return fmt.Sprintf("<%s>", i.val)
|
||||
case len(i.val) > 10:
|
||||
return fmt.Sprintf("%.10q...", i.val)
|
||||
}
|
||||
return fmt.Sprintf("%q", i.val)
|
||||
}
|
||||
|
||||
// itemType identifies the type of lex items.
|
||||
type itemType int
|
||||
|
||||
const (
|
||||
itemError itemType = iota // error occurred; value is text of error
|
||||
itemBool // boolean constant
|
||||
itemChar // printable ASCII character; grab bag for comma etc.
|
||||
itemCharConstant // character constant
|
||||
itemComplex // complex constant (1+2i); imaginary is just a number
|
||||
itemColonEquals // colon-equals (':=') introducing a declaration
|
||||
itemEOF
|
||||
itemField // alphanumeric identifier starting with '.'
|
||||
itemIdentifier // alphanumeric identifier not starting with '.'
|
||||
itemLeftDelim // left action delimiter
|
||||
itemLeftParen // '(' inside action
|
||||
itemNumber // simple number, including imaginary
|
||||
itemPipe // pipe symbol
|
||||
itemRawString // raw quoted string (includes quotes)
|
||||
itemRightDelim // right action delimiter
|
||||
itemElideNewline // elide newline after right delim
|
||||
itemRightParen // ')' inside action
|
||||
itemSpace // run of spaces separating arguments
|
||||
itemString // quoted string (includes quotes)
|
||||
itemText // plain text
|
||||
itemVariable // variable starting with '$', such as '$' or '$1' or '$hello'
|
||||
// Keywords appear after all the rest.
|
||||
itemKeyword // used only to delimit the keywords
|
||||
itemDot // the cursor, spelled '.'
|
||||
itemDefine // define keyword
|
||||
itemElse // else keyword
|
||||
itemEnd // end keyword
|
||||
itemIf // if keyword
|
||||
itemNil // the untyped nil constant, easiest to treat as a keyword
|
||||
itemRange // range keyword
|
||||
itemTemplate // template keyword
|
||||
itemWith // with keyword
|
||||
)
|
||||
|
||||
var key = map[string]itemType{
|
||||
".": itemDot,
|
||||
"define": itemDefine,
|
||||
"else": itemElse,
|
||||
"end": itemEnd,
|
||||
"if": itemIf,
|
||||
"range": itemRange,
|
||||
"nil": itemNil,
|
||||
"template": itemTemplate,
|
||||
"with": itemWith,
|
||||
}
|
||||
|
||||
const eof = -1
|
||||
|
||||
// stateFn represents the state of the scanner as a function that returns the next state.
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
// lexer holds the state of the scanner.
|
||||
type lexer struct {
|
||||
name string // the name of the input; used only for error reports
|
||||
input string // the string being scanned
|
||||
leftDelim string // start of action
|
||||
rightDelim string // end of action
|
||||
state stateFn // the next lexing function to enter
|
||||
pos Pos // current position in the input
|
||||
start Pos // start position of this item
|
||||
width Pos // width of last rune read from input
|
||||
lastPos Pos // position of most recent item returned by nextItem
|
||||
items chan item // channel of scanned items
|
||||
parenDepth int // nesting depth of ( ) exprs
|
||||
}
|
||||
|
||||
// next returns the next rune in the input.
|
||||
func (l *lexer) next() rune {
|
||||
if int(l.pos) >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
|
||||
l.width = Pos(w)
|
||||
l.pos += l.width
|
||||
return r
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next rune in the input.
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
// backup steps back one rune. Can only be called once per call of next.
|
||||
func (l *lexer) backup() {
|
||||
l.pos -= l.width
|
||||
}
|
||||
|
||||
// emit passes an item back to the client.
|
||||
func (l *lexer) emit(t itemType) {
|
||||
l.items <- item{t, l.start, l.input[l.start:l.pos]}
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// ignore skips over the pending input before this point.
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.pos
|
||||
}
|
||||
|
||||
// accept consumes the next rune if it's from the valid set.
|
||||
func (l *lexer) accept(valid string) bool {
|
||||
if strings.IndexRune(valid, l.next()) >= 0 {
|
||||
return true
|
||||
}
|
||||
l.backup()
|
||||
return false
|
||||
}
|
||||
|
||||
// acceptRun consumes a run of runes from the valid set.
|
||||
func (l *lexer) acceptRun(valid string) {
|
||||
for strings.IndexRune(valid, l.next()) >= 0 {
|
||||
}
|
||||
l.backup()
|
||||
}
|
||||
|
||||
// lineNumber reports which line we're on, based on the position of
|
||||
// the previous item returned by nextItem. Doing it this way
|
||||
// means we don't have to worry about peek double counting.
|
||||
func (l *lexer) lineNumber() int {
|
||||
return 1 + strings.Count(l.input[:l.lastPos], "\n")
|
||||
}
|
||||
|
||||
// errorf returns an error token and terminates the scan by passing
|
||||
// back a nil pointer that will be the next state, terminating l.nextItem.
|
||||
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
|
||||
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextItem returns the next item from the input.
|
||||
func (l *lexer) nextItem() item {
|
||||
item := <-l.items
|
||||
l.lastPos = item.pos
|
||||
return item
|
||||
}
|
||||
|
||||
// lex creates a new scanner for the input string.
|
||||
func lex(name, input, left, right string) *lexer {
|
||||
if left == "" {
|
||||
left = leftDelim
|
||||
}
|
||||
if right == "" {
|
||||
right = rightDelim
|
||||
}
|
||||
l := &lexer{
|
||||
name: name,
|
||||
input: input,
|
||||
leftDelim: left,
|
||||
rightDelim: right,
|
||||
items: make(chan item),
|
||||
}
|
||||
go l.run()
|
||||
return l
|
||||
}
|
||||
|
||||
// run runs the state machine for the lexer.
|
||||
func (l *lexer) run() {
|
||||
for l.state = lexText; l.state != nil; {
|
||||
l.state = l.state(l)
|
||||
}
|
||||
}
|
||||
|
||||
// state functions
|
||||
|
||||
const (
|
||||
leftDelim = "{{"
|
||||
rightDelim = "}}"
|
||||
leftComment = "/*"
|
||||
rightComment = "*/"
|
||||
)
|
||||
|
||||
// lexText scans until an opening action delimiter, "{{".
|
||||
func lexText(l *lexer) stateFn {
|
||||
for {
|
||||
if strings.HasPrefix(l.input[l.pos:], l.leftDelim) {
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
return lexLeftDelim
|
||||
}
|
||||
if l.next() == eof {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Correctly reached EOF.
|
||||
if l.pos > l.start {
|
||||
l.emit(itemText)
|
||||
}
|
||||
l.emit(itemEOF)
|
||||
return nil
|
||||
}
|
||||
|
||||
// lexLeftDelim scans the left delimiter, which is known to be present.
|
||||
func lexLeftDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.leftDelim))
|
||||
if strings.HasPrefix(l.input[l.pos:], leftComment) {
|
||||
return lexComment
|
||||
}
|
||||
l.emit(itemLeftDelim)
|
||||
l.parenDepth = 0
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexComment scans a comment. The left comment marker is known to be present.
|
||||
func lexComment(l *lexer) stateFn {
|
||||
l.pos += Pos(len(leftComment))
|
||||
i := strings.Index(l.input[l.pos:], rightComment)
|
||||
if i < 0 {
|
||||
return l.errorf("unclosed comment")
|
||||
}
|
||||
l.pos += Pos(i + len(rightComment))
|
||||
if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||
return l.errorf("comment ends before closing delimiter")
|
||||
|
||||
}
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.ignore()
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexRightDelim scans the right delimiter, which is known to be present.
|
||||
func lexRightDelim(l *lexer) stateFn {
|
||||
l.pos += Pos(len(l.rightDelim))
|
||||
l.emit(itemRightDelim)
|
||||
if l.peek() == '\\' {
|
||||
l.pos++
|
||||
l.emit(itemElideNewline)
|
||||
}
|
||||
return lexText
|
||||
}
|
||||
|
||||
// lexInsideAction scans the elements inside action delimiters.
|
||||
func lexInsideAction(l *lexer) stateFn {
|
||||
// Either number, quoted string, or identifier.
|
||||
// Spaces separate arguments; runs of spaces turn into itemSpace.
|
||||
// Pipe symbols separate and are emitted.
|
||||
if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) {
|
||||
if l.parenDepth == 0 {
|
||||
return lexRightDelim
|
||||
}
|
||||
return l.errorf("unclosed left paren")
|
||||
}
|
||||
switch r := l.next(); {
|
||||
case r == eof || isEndOfLine(r):
|
||||
return l.errorf("unclosed action")
|
||||
case isSpace(r):
|
||||
return lexSpace
|
||||
case r == ':':
|
||||
if l.next() != '=' {
|
||||
return l.errorf("expected :=")
|
||||
}
|
||||
l.emit(itemColonEquals)
|
||||
case r == '|':
|
||||
l.emit(itemPipe)
|
||||
case r == '"':
|
||||
return lexQuote
|
||||
case r == '`':
|
||||
return lexRawQuote
|
||||
case r == '$':
|
||||
return lexVariable
|
||||
case r == '\'':
|
||||
return lexChar
|
||||
case r == '.':
|
||||
// special look-ahead for ".field" so we don't break l.backup().
|
||||
if l.pos < Pos(len(l.input)) {
|
||||
r := l.input[l.pos]
|
||||
if r < '0' || '9' < r {
|
||||
return lexField
|
||||
}
|
||||
}
|
||||
fallthrough // '.' can start a number.
|
||||
case r == '+' || r == '-' || ('0' <= r && r <= '9'):
|
||||
l.backup()
|
||||
return lexNumber
|
||||
case isAlphaNumeric(r):
|
||||
l.backup()
|
||||
return lexIdentifier
|
||||
case r == '(':
|
||||
l.emit(itemLeftParen)
|
||||
l.parenDepth++
|
||||
return lexInsideAction
|
||||
case r == ')':
|
||||
l.emit(itemRightParen)
|
||||
l.parenDepth--
|
||||
if l.parenDepth < 0 {
|
||||
return l.errorf("unexpected right paren %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
case r <= unicode.MaxASCII && unicode.IsPrint(r):
|
||||
l.emit(itemChar)
|
||||
return lexInsideAction
|
||||
default:
|
||||
return l.errorf("unrecognized character in action: %#U", r)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexSpace scans a run of space characters.
|
||||
// One space has already been seen.
|
||||
func lexSpace(l *lexer) stateFn {
|
||||
for isSpace(l.peek()) {
|
||||
l.next()
|
||||
}
|
||||
l.emit(itemSpace)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexIdentifier scans an alphanumeric.
|
||||
func lexIdentifier(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case isAlphaNumeric(r):
|
||||
// absorb.
|
||||
default:
|
||||
l.backup()
|
||||
word := l.input[l.start:l.pos]
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
switch {
|
||||
case key[word] > itemKeyword:
|
||||
l.emit(key[word])
|
||||
case word[0] == '.':
|
||||
l.emit(itemField)
|
||||
case word == "true", word == "false":
|
||||
l.emit(itemBool)
|
||||
default:
|
||||
l.emit(itemIdentifier)
|
||||
}
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexField scans a field: .Alphanumeric.
|
||||
// The . has been scanned.
|
||||
func lexField(l *lexer) stateFn {
|
||||
return lexFieldOrVariable(l, itemField)
|
||||
}
|
||||
|
||||
// lexVariable scans a Variable: $Alphanumeric.
|
||||
// The $ has been scanned.
|
||||
func lexVariable(l *lexer) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "$".
|
||||
l.emit(itemVariable)
|
||||
return lexInsideAction
|
||||
}
|
||||
return lexFieldOrVariable(l, itemVariable)
|
||||
}
|
||||
|
||||
// lexVariable scans a field or variable: [.$]Alphanumeric.
|
||||
// The . or $ has been scanned.
|
||||
func lexFieldOrVariable(l *lexer, typ itemType) stateFn {
|
||||
if l.atTerminator() { // Nothing interesting follows -> "." or "$".
|
||||
if typ == itemVariable {
|
||||
l.emit(itemVariable)
|
||||
} else {
|
||||
l.emit(itemDot)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
var r rune
|
||||
for {
|
||||
r = l.next()
|
||||
if !isAlphaNumeric(r) {
|
||||
l.backup()
|
||||
break
|
||||
}
|
||||
}
|
||||
if !l.atTerminator() {
|
||||
return l.errorf("bad character %#U", r)
|
||||
}
|
||||
l.emit(typ)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// atTerminator reports whether the input is at valid termination character to
|
||||
// appear after an identifier. Breaks .X.Y into two pieces. Also catches cases
|
||||
// like "$x+2" not being acceptable without a space, in case we decide one
|
||||
// day to implement arithmetic.
|
||||
func (l *lexer) atTerminator() bool {
|
||||
r := l.peek()
|
||||
if isSpace(r) || isEndOfLine(r) {
|
||||
return true
|
||||
}
|
||||
switch r {
|
||||
case eof, '.', ',', '|', ':', ')', '(':
|
||||
return true
|
||||
}
|
||||
// Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will
|
||||
// succeed but should fail) but only in extremely rare cases caused by willfully
|
||||
// bad choice of delimiter.
|
||||
if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// lexChar scans a character constant. The initial quote is already
|
||||
// scanned. Syntax checking is done by the parser.
|
||||
func lexChar(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated character constant")
|
||||
case '\'':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemCharConstant)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This
|
||||
// isn't a perfect number scanner - for instance it accepts "." and "0x0.2"
|
||||
// and "089" - but when it's wrong the input is invalid and the parser (via
|
||||
// strconv) will notice.
|
||||
func lexNumber(l *lexer) stateFn {
|
||||
if !l.scanNumber() {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
if sign := l.peek(); sign == '+' || sign == '-' {
|
||||
// Complex: 1+2i. No spaces, must end in 'i'.
|
||||
if !l.scanNumber() || l.input[l.pos-1] != 'i' {
|
||||
return l.errorf("bad number syntax: %q", l.input[l.start:l.pos])
|
||||
}
|
||||
l.emit(itemComplex)
|
||||
} else {
|
||||
l.emit(itemNumber)
|
||||
}
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
func (l *lexer) scanNumber() bool {
|
||||
// Optional leading sign.
|
||||
l.accept("+-")
|
||||
// Is it hex?
|
||||
digits := "0123456789"
|
||||
if l.accept("0") && l.accept("xX") {
|
||||
digits = "0123456789abcdefABCDEF"
|
||||
}
|
||||
l.acceptRun(digits)
|
||||
if l.accept(".") {
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun("0123456789")
|
||||
}
|
||||
// Is it imaginary?
|
||||
l.accept("i")
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if isAlphaNumeric(l.peek()) {
|
||||
l.next()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// lexQuote scans a quoted string.
|
||||
func lexQuote(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case '\\':
|
||||
if r := l.next(); r != eof && r != '\n' {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated quoted string")
|
||||
case '"':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// lexRawQuote scans a raw quoted string.
|
||||
func lexRawQuote(l *lexer) stateFn {
|
||||
Loop:
|
||||
for {
|
||||
switch l.next() {
|
||||
case eof, '\n':
|
||||
return l.errorf("unterminated raw quoted string")
|
||||
case '`':
|
||||
break Loop
|
||||
}
|
||||
}
|
||||
l.emit(itemRawString)
|
||||
return lexInsideAction
|
||||
}
|
||||
|
||||
// isSpace reports whether r is a space character.
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t'
|
||||
}
|
||||
|
||||
// isEndOfLine reports whether r is an end-of-line character.
|
||||
func isEndOfLine(r rune) bool {
|
||||
return r == '\r' || r == '\n'
|
||||
}
|
||||
|
||||
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
|
||||
func isAlphaNumeric(r rune) bool {
|
||||
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
|
||||
}
|
||||
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
Normal file
834
vendor/github.com/alecthomas/template/parse/node.go
generated
vendored
Normal file
@@ -0,0 +1,834 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse nodes.
|
||||
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var textFormat = "%s" // Changed to "%q" in tests for better error messages.
|
||||
|
||||
// A Node is an element in the parse tree. The interface is trivial.
|
||||
// The interface contains an unexported method so that only
|
||||
// types local to this package can satisfy it.
|
||||
type Node interface {
|
||||
Type() NodeType
|
||||
String() string
|
||||
// Copy does a deep copy of the Node and all its components.
|
||||
// To avoid type assertions, some XxxNodes also have specialized
|
||||
// CopyXxx methods that return *XxxNode.
|
||||
Copy() Node
|
||||
Position() Pos // byte position of start of node in full original input string
|
||||
// tree returns the containing *Tree.
|
||||
// It is unexported so all implementations of Node are in this package.
|
||||
tree() *Tree
|
||||
}
|
||||
|
||||
// NodeType identifies the type of a parse tree node.
|
||||
type NodeType int
|
||||
|
||||
// Pos represents a byte position in the original input text from which
|
||||
// this template was parsed.
|
||||
type Pos int
|
||||
|
||||
func (p Pos) Position() Pos {
|
||||
return p
|
||||
}
|
||||
|
||||
// Type returns itself and provides an easy default implementation
|
||||
// for embedding in a Node. Embedded in all non-trivial Nodes.
|
||||
func (t NodeType) Type() NodeType {
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
NodeText NodeType = iota // Plain text.
|
||||
NodeAction // A non-control action such as a field evaluation.
|
||||
NodeBool // A boolean constant.
|
||||
NodeChain // A sequence of field accesses.
|
||||
NodeCommand // An element of a pipeline.
|
||||
NodeDot // The cursor, dot.
|
||||
nodeElse // An else action. Not added to tree.
|
||||
nodeEnd // An end action. Not added to tree.
|
||||
NodeField // A field or method name.
|
||||
NodeIdentifier // An identifier; always a function name.
|
||||
NodeIf // An if action.
|
||||
NodeList // A list of Nodes.
|
||||
NodeNil // An untyped nil constant.
|
||||
NodeNumber // A numerical constant.
|
||||
NodePipe // A pipeline of commands.
|
||||
NodeRange // A range action.
|
||||
NodeString // A string constant.
|
||||
NodeTemplate // A template invocation action.
|
||||
NodeVariable // A $ variable.
|
||||
NodeWith // A with action.
|
||||
)
|
||||
|
||||
// Nodes.
|
||||
|
||||
// ListNode holds a sequence of nodes.
|
||||
type ListNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Nodes []Node // The element nodes in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newList(pos Pos) *ListNode {
|
||||
return &ListNode{tr: t, NodeType: NodeList, Pos: pos}
|
||||
}
|
||||
|
||||
func (l *ListNode) append(n Node) {
|
||||
l.Nodes = append(l.Nodes, n)
|
||||
}
|
||||
|
||||
func (l *ListNode) tree() *Tree {
|
||||
return l.tr
|
||||
}
|
||||
|
||||
func (l *ListNode) String() string {
|
||||
b := new(bytes.Buffer)
|
||||
for _, n := range l.Nodes {
|
||||
fmt.Fprint(b, n)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (l *ListNode) CopyList() *ListNode {
|
||||
if l == nil {
|
||||
return l
|
||||
}
|
||||
n := l.tr.newList(l.Pos)
|
||||
for _, elem := range l.Nodes {
|
||||
n.append(elem.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (l *ListNode) Copy() Node {
|
||||
return l.CopyList()
|
||||
}
|
||||
|
||||
// TextNode holds plain text.
|
||||
type TextNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Text []byte // The text; may span newlines.
|
||||
}
|
||||
|
||||
func (t *Tree) newText(pos Pos, text string) *TextNode {
|
||||
return &TextNode{tr: t, NodeType: NodeText, Pos: pos, Text: []byte(text)}
|
||||
}
|
||||
|
||||
func (t *TextNode) String() string {
|
||||
return fmt.Sprintf(textFormat, t.Text)
|
||||
}
|
||||
|
||||
func (t *TextNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TextNode) Copy() Node {
|
||||
return &TextNode{tr: t.tr, NodeType: NodeText, Pos: t.Pos, Text: append([]byte{}, t.Text...)}
|
||||
}
|
||||
|
||||
// PipeNode holds a pipeline with optional declaration
|
||||
type PipeNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Decl []*VariableNode // Variable declarations in lexical order.
|
||||
Cmds []*CommandNode // The commands in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newPipeline(pos Pos, line int, decl []*VariableNode) *PipeNode {
|
||||
return &PipeNode{tr: t, NodeType: NodePipe, Pos: pos, Line: line, Decl: decl}
|
||||
}
|
||||
|
||||
func (p *PipeNode) append(command *CommandNode) {
|
||||
p.Cmds = append(p.Cmds, command)
|
||||
}
|
||||
|
||||
func (p *PipeNode) String() string {
|
||||
s := ""
|
||||
if len(p.Decl) > 0 {
|
||||
for i, v := range p.Decl {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += v.String()
|
||||
}
|
||||
s += " := "
|
||||
}
|
||||
for i, c := range p.Cmds {
|
||||
if i > 0 {
|
||||
s += " | "
|
||||
}
|
||||
s += c.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *PipeNode) tree() *Tree {
|
||||
return p.tr
|
||||
}
|
||||
|
||||
func (p *PipeNode) CopyPipe() *PipeNode {
|
||||
if p == nil {
|
||||
return p
|
||||
}
|
||||
var decl []*VariableNode
|
||||
for _, d := range p.Decl {
|
||||
decl = append(decl, d.Copy().(*VariableNode))
|
||||
}
|
||||
n := p.tr.newPipeline(p.Pos, p.Line, decl)
|
||||
for _, c := range p.Cmds {
|
||||
n.append(c.Copy().(*CommandNode))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *PipeNode) Copy() Node {
|
||||
return p.CopyPipe()
|
||||
}
|
||||
|
||||
// ActionNode holds an action (something bounded by delimiters).
|
||||
// Control actions have their own nodes; ActionNode represents simple
|
||||
// ones such as field evaluations and parenthesized pipelines.
|
||||
type ActionNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Pipe *PipeNode // The pipeline in the action.
|
||||
}
|
||||
|
||||
func (t *Tree) newAction(pos Pos, line int, pipe *PipeNode) *ActionNode {
|
||||
return &ActionNode{tr: t, NodeType: NodeAction, Pos: pos, Line: line, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (a *ActionNode) String() string {
|
||||
return fmt.Sprintf("{{%s}}", a.Pipe)
|
||||
|
||||
}
|
||||
|
||||
func (a *ActionNode) tree() *Tree {
|
||||
return a.tr
|
||||
}
|
||||
|
||||
func (a *ActionNode) Copy() Node {
|
||||
return a.tr.newAction(a.Pos, a.Line, a.Pipe.CopyPipe())
|
||||
|
||||
}
|
||||
|
||||
// CommandNode holds a command (a pipeline inside an evaluating action).
|
||||
type CommandNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Args []Node // Arguments in lexical order: Identifier, field, or constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newCommand(pos Pos) *CommandNode {
|
||||
return &CommandNode{tr: t, NodeType: NodeCommand, Pos: pos}
|
||||
}
|
||||
|
||||
func (c *CommandNode) append(arg Node) {
|
||||
c.Args = append(c.Args, arg)
|
||||
}
|
||||
|
||||
func (c *CommandNode) String() string {
|
||||
s := ""
|
||||
for i, arg := range c.Args {
|
||||
if i > 0 {
|
||||
s += " "
|
||||
}
|
||||
if arg, ok := arg.(*PipeNode); ok {
|
||||
s += "(" + arg.String() + ")"
|
||||
continue
|
||||
}
|
||||
s += arg.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *CommandNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *CommandNode) Copy() Node {
|
||||
if c == nil {
|
||||
return c
|
||||
}
|
||||
n := c.tr.newCommand(c.Pos)
|
||||
for _, c := range c.Args {
|
||||
n.append(c.Copy())
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// IdentifierNode holds an identifier.
|
||||
type IdentifierNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident string // The identifier's name.
|
||||
}
|
||||
|
||||
// NewIdentifier returns a new IdentifierNode with the given identifier name.
|
||||
func NewIdentifier(ident string) *IdentifierNode {
|
||||
return &IdentifierNode{NodeType: NodeIdentifier, Ident: ident}
|
||||
}
|
||||
|
||||
// SetPos sets the position. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetPos(pos Pos) *IdentifierNode {
|
||||
i.Pos = pos
|
||||
return i
|
||||
}
|
||||
|
||||
// SetTree sets the parent tree for the node. NewIdentifier is a public method so we can't modify its signature.
|
||||
// Chained for convenience.
|
||||
// TODO: fix one day?
|
||||
func (i *IdentifierNode) SetTree(t *Tree) *IdentifierNode {
|
||||
i.tr = t
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) String() string {
|
||||
return i.Ident
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) tree() *Tree {
|
||||
return i.tr
|
||||
}
|
||||
|
||||
func (i *IdentifierNode) Copy() Node {
|
||||
return NewIdentifier(i.Ident).SetTree(i.tr).SetPos(i.Pos)
|
||||
}
|
||||
|
||||
// VariableNode holds a list of variable names, possibly with chained field
|
||||
// accesses. The dollar sign is part of the (first) name.
|
||||
type VariableNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // Variable name and fields in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newVariable(pos Pos, ident string) *VariableNode {
|
||||
return &VariableNode{tr: t, NodeType: NodeVariable, Pos: pos, Ident: strings.Split(ident, ".")}
|
||||
}
|
||||
|
||||
func (v *VariableNode) String() string {
|
||||
s := ""
|
||||
for i, id := range v.Ident {
|
||||
if i > 0 {
|
||||
s += "."
|
||||
}
|
||||
s += id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (v *VariableNode) tree() *Tree {
|
||||
return v.tr
|
||||
}
|
||||
|
||||
func (v *VariableNode) Copy() Node {
|
||||
return &VariableNode{tr: v.tr, NodeType: NodeVariable, Pos: v.Pos, Ident: append([]string{}, v.Ident...)}
|
||||
}
|
||||
|
||||
// DotNode holds the special identifier '.'.
|
||||
type DotNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newDot(pos Pos) *DotNode {
|
||||
return &DotNode{tr: t, NodeType: NodeDot, Pos: pos}
|
||||
}
|
||||
|
||||
func (d *DotNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeDot
|
||||
}
|
||||
|
||||
func (d *DotNode) String() string {
|
||||
return "."
|
||||
}
|
||||
|
||||
func (d *DotNode) tree() *Tree {
|
||||
return d.tr
|
||||
}
|
||||
|
||||
func (d *DotNode) Copy() Node {
|
||||
return d.tr.newDot(d.Pos)
|
||||
}
|
||||
|
||||
// NilNode holds the special identifier 'nil' representing an untyped nil constant.
|
||||
type NilNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newNil(pos Pos) *NilNode {
|
||||
return &NilNode{tr: t, NodeType: NodeNil, Pos: pos}
|
||||
}
|
||||
|
||||
func (n *NilNode) Type() NodeType {
|
||||
// Override method on embedded NodeType for API compatibility.
|
||||
// TODO: Not really a problem; could change API without effect but
|
||||
// api tool complains.
|
||||
return NodeNil
|
||||
}
|
||||
|
||||
func (n *NilNode) String() string {
|
||||
return "nil"
|
||||
}
|
||||
|
||||
func (n *NilNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NilNode) Copy() Node {
|
||||
return n.tr.newNil(n.Pos)
|
||||
}
|
||||
|
||||
// FieldNode holds a field (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The period is dropped from each ident.
|
||||
type FieldNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Ident []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newField(pos Pos, ident string) *FieldNode {
|
||||
return &FieldNode{tr: t, NodeType: NodeField, Pos: pos, Ident: strings.Split(ident[1:], ".")} // [1:] to drop leading period
|
||||
}
|
||||
|
||||
func (f *FieldNode) String() string {
|
||||
s := ""
|
||||
for _, id := range f.Ident {
|
||||
s += "." + id
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (f *FieldNode) tree() *Tree {
|
||||
return f.tr
|
||||
}
|
||||
|
||||
func (f *FieldNode) Copy() Node {
|
||||
return &FieldNode{tr: f.tr, NodeType: NodeField, Pos: f.Pos, Ident: append([]string{}, f.Ident...)}
|
||||
}
|
||||
|
||||
// ChainNode holds a term followed by a chain of field accesses (identifier starting with '.').
|
||||
// The names may be chained ('.x.y').
|
||||
// The periods are dropped from each ident.
|
||||
type ChainNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Node Node
|
||||
Field []string // The identifiers in lexical order.
|
||||
}
|
||||
|
||||
func (t *Tree) newChain(pos Pos, node Node) *ChainNode {
|
||||
return &ChainNode{tr: t, NodeType: NodeChain, Pos: pos, Node: node}
|
||||
}
|
||||
|
||||
// Add adds the named field (which should start with a period) to the end of the chain.
|
||||
func (c *ChainNode) Add(field string) {
|
||||
if len(field) == 0 || field[0] != '.' {
|
||||
panic("no dot in field")
|
||||
}
|
||||
field = field[1:] // Remove leading dot.
|
||||
if field == "" {
|
||||
panic("empty field")
|
||||
}
|
||||
c.Field = append(c.Field, field)
|
||||
}
|
||||
|
||||
func (c *ChainNode) String() string {
|
||||
s := c.Node.String()
|
||||
if _, ok := c.Node.(*PipeNode); ok {
|
||||
s = "(" + s + ")"
|
||||
}
|
||||
for _, field := range c.Field {
|
||||
s += "." + field
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (c *ChainNode) tree() *Tree {
|
||||
return c.tr
|
||||
}
|
||||
|
||||
func (c *ChainNode) Copy() Node {
|
||||
return &ChainNode{tr: c.tr, NodeType: NodeChain, Pos: c.Pos, Node: c.Node, Field: append([]string{}, c.Field...)}
|
||||
}
|
||||
|
||||
// BoolNode holds a boolean constant.
|
||||
type BoolNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
True bool // The value of the boolean constant.
|
||||
}
|
||||
|
||||
func (t *Tree) newBool(pos Pos, true bool) *BoolNode {
|
||||
return &BoolNode{tr: t, NodeType: NodeBool, Pos: pos, True: true}
|
||||
}
|
||||
|
||||
func (b *BoolNode) String() string {
|
||||
if b.True {
|
||||
return "true"
|
||||
}
|
||||
return "false"
|
||||
}
|
||||
|
||||
func (b *BoolNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BoolNode) Copy() Node {
|
||||
return b.tr.newBool(b.Pos, b.True)
|
||||
}
|
||||
|
||||
// NumberNode holds a number: signed or unsigned integer, float, or complex.
|
||||
// The value is parsed and stored under all the types that can represent the value.
|
||||
// This simulates in a small amount of code the behavior of Go's ideal constants.
|
||||
type NumberNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
IsInt bool // Number has an integral value.
|
||||
IsUint bool // Number has an unsigned integral value.
|
||||
IsFloat bool // Number has a floating-point value.
|
||||
IsComplex bool // Number is complex.
|
||||
Int64 int64 // The signed integer value.
|
||||
Uint64 uint64 // The unsigned integer value.
|
||||
Float64 float64 // The floating-point value.
|
||||
Complex128 complex128 // The complex value.
|
||||
Text string // The original textual representation from the input.
|
||||
}
|
||||
|
||||
func (t *Tree) newNumber(pos Pos, text string, typ itemType) (*NumberNode, error) {
|
||||
n := &NumberNode{tr: t, NodeType: NodeNumber, Pos: pos, Text: text}
|
||||
switch typ {
|
||||
case itemCharConstant:
|
||||
rune, _, tail, err := strconv.UnquoteChar(text[1:], text[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tail != "'" {
|
||||
return nil, fmt.Errorf("malformed character constant: %s", text)
|
||||
}
|
||||
n.Int64 = int64(rune)
|
||||
n.IsInt = true
|
||||
n.Uint64 = uint64(rune)
|
||||
n.IsUint = true
|
||||
n.Float64 = float64(rune) // odd but those are the rules.
|
||||
n.IsFloat = true
|
||||
return n, nil
|
||||
case itemComplex:
|
||||
// fmt.Sscan can parse the pair, so let it do the work.
|
||||
if _, err := fmt.Sscan(text, &n.Complex128); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
n.IsComplex = true
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
// Imaginary constants can only be complex unless they are zero.
|
||||
if len(text) > 0 && text[len(text)-1] == 'i' {
|
||||
f, err := strconv.ParseFloat(text[:len(text)-1], 64)
|
||||
if err == nil {
|
||||
n.IsComplex = true
|
||||
n.Complex128 = complex(0, f)
|
||||
n.simplifyComplex()
|
||||
return n, nil
|
||||
}
|
||||
}
|
||||
// Do integer test first so we get 0x123 etc.
|
||||
u, err := strconv.ParseUint(text, 0, 64) // will fail for -0; fixed below.
|
||||
if err == nil {
|
||||
n.IsUint = true
|
||||
n.Uint64 = u
|
||||
}
|
||||
i, err := strconv.ParseInt(text, 0, 64)
|
||||
if err == nil {
|
||||
n.IsInt = true
|
||||
n.Int64 = i
|
||||
if i == 0 {
|
||||
n.IsUint = true // in case of -0.
|
||||
n.Uint64 = u
|
||||
}
|
||||
}
|
||||
// If an integer extraction succeeded, promote the float.
|
||||
if n.IsInt {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Int64)
|
||||
} else if n.IsUint {
|
||||
n.IsFloat = true
|
||||
n.Float64 = float64(n.Uint64)
|
||||
} else {
|
||||
f, err := strconv.ParseFloat(text, 64)
|
||||
if err == nil {
|
||||
n.IsFloat = true
|
||||
n.Float64 = f
|
||||
// If a floating-point extraction succeeded, extract the int if needed.
|
||||
if !n.IsInt && float64(int64(f)) == f {
|
||||
n.IsInt = true
|
||||
n.Int64 = int64(f)
|
||||
}
|
||||
if !n.IsUint && float64(uint64(f)) == f {
|
||||
n.IsUint = true
|
||||
n.Uint64 = uint64(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !n.IsInt && !n.IsUint && !n.IsFloat {
|
||||
return nil, fmt.Errorf("illegal number syntax: %q", text)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// simplifyComplex pulls out any other types that are represented by the complex number.
|
||||
// These all require that the imaginary part be zero.
|
||||
func (n *NumberNode) simplifyComplex() {
|
||||
n.IsFloat = imag(n.Complex128) == 0
|
||||
if n.IsFloat {
|
||||
n.Float64 = real(n.Complex128)
|
||||
n.IsInt = float64(int64(n.Float64)) == n.Float64
|
||||
if n.IsInt {
|
||||
n.Int64 = int64(n.Float64)
|
||||
}
|
||||
n.IsUint = float64(uint64(n.Float64)) == n.Float64
|
||||
if n.IsUint {
|
||||
n.Uint64 = uint64(n.Float64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NumberNode) String() string {
|
||||
return n.Text
|
||||
}
|
||||
|
||||
func (n *NumberNode) tree() *Tree {
|
||||
return n.tr
|
||||
}
|
||||
|
||||
func (n *NumberNode) Copy() Node {
|
||||
nn := new(NumberNode)
|
||||
*nn = *n // Easy, fast, correct.
|
||||
return nn
|
||||
}
|
||||
|
||||
// StringNode holds a string constant. The value has been "unquoted".
|
||||
type StringNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Quoted string // The original text of the string, with quotes.
|
||||
Text string // The string, after quote processing.
|
||||
}
|
||||
|
||||
func (t *Tree) newString(pos Pos, orig, text string) *StringNode {
|
||||
return &StringNode{tr: t, NodeType: NodeString, Pos: pos, Quoted: orig, Text: text}
|
||||
}
|
||||
|
||||
func (s *StringNode) String() string {
|
||||
return s.Quoted
|
||||
}
|
||||
|
||||
func (s *StringNode) tree() *Tree {
|
||||
return s.tr
|
||||
}
|
||||
|
||||
func (s *StringNode) Copy() Node {
|
||||
return s.tr.newString(s.Pos, s.Quoted, s.Text)
|
||||
}
|
||||
|
||||
// endNode represents an {{end}} action.
|
||||
// It does not appear in the final parse tree.
|
||||
type endNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
}
|
||||
|
||||
func (t *Tree) newEnd(pos Pos) *endNode {
|
||||
return &endNode{tr: t, NodeType: nodeEnd, Pos: pos}
|
||||
}
|
||||
|
||||
func (e *endNode) String() string {
|
||||
return "{{end}}"
|
||||
}
|
||||
|
||||
func (e *endNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *endNode) Copy() Node {
|
||||
return e.tr.newEnd(e.Pos)
|
||||
}
|
||||
|
||||
// elseNode represents an {{else}} action. Does not appear in the final tree.
|
||||
type elseNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
}
|
||||
|
||||
func (t *Tree) newElse(pos Pos, line int) *elseNode {
|
||||
return &elseNode{tr: t, NodeType: nodeElse, Pos: pos, Line: line}
|
||||
}
|
||||
|
||||
func (e *elseNode) Type() NodeType {
|
||||
return nodeElse
|
||||
}
|
||||
|
||||
func (e *elseNode) String() string {
|
||||
return "{{else}}"
|
||||
}
|
||||
|
||||
func (e *elseNode) tree() *Tree {
|
||||
return e.tr
|
||||
}
|
||||
|
||||
func (e *elseNode) Copy() Node {
|
||||
return e.tr.newElse(e.Pos, e.Line)
|
||||
}
|
||||
|
||||
// BranchNode is the common representation of if, range, and with.
|
||||
type BranchNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Pipe *PipeNode // The pipeline to be evaluated.
|
||||
List *ListNode // What to execute if the value is non-empty.
|
||||
ElseList *ListNode // What to execute if the value is empty (nil if absent).
|
||||
}
|
||||
|
||||
func (b *BranchNode) String() string {
|
||||
name := ""
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
name = "if"
|
||||
case NodeRange:
|
||||
name = "range"
|
||||
case NodeWith:
|
||||
name = "with"
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
if b.ElseList != nil {
|
||||
return fmt.Sprintf("{{%s %s}}%s{{else}}%s{{end}}", name, b.Pipe, b.List, b.ElseList)
|
||||
}
|
||||
return fmt.Sprintf("{{%s %s}}%s{{end}}", name, b.Pipe, b.List)
|
||||
}
|
||||
|
||||
func (b *BranchNode) tree() *Tree {
|
||||
return b.tr
|
||||
}
|
||||
|
||||
func (b *BranchNode) Copy() Node {
|
||||
switch b.NodeType {
|
||||
case NodeIf:
|
||||
return b.tr.newIf(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeRange:
|
||||
return b.tr.newRange(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
case NodeWith:
|
||||
return b.tr.newWith(b.Pos, b.Line, b.Pipe, b.List, b.ElseList)
|
||||
default:
|
||||
panic("unknown branch type")
|
||||
}
|
||||
}
|
||||
|
||||
// IfNode represents an {{if}} action and its commands.
|
||||
type IfNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newIf(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *IfNode {
|
||||
return &IfNode{BranchNode{tr: t, NodeType: NodeIf, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (i *IfNode) Copy() Node {
|
||||
return i.tr.newIf(i.Pos, i.Line, i.Pipe.CopyPipe(), i.List.CopyList(), i.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// RangeNode represents a {{range}} action and its commands.
|
||||
type RangeNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newRange(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *RangeNode {
|
||||
return &RangeNode{BranchNode{tr: t, NodeType: NodeRange, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (r *RangeNode) Copy() Node {
|
||||
return r.tr.newRange(r.Pos, r.Line, r.Pipe.CopyPipe(), r.List.CopyList(), r.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// WithNode represents a {{with}} action and its commands.
|
||||
type WithNode struct {
|
||||
BranchNode
|
||||
}
|
||||
|
||||
func (t *Tree) newWith(pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) *WithNode {
|
||||
return &WithNode{BranchNode{tr: t, NodeType: NodeWith, Pos: pos, Line: line, Pipe: pipe, List: list, ElseList: elseList}}
|
||||
}
|
||||
|
||||
func (w *WithNode) Copy() Node {
|
||||
return w.tr.newWith(w.Pos, w.Line, w.Pipe.CopyPipe(), w.List.CopyList(), w.ElseList.CopyList())
|
||||
}
|
||||
|
||||
// TemplateNode represents a {{template}} action.
|
||||
type TemplateNode struct {
|
||||
NodeType
|
||||
Pos
|
||||
tr *Tree
|
||||
Line int // The line number in the input (deprecated; kept for compatibility)
|
||||
Name string // The name of the template (unquoted).
|
||||
Pipe *PipeNode // The command to evaluate as dot for the template.
|
||||
}
|
||||
|
||||
func (t *Tree) newTemplate(pos Pos, line int, name string, pipe *PipeNode) *TemplateNode {
|
||||
return &TemplateNode{tr: t, NodeType: NodeTemplate, Pos: pos, Line: line, Name: name, Pipe: pipe}
|
||||
}
|
||||
|
||||
func (t *TemplateNode) String() string {
|
||||
if t.Pipe == nil {
|
||||
return fmt.Sprintf("{{template %q}}", t.Name)
|
||||
}
|
||||
return fmt.Sprintf("{{template %q %s}}", t.Name, t.Pipe)
|
||||
}
|
||||
|
||||
func (t *TemplateNode) tree() *Tree {
|
||||
return t.tr
|
||||
}
|
||||
|
||||
func (t *TemplateNode) Copy() Node {
|
||||
return t.tr.newTemplate(t.Pos, t.Line, t.Name, t.Pipe.CopyPipe())
|
||||
}
|
||||
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
Normal file
700
vendor/github.com/alecthomas/template/parse/parse.go
generated
vendored
Normal file
@@ -0,0 +1,700 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package parse builds parse trees for templates as defined by text/template
|
||||
// and html/template. Clients should use those packages to construct templates
|
||||
// rather than this one, which provides shared internal data structures not
|
||||
// intended for general use.
|
||||
package parse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Tree is the representation of a single parsed template.
|
||||
type Tree struct {
|
||||
Name string // name of the template represented by the tree.
|
||||
ParseName string // name of the top-level template during parsing, for error messages.
|
||||
Root *ListNode // top-level root of the tree.
|
||||
text string // text parsed to create the template (or its parent)
|
||||
// Parsing only; cleared after parse.
|
||||
funcs []map[string]interface{}
|
||||
lex *lexer
|
||||
token [3]item // three-token lookahead for parser.
|
||||
peekCount int
|
||||
vars []string // variables defined at the moment.
|
||||
}
|
||||
|
||||
// Copy returns a copy of the Tree. Any parsing state is discarded.
|
||||
func (t *Tree) Copy() *Tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return &Tree{
|
||||
Name: t.Name,
|
||||
ParseName: t.ParseName,
|
||||
Root: t.Root.CopyList(),
|
||||
text: t.text,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse returns a map from template name to parse.Tree, created by parsing the
|
||||
// templates described in the argument string. The top-level template will be
|
||||
// given the specified name. If an error is encountered, parsing stops and an
|
||||
// empty map is returned with the error.
|
||||
func Parse(name, text, leftDelim, rightDelim string, funcs ...map[string]interface{}) (treeSet map[string]*Tree, err error) {
|
||||
treeSet = make(map[string]*Tree)
|
||||
t := New(name)
|
||||
t.text = text
|
||||
_, err = t.Parse(text, leftDelim, rightDelim, treeSet, funcs...)
|
||||
return
|
||||
}
|
||||
|
||||
// next returns the next token.
|
||||
func (t *Tree) next() item {
|
||||
if t.peekCount > 0 {
|
||||
t.peekCount--
|
||||
} else {
|
||||
t.token[0] = t.lex.nextItem()
|
||||
}
|
||||
return t.token[t.peekCount]
|
||||
}
|
||||
|
||||
// backup backs the input stream up one token.
|
||||
func (t *Tree) backup() {
|
||||
t.peekCount++
|
||||
}
|
||||
|
||||
// backup2 backs the input stream up two tokens.
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup2(t1 item) {
|
||||
t.token[1] = t1
|
||||
t.peekCount = 2
|
||||
}
|
||||
|
||||
// backup3 backs the input stream up three tokens
|
||||
// The zeroth token is already there.
|
||||
func (t *Tree) backup3(t2, t1 item) { // Reverse order: we're pushing back.
|
||||
t.token[1] = t1
|
||||
t.token[2] = t2
|
||||
t.peekCount = 3
|
||||
}
|
||||
|
||||
// peek returns but does not consume the next token.
|
||||
func (t *Tree) peek() item {
|
||||
if t.peekCount > 0 {
|
||||
return t.token[t.peekCount-1]
|
||||
}
|
||||
t.peekCount = 1
|
||||
t.token[0] = t.lex.nextItem()
|
||||
return t.token[0]
|
||||
}
|
||||
|
||||
// nextNonSpace returns the next non-space token.
|
||||
func (t *Tree) nextNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// peekNonSpace returns but does not consume the next non-space token.
|
||||
func (t *Tree) peekNonSpace() (token item) {
|
||||
for {
|
||||
token = t.next()
|
||||
if token.typ != itemSpace {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.backup()
|
||||
return token
|
||||
}
|
||||
|
||||
// Parsing.
|
||||
|
||||
// New allocates a new parse tree with the given name.
|
||||
func New(name string, funcs ...map[string]interface{}) *Tree {
|
||||
return &Tree{
|
||||
Name: name,
|
||||
funcs: funcs,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContext returns a textual representation of the location of the node in the input text.
|
||||
// The receiver is only used when the node does not have a pointer to the tree inside,
|
||||
// which can occur in old code.
|
||||
func (t *Tree) ErrorContext(n Node) (location, context string) {
|
||||
pos := int(n.Position())
|
||||
tree := n.tree()
|
||||
if tree == nil {
|
||||
tree = t
|
||||
}
|
||||
text := tree.text[:pos]
|
||||
byteNum := strings.LastIndex(text, "\n")
|
||||
if byteNum == -1 {
|
||||
byteNum = pos // On first line.
|
||||
} else {
|
||||
byteNum++ // After the newline.
|
||||
byteNum = pos - byteNum
|
||||
}
|
||||
lineNum := 1 + strings.Count(text, "\n")
|
||||
context = n.String()
|
||||
if len(context) > 20 {
|
||||
context = fmt.Sprintf("%.20s...", context)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d:%d", tree.ParseName, lineNum, byteNum), context
|
||||
}
|
||||
|
||||
// errorf formats the error and terminates processing.
|
||||
func (t *Tree) errorf(format string, args ...interface{}) {
|
||||
t.Root = nil
|
||||
format = fmt.Sprintf("template: %s:%d: %s", t.ParseName, t.lex.lineNumber(), format)
|
||||
panic(fmt.Errorf(format, args...))
|
||||
}
|
||||
|
||||
// error terminates processing.
|
||||
func (t *Tree) error(err error) {
|
||||
t.errorf("%s", err)
|
||||
}
|
||||
|
||||
// expect consumes the next token and guarantees it has the required type.
|
||||
func (t *Tree) expect(expected itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// expectOneOf consumes the next token and guarantees it has one of the required types.
|
||||
func (t *Tree) expectOneOf(expected1, expected2 itemType, context string) item {
|
||||
token := t.nextNonSpace()
|
||||
if token.typ != expected1 && token.typ != expected2 {
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
// unexpected complains about the token and terminates processing.
|
||||
func (t *Tree) unexpected(token item, context string) {
|
||||
t.errorf("unexpected %s in %s", token, context)
|
||||
}
|
||||
|
||||
// recover is the handler that turns panics into returns from the top level of Parse.
|
||||
func (t *Tree) recover(errp *error) {
|
||||
e := recover()
|
||||
if e != nil {
|
||||
if _, ok := e.(runtime.Error); ok {
|
||||
panic(e)
|
||||
}
|
||||
if t != nil {
|
||||
t.stopParse()
|
||||
}
|
||||
*errp = e.(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// startParse initializes the parser, using the lexer.
|
||||
func (t *Tree) startParse(funcs []map[string]interface{}, lex *lexer) {
|
||||
t.Root = nil
|
||||
t.lex = lex
|
||||
t.vars = []string{"$"}
|
||||
t.funcs = funcs
|
||||
}
|
||||
|
||||
// stopParse terminates parsing.
|
||||
func (t *Tree) stopParse() {
|
||||
t.lex = nil
|
||||
t.vars = nil
|
||||
t.funcs = nil
|
||||
}
|
||||
|
||||
// Parse parses the template definition string to construct a representation of
|
||||
// the template for execution. If either action delimiter string is empty, the
|
||||
// default ("{{" or "}}") is used. Embedded template definitions are added to
|
||||
// the treeSet map.
|
||||
func (t *Tree) Parse(text, leftDelim, rightDelim string, treeSet map[string]*Tree, funcs ...map[string]interface{}) (tree *Tree, err error) {
|
||||
defer t.recover(&err)
|
||||
t.ParseName = t.Name
|
||||
t.startParse(funcs, lex(t.Name, text, leftDelim, rightDelim))
|
||||
t.text = text
|
||||
t.parse(treeSet)
|
||||
t.add(treeSet)
|
||||
t.stopParse()
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// add adds tree to the treeSet.
|
||||
func (t *Tree) add(treeSet map[string]*Tree) {
|
||||
tree := treeSet[t.Name]
|
||||
if tree == nil || IsEmptyTree(tree.Root) {
|
||||
treeSet[t.Name] = t
|
||||
return
|
||||
}
|
||||
if !IsEmptyTree(t.Root) {
|
||||
t.errorf("template: multiple definition of template %q", t.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEmptyTree reports whether this tree (node) is empty of everything but space.
|
||||
func IsEmptyTree(n Node) bool {
|
||||
switch n := n.(type) {
|
||||
case nil:
|
||||
return true
|
||||
case *ActionNode:
|
||||
case *IfNode:
|
||||
case *ListNode:
|
||||
for _, node := range n.Nodes {
|
||||
if !IsEmptyTree(node) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *RangeNode:
|
||||
case *TemplateNode:
|
||||
case *TextNode:
|
||||
return len(bytes.TrimSpace(n.Text)) == 0
|
||||
case *WithNode:
|
||||
default:
|
||||
panic("unknown node: " + n.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// parse is the top-level parser for a template, essentially the same
|
||||
// as itemList except it also parses {{define}} actions.
|
||||
// It runs to EOF.
|
||||
func (t *Tree) parse(treeSet map[string]*Tree) (next Node) {
|
||||
t.Root = t.newList(t.peek().pos)
|
||||
for t.peek().typ != itemEOF {
|
||||
if t.peek().typ == itemLeftDelim {
|
||||
delim := t.next()
|
||||
if t.nextNonSpace().typ == itemDefine {
|
||||
newT := New("definition") // name will be updated once we know it.
|
||||
newT.text = t.text
|
||||
newT.ParseName = t.ParseName
|
||||
newT.startParse(t.funcs, t.lex)
|
||||
newT.parseDefinition(treeSet)
|
||||
continue
|
||||
}
|
||||
t.backup2(delim)
|
||||
}
|
||||
n := t.textOrAction()
|
||||
if n.Type() == nodeEnd {
|
||||
t.errorf("unexpected %s", n)
|
||||
}
|
||||
t.Root.append(n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseDefinition parses a {{define}} ... {{end}} template definition and
|
||||
// installs the definition in the treeSet map. The "define" keyword has already
|
||||
// been scanned.
|
||||
func (t *Tree) parseDefinition(treeSet map[string]*Tree) {
|
||||
const context = "define clause"
|
||||
name := t.expectOneOf(itemString, itemRawString, context)
|
||||
var err error
|
||||
t.Name, err = strconv.Unquote(name.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
t.expect(itemRightDelim, context)
|
||||
var end Node
|
||||
t.Root, end = t.itemList()
|
||||
if end.Type() != nodeEnd {
|
||||
t.errorf("unexpected %s in %s", end, context)
|
||||
}
|
||||
t.add(treeSet)
|
||||
t.stopParse()
|
||||
}
|
||||
|
||||
// itemList:
|
||||
// textOrAction*
|
||||
// Terminates at {{end}} or {{else}}, returned separately.
|
||||
func (t *Tree) itemList() (list *ListNode, next Node) {
|
||||
list = t.newList(t.peekNonSpace().pos)
|
||||
for t.peekNonSpace().typ != itemEOF {
|
||||
n := t.textOrAction()
|
||||
switch n.Type() {
|
||||
case nodeEnd, nodeElse:
|
||||
return list, n
|
||||
}
|
||||
list.append(n)
|
||||
}
|
||||
t.errorf("unexpected EOF")
|
||||
return
|
||||
}
|
||||
|
||||
// textOrAction:
|
||||
// text | action
|
||||
func (t *Tree) textOrAction() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemElideNewline:
|
||||
return t.elideNewline()
|
||||
case itemText:
|
||||
return t.newText(token.pos, token.val)
|
||||
case itemLeftDelim:
|
||||
return t.action()
|
||||
default:
|
||||
t.unexpected(token, "input")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// elideNewline:
|
||||
// Remove newlines trailing rightDelim if \\ is present.
|
||||
func (t *Tree) elideNewline() Node {
|
||||
token := t.peek()
|
||||
if token.typ != itemText {
|
||||
t.unexpected(token, "input")
|
||||
return nil
|
||||
}
|
||||
|
||||
t.next()
|
||||
stripped := strings.TrimLeft(token.val, "\n\r")
|
||||
diff := len(token.val) - len(stripped)
|
||||
if diff > 0 {
|
||||
// This is a bit nasty. We mutate the token in-place to remove
|
||||
// preceding newlines.
|
||||
token.pos += Pos(diff)
|
||||
token.val = stripped
|
||||
}
|
||||
return t.newText(token.pos, token.val)
|
||||
}
|
||||
|
||||
// Action:
|
||||
// control
|
||||
// command ("|" command)*
|
||||
// Left delim is past. Now get actions.
|
||||
// First word could be a keyword such as range.
|
||||
func (t *Tree) action() (n Node) {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemElse:
|
||||
return t.elseControl()
|
||||
case itemEnd:
|
||||
return t.endControl()
|
||||
case itemIf:
|
||||
return t.ifControl()
|
||||
case itemRange:
|
||||
return t.rangeControl()
|
||||
case itemTemplate:
|
||||
return t.templateControl()
|
||||
case itemWith:
|
||||
return t.withControl()
|
||||
}
|
||||
t.backup()
|
||||
// Do not pop variables; they persist until "end".
|
||||
return t.newAction(t.peek().pos, t.lex.lineNumber(), t.pipeline("command"))
|
||||
}
|
||||
|
||||
// Pipeline:
|
||||
// declarations? command ('|' command)*
|
||||
func (t *Tree) pipeline(context string) (pipe *PipeNode) {
|
||||
var decl []*VariableNode
|
||||
pos := t.peekNonSpace().pos
|
||||
// Are there declarations?
|
||||
for {
|
||||
if v := t.peekNonSpace(); v.typ == itemVariable {
|
||||
t.next()
|
||||
// Since space is a token, we need 3-token look-ahead here in the worst case:
|
||||
// in "$x foo" we need to read "foo" (as opposed to ":=") to know that $x is an
|
||||
// argument variable rather than a declaration. So remember the token
|
||||
// adjacent to the variable so we can push it back if necessary.
|
||||
tokenAfterVariable := t.peek()
|
||||
if next := t.peekNonSpace(); next.typ == itemColonEquals || (next.typ == itemChar && next.val == ",") {
|
||||
t.nextNonSpace()
|
||||
variable := t.newVariable(v.pos, v.val)
|
||||
decl = append(decl, variable)
|
||||
t.vars = append(t.vars, v.val)
|
||||
if next.typ == itemChar && next.val == "," {
|
||||
if context == "range" && len(decl) < 2 {
|
||||
continue
|
||||
}
|
||||
t.errorf("too many declarations in %s", context)
|
||||
}
|
||||
} else if tokenAfterVariable.typ == itemSpace {
|
||||
t.backup3(v, tokenAfterVariable)
|
||||
} else {
|
||||
t.backup2(v)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
pipe = t.newPipeline(pos, t.lex.lineNumber(), decl)
|
||||
for {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemRightDelim, itemRightParen:
|
||||
if len(pipe.Cmds) == 0 {
|
||||
t.errorf("missing value for %s", context)
|
||||
}
|
||||
if token.typ == itemRightParen {
|
||||
t.backup()
|
||||
}
|
||||
return
|
||||
case itemBool, itemCharConstant, itemComplex, itemDot, itemField, itemIdentifier,
|
||||
itemNumber, itemNil, itemRawString, itemString, itemVariable, itemLeftParen:
|
||||
t.backup()
|
||||
pipe.append(t.command())
|
||||
default:
|
||||
t.unexpected(token, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) parseControl(allowElseIf bool, context string) (pos Pos, line int, pipe *PipeNode, list, elseList *ListNode) {
|
||||
defer t.popVars(len(t.vars))
|
||||
line = t.lex.lineNumber()
|
||||
pipe = t.pipeline(context)
|
||||
var next Node
|
||||
list, next = t.itemList()
|
||||
switch next.Type() {
|
||||
case nodeEnd: //done
|
||||
case nodeElse:
|
||||
if allowElseIf {
|
||||
// Special case for "else if". If the "else" is followed immediately by an "if",
|
||||
// the elseControl will have left the "if" token pending. Treat
|
||||
// {{if a}}_{{else if b}}_{{end}}
|
||||
// as
|
||||
// {{if a}}_{{else}}{{if b}}_{{end}}{{end}}.
|
||||
// To do this, parse the if as usual and stop at it {{end}}; the subsequent{{end}}
|
||||
// is assumed. This technique works even for long if-else-if chains.
|
||||
// TODO: Should we allow else-if in with and range?
|
||||
if t.peek().typ == itemIf {
|
||||
t.next() // Consume the "if" token.
|
||||
elseList = t.newList(next.Position())
|
||||
elseList.append(t.ifControl())
|
||||
// Do not consume the next item - only one {{end}} required.
|
||||
break
|
||||
}
|
||||
}
|
||||
elseList, next = t.itemList()
|
||||
if next.Type() != nodeEnd {
|
||||
t.errorf("expected end; found %s", next)
|
||||
}
|
||||
}
|
||||
return pipe.Position(), line, pipe, list, elseList
|
||||
}
|
||||
|
||||
// If:
|
||||
// {{if pipeline}} itemList {{end}}
|
||||
// {{if pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) ifControl() Node {
|
||||
return t.newIf(t.parseControl(true, "if"))
|
||||
}
|
||||
|
||||
// Range:
|
||||
// {{range pipeline}} itemList {{end}}
|
||||
// {{range pipeline}} itemList {{else}} itemList {{end}}
|
||||
// Range keyword is past.
|
||||
func (t *Tree) rangeControl() Node {
|
||||
return t.newRange(t.parseControl(false, "range"))
|
||||
}
|
||||
|
||||
// With:
|
||||
// {{with pipeline}} itemList {{end}}
|
||||
// {{with pipeline}} itemList {{else}} itemList {{end}}
|
||||
// If keyword is past.
|
||||
func (t *Tree) withControl() Node {
|
||||
return t.newWith(t.parseControl(false, "with"))
|
||||
}
|
||||
|
||||
// End:
|
||||
// {{end}}
|
||||
// End keyword is past.
|
||||
func (t *Tree) endControl() Node {
|
||||
return t.newEnd(t.expect(itemRightDelim, "end").pos)
|
||||
}
|
||||
|
||||
// Else:
|
||||
// {{else}}
|
||||
// Else keyword is past.
|
||||
func (t *Tree) elseControl() Node {
|
||||
// Special case for "else if".
|
||||
peek := t.peekNonSpace()
|
||||
if peek.typ == itemIf {
|
||||
// We see "{{else if ... " but in effect rewrite it to {{else}}{{if ... ".
|
||||
return t.newElse(peek.pos, t.lex.lineNumber())
|
||||
}
|
||||
return t.newElse(t.expect(itemRightDelim, "else").pos, t.lex.lineNumber())
|
||||
}
|
||||
|
||||
// Template:
|
||||
// {{template stringValue pipeline}}
|
||||
// Template keyword is past. The name must be something that can evaluate
|
||||
// to a string.
|
||||
func (t *Tree) templateControl() Node {
|
||||
var name string
|
||||
token := t.nextNonSpace()
|
||||
switch token.typ {
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
name = s
|
||||
default:
|
||||
t.unexpected(token, "template invocation")
|
||||
}
|
||||
var pipe *PipeNode
|
||||
if t.nextNonSpace().typ != itemRightDelim {
|
||||
t.backup()
|
||||
// Do not pop variables; they persist until "end".
|
||||
pipe = t.pipeline("template")
|
||||
}
|
||||
return t.newTemplate(token.pos, t.lex.lineNumber(), name, pipe)
|
||||
}
|
||||
|
||||
// command:
|
||||
// operand (space operand)*
|
||||
// space-separated arguments up to a pipeline character or right delimiter.
|
||||
// we consume the pipe character but leave the right delim to terminate the action.
|
||||
func (t *Tree) command() *CommandNode {
|
||||
cmd := t.newCommand(t.peekNonSpace().pos)
|
||||
for {
|
||||
t.peekNonSpace() // skip leading spaces.
|
||||
operand := t.operand()
|
||||
if operand != nil {
|
||||
cmd.append(operand)
|
||||
}
|
||||
switch token := t.next(); token.typ {
|
||||
case itemSpace:
|
||||
continue
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemRightDelim, itemRightParen:
|
||||
t.backup()
|
||||
case itemPipe:
|
||||
default:
|
||||
t.errorf("unexpected %s in operand; missing space?", token)
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(cmd.Args) == 0 {
|
||||
t.errorf("empty command")
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// operand:
|
||||
// term .Field*
|
||||
// An operand is a space-separated component of a command,
|
||||
// a term possibly followed by field accesses.
|
||||
// A nil return means the next item is not an operand.
|
||||
func (t *Tree) operand() Node {
|
||||
node := t.term()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
if t.peek().typ == itemField {
|
||||
chain := t.newChain(t.peek().pos, node)
|
||||
for t.peek().typ == itemField {
|
||||
chain.Add(t.next().val)
|
||||
}
|
||||
// Compatibility with original API: If the term is of type NodeField
|
||||
// or NodeVariable, just put more fields on the original.
|
||||
// Otherwise, keep the Chain node.
|
||||
// TODO: Switch to Chains always when we can.
|
||||
switch node.Type() {
|
||||
case NodeField:
|
||||
node = t.newField(chain.Position(), chain.String())
|
||||
case NodeVariable:
|
||||
node = t.newVariable(chain.Position(), chain.String())
|
||||
default:
|
||||
node = chain
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// term:
|
||||
// literal (number, string, nil, boolean)
|
||||
// function (identifier)
|
||||
// .
|
||||
// .Field
|
||||
// $
|
||||
// '(' pipeline ')'
|
||||
// A term is a simple "expression".
|
||||
// A nil return means the next item is not a term.
|
||||
func (t *Tree) term() Node {
|
||||
switch token := t.nextNonSpace(); token.typ {
|
||||
case itemError:
|
||||
t.errorf("%s", token.val)
|
||||
case itemIdentifier:
|
||||
if !t.hasFunction(token.val) {
|
||||
t.errorf("function %q not defined", token.val)
|
||||
}
|
||||
return NewIdentifier(token.val).SetTree(t).SetPos(token.pos)
|
||||
case itemDot:
|
||||
return t.newDot(token.pos)
|
||||
case itemNil:
|
||||
return t.newNil(token.pos)
|
||||
case itemVariable:
|
||||
return t.useVar(token.pos, token.val)
|
||||
case itemField:
|
||||
return t.newField(token.pos, token.val)
|
||||
case itemBool:
|
||||
return t.newBool(token.pos, token.val == "true")
|
||||
case itemCharConstant, itemComplex, itemNumber:
|
||||
number, err := t.newNumber(token.pos, token.val, token.typ)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return number
|
||||
case itemLeftParen:
|
||||
pipe := t.pipeline("parenthesized pipeline")
|
||||
if token := t.next(); token.typ != itemRightParen {
|
||||
t.errorf("unclosed right paren: unexpected %s", token)
|
||||
}
|
||||
return pipe
|
||||
case itemString, itemRawString:
|
||||
s, err := strconv.Unquote(token.val)
|
||||
if err != nil {
|
||||
t.error(err)
|
||||
}
|
||||
return t.newString(token.pos, token.val, s)
|
||||
}
|
||||
t.backup()
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasFunction reports if a function name exists in the Tree's maps.
|
||||
func (t *Tree) hasFunction(name string) bool {
|
||||
for _, funcMap := range t.funcs {
|
||||
if funcMap == nil {
|
||||
continue
|
||||
}
|
||||
if funcMap[name] != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// popVars trims the variable list to the specified length
|
||||
func (t *Tree) popVars(n int) {
|
||||
t.vars = t.vars[:n]
|
||||
}
|
||||
|
||||
// useVar returns a node for a variable reference. It errors if the
|
||||
// variable is not defined.
|
||||
func (t *Tree) useVar(pos Pos, name string) Node {
|
||||
v := t.newVariable(pos, name)
|
||||
for _, varName := range t.vars {
|
||||
if varName == v.Ident[0] {
|
||||
return v
|
||||
}
|
||||
}
|
||||
t.errorf("undefined variable %q", v.Ident[0])
|
||||
return nil
|
||||
}
|
||||
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
Normal file
218
vendor/github.com/alecthomas/template/template.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/alecthomas/template/parse"
|
||||
)
|
||||
|
||||
// common holds the information shared by related templates.
|
||||
type common struct {
|
||||
tmpl map[string]*Template
|
||||
// We use two maps, one for parsing and one for execution.
|
||||
// This separation makes the API cleaner since it doesn't
|
||||
// expose reflection to the client.
|
||||
parseFuncs FuncMap
|
||||
execFuncs map[string]reflect.Value
|
||||
}
|
||||
|
||||
// Template is the representation of a parsed template. The *parse.Tree
|
||||
// field is exported only for use by html/template and should be treated
|
||||
// as unexported by all other clients.
|
||||
type Template struct {
|
||||
name string
|
||||
*parse.Tree
|
||||
*common
|
||||
leftDelim string
|
||||
rightDelim string
|
||||
}
|
||||
|
||||
// New allocates a new template with the given name.
|
||||
func New(name string) *Template {
|
||||
return &Template{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the name of the template.
|
||||
func (t *Template) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// New allocates a new template associated with the given one and with the same
|
||||
// delimiters. The association, which is transitive, allows one template to
|
||||
// invoke another with a {{template}} action.
|
||||
func (t *Template) New(name string) *Template {
|
||||
t.init()
|
||||
return &Template{
|
||||
name: name,
|
||||
common: t.common,
|
||||
leftDelim: t.leftDelim,
|
||||
rightDelim: t.rightDelim,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Template) init() {
|
||||
if t.common == nil {
|
||||
t.common = new(common)
|
||||
t.tmpl = make(map[string]*Template)
|
||||
t.parseFuncs = make(FuncMap)
|
||||
t.execFuncs = make(map[string]reflect.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Clone returns a duplicate of the template, including all associated
|
||||
// templates. The actual representation is not copied, but the name space of
|
||||
// associated templates is, so further calls to Parse in the copy will add
|
||||
// templates to the copy but not to the original. Clone can be used to prepare
|
||||
// common templates and use them with variant definitions for other templates
|
||||
// by adding the variants after the clone is made.
|
||||
func (t *Template) Clone() (*Template, error) {
|
||||
nt := t.copy(nil)
|
||||
nt.init()
|
||||
nt.tmpl[t.name] = nt
|
||||
for k, v := range t.tmpl {
|
||||
if k == t.name { // Already installed.
|
||||
continue
|
||||
}
|
||||
// The associated templates share nt's common structure.
|
||||
tmpl := v.copy(nt.common)
|
||||
nt.tmpl[k] = tmpl
|
||||
}
|
||||
for k, v := range t.parseFuncs {
|
||||
nt.parseFuncs[k] = v
|
||||
}
|
||||
for k, v := range t.execFuncs {
|
||||
nt.execFuncs[k] = v
|
||||
}
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// copy returns a shallow copy of t, with common set to the argument.
|
||||
func (t *Template) copy(c *common) *Template {
|
||||
nt := New(t.name)
|
||||
nt.Tree = t.Tree
|
||||
nt.common = c
|
||||
nt.leftDelim = t.leftDelim
|
||||
nt.rightDelim = t.rightDelim
|
||||
return nt
|
||||
}
|
||||
|
||||
// AddParseTree creates a new template with the name and parse tree
|
||||
// and associates it with t.
|
||||
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
||||
if t.common != nil && t.tmpl[name] != nil {
|
||||
return nil, fmt.Errorf("template: redefinition of template %q", name)
|
||||
}
|
||||
nt := t.New(name)
|
||||
nt.Tree = tree
|
||||
t.tmpl[name] = nt
|
||||
return nt, nil
|
||||
}
|
||||
|
||||
// Templates returns a slice of the templates associated with t, including t
|
||||
// itself.
|
||||
func (t *Template) Templates() []*Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
// Return a slice so we don't expose the map.
|
||||
m := make([]*Template, 0, len(t.tmpl))
|
||||
for _, v := range t.tmpl {
|
||||
m = append(m, v)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Delims sets the action delimiters to the specified strings, to be used in
|
||||
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
||||
// definitions will inherit the settings. An empty delimiter stands for the
|
||||
// corresponding default: {{ or }}.
|
||||
// The return value is the template, so calls can be chained.
|
||||
func (t *Template) Delims(left, right string) *Template {
|
||||
t.leftDelim = left
|
||||
t.rightDelim = right
|
||||
return t
|
||||
}
|
||||
|
||||
// Funcs adds the elements of the argument map to the template's function map.
|
||||
// It panics if a value in the map is not a function with appropriate return
|
||||
// type. However, it is legal to overwrite elements of the map. The return
|
||||
// value is the template, so calls can be chained.
|
||||
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
||||
t.init()
|
||||
addValueFuncs(t.execFuncs, funcMap)
|
||||
addFuncs(t.parseFuncs, funcMap)
|
||||
return t
|
||||
}
|
||||
|
||||
// Lookup returns the template with the given name that is associated with t,
|
||||
// or nil if there is no such template.
|
||||
func (t *Template) Lookup(name string) *Template {
|
||||
if t.common == nil {
|
||||
return nil
|
||||
}
|
||||
return t.tmpl[name]
|
||||
}
|
||||
|
||||
// Parse parses a string into a template. Nested template definitions will be
|
||||
// associated with the top-level template t. Parse may be called multiple times
|
||||
// to parse definitions of templates to associate with t. It is an error if a
|
||||
// resulting template is non-empty (contains content other than template
|
||||
// definitions) and would replace a non-empty template with the same name.
|
||||
// (In multiple calls to Parse with the same receiver template, only one call
|
||||
// can contain text other than space, comments, and template definitions.)
|
||||
func (t *Template) Parse(text string) (*Template, error) {
|
||||
t.init()
|
||||
trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Add the newly parsed trees, including the one for t, into our common structure.
|
||||
for name, tree := range trees {
|
||||
// If the name we parsed is the name of this template, overwrite this template.
|
||||
// The associate method checks it's not a redefinition.
|
||||
tmpl := t
|
||||
if name != t.name {
|
||||
tmpl = t.New(name)
|
||||
}
|
||||
// Even if t == tmpl, we need to install it in the common.tmpl map.
|
||||
if replace, err := t.associate(tmpl, tree); err != nil {
|
||||
return nil, err
|
||||
} else if replace {
|
||||
tmpl.Tree = tree
|
||||
}
|
||||
tmpl.leftDelim = t.leftDelim
|
||||
tmpl.rightDelim = t.rightDelim
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// associate installs the new template into the group of templates associated
|
||||
// with t. It is an error to reuse a name except to overwrite an empty
|
||||
// template. The two are already known to share the common structure.
|
||||
// The boolean return value reports wither to store this tree as t.Tree.
|
||||
func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) {
|
||||
if new.common != t.common {
|
||||
panic("internal error: associate not common")
|
||||
}
|
||||
name := new.name
|
||||
if old := t.tmpl[name]; old != nil {
|
||||
oldIsEmpty := parse.IsEmptyTree(old.Root)
|
||||
newIsEmpty := parse.IsEmptyTree(tree.Root)
|
||||
if newIsEmpty {
|
||||
// Whether old is empty or not, new is empty; no reason to replace old.
|
||||
return false, nil
|
||||
}
|
||||
if !oldIsEmpty {
|
||||
return false, fmt.Errorf("template: redefinition of template %q", name)
|
||||
}
|
||||
}
|
||||
t.tmpl[name] = new
|
||||
return true, nil
|
||||
}
|
||||
19
vendor/github.com/alecthomas/units/COPYING
generated
vendored
Normal file
19
vendor/github.com/alecthomas/units/COPYING
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2014 Alec Thomas
|
||||
|
||||
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.
|
||||
11
vendor/github.com/alecthomas/units/README.md
generated
vendored
Normal file
11
vendor/github.com/alecthomas/units/README.md
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Units - Helpful unit multipliers and functions for Go
|
||||
|
||||
The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package.
|
||||
|
||||
It allows for code like this:
|
||||
|
||||
```go
|
||||
n, err := ParseBase2Bytes("1KB")
|
||||
// n == 1024
|
||||
n = units.Mebibyte * 512
|
||||
```
|
||||
91
vendor/github.com/alecthomas/units/bytes.go
generated
vendored
Normal file
91
vendor/github.com/alecthomas/units/bytes.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
package units
|
||||
|
||||
// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte,
|
||||
// etc.).
|
||||
type Base2Bytes int64
|
||||
|
||||
// Base-2 byte units.
|
||||
const (
|
||||
Kibibyte Base2Bytes = 1024
|
||||
KiB = Kibibyte
|
||||
Mebibyte = Kibibyte * 1024
|
||||
MiB = Mebibyte
|
||||
Gibibyte = Mebibyte * 1024
|
||||
GiB = Gibibyte
|
||||
Tebibyte = Gibibyte * 1024
|
||||
TiB = Tebibyte
|
||||
Pebibyte = Tebibyte * 1024
|
||||
PiB = Pebibyte
|
||||
Exbibyte = Pebibyte * 1024
|
||||
EiB = Exbibyte
|
||||
)
|
||||
|
||||
var (
|
||||
bytesUnitMap = MakeUnitMap("iB", "B", 1024)
|
||||
oldBytesUnitMap = MakeUnitMap("B", "B", 1024)
|
||||
)
|
||||
|
||||
// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB
|
||||
// and KiB are both 1024.
|
||||
// However "kB", which is the correct SI spelling of 1000 Bytes, is rejected.
|
||||
func ParseBase2Bytes(s string) (Base2Bytes, error) {
|
||||
n, err := ParseUnit(s, bytesUnitMap)
|
||||
if err != nil {
|
||||
n, err = ParseUnit(s, oldBytesUnitMap)
|
||||
}
|
||||
return Base2Bytes(n), err
|
||||
}
|
||||
|
||||
func (b Base2Bytes) String() string {
|
||||
return ToString(int64(b), 1024, "iB", "B")
|
||||
}
|
||||
|
||||
func (b *Base2Bytes) UnmarshalText(text []byte) error {
|
||||
n, err := ParseBase2Bytes(string(text))
|
||||
*b = n
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
metricBytesUnitMap = MakeUnitMap("B", "B", 1000)
|
||||
)
|
||||
|
||||
// MetricBytes are SI byte units (1000 bytes in a kilobyte).
|
||||
type MetricBytes SI
|
||||
|
||||
// SI base-10 byte units.
|
||||
const (
|
||||
Kilobyte MetricBytes = 1000
|
||||
KB = Kilobyte
|
||||
Megabyte = Kilobyte * 1000
|
||||
MB = Megabyte
|
||||
Gigabyte = Megabyte * 1000
|
||||
GB = Gigabyte
|
||||
Terabyte = Gigabyte * 1000
|
||||
TB = Terabyte
|
||||
Petabyte = Terabyte * 1000
|
||||
PB = Petabyte
|
||||
Exabyte = Petabyte * 1000
|
||||
EB = Exabyte
|
||||
)
|
||||
|
||||
// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes.
|
||||
func ParseMetricBytes(s string) (MetricBytes, error) {
|
||||
n, err := ParseUnit(s, metricBytesUnitMap)
|
||||
return MetricBytes(n), err
|
||||
}
|
||||
|
||||
// TODO: represents 1000B as uppercase "KB", while SI standard requires "kB".
|
||||
func (m MetricBytes) String() string {
|
||||
return ToString(int64(m), 1000, "B", "B")
|
||||
}
|
||||
|
||||
// ParseStrictBytes supports both iB and B suffixes for base 2 and metric,
|
||||
// respectively. That is, KiB represents 1024 and kB, KB represent 1000.
|
||||
func ParseStrictBytes(s string) (int64, error) {
|
||||
n, err := ParseUnit(s, bytesUnitMap)
|
||||
if err != nil {
|
||||
n, err = ParseUnit(s, metricBytesUnitMap)
|
||||
}
|
||||
return int64(n), err
|
||||
}
|
||||
13
vendor/github.com/alecthomas/units/doc.go
generated
vendored
Normal file
13
vendor/github.com/alecthomas/units/doc.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Package units provides helpful unit multipliers and functions for Go.
|
||||
//
|
||||
// The goal of this package is to have functionality similar to the time [1] package.
|
||||
//
|
||||
//
|
||||
// [1] http://golang.org/pkg/time/
|
||||
//
|
||||
// It allows for code like this:
|
||||
//
|
||||
// n, err := ParseBase2Bytes("1KB")
|
||||
// // n == 1024
|
||||
// n = units.Mebibyte * 512
|
||||
package units
|
||||
5
vendor/github.com/alecthomas/units/go.mod
generated
vendored
Normal file
5
vendor/github.com/alecthomas/units/go.mod
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/alecthomas/units
|
||||
|
||||
go 1.15
|
||||
|
||||
require github.com/stretchr/testify v1.4.0
|
||||
11
vendor/github.com/alecthomas/units/go.sum
generated
vendored
Normal file
11
vendor/github.com/alecthomas/units/go.sum
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
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.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
50
vendor/github.com/alecthomas/units/si.go
generated
vendored
Normal file
50
vendor/github.com/alecthomas/units/si.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
package units
|
||||
|
||||
// SI units.
|
||||
type SI int64
|
||||
|
||||
// SI unit multiples.
|
||||
const (
|
||||
Kilo SI = 1000
|
||||
Mega = Kilo * 1000
|
||||
Giga = Mega * 1000
|
||||
Tera = Giga * 1000
|
||||
Peta = Tera * 1000
|
||||
Exa = Peta * 1000
|
||||
)
|
||||
|
||||
func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 {
|
||||
res := map[string]float64{
|
||||
shortSuffix: 1,
|
||||
// see below for "k" / "K"
|
||||
"M" + suffix: float64(scale * scale),
|
||||
"G" + suffix: float64(scale * scale * scale),
|
||||
"T" + suffix: float64(scale * scale * scale * scale),
|
||||
"P" + suffix: float64(scale * scale * scale * scale * scale),
|
||||
"E" + suffix: float64(scale * scale * scale * scale * scale * scale),
|
||||
}
|
||||
|
||||
// Standard SI prefixes use lowercase "k" for kilo = 1000.
|
||||
// For compatibility, and to be fool-proof, we accept both "k" and "K" in metric mode.
|
||||
//
|
||||
// However, official binary prefixes are always capitalized - "KiB" -
|
||||
// and we specifically never parse "kB" as 1024B because:
|
||||
//
|
||||
// (1) people pedantic enough to use lowercase according to SI unlikely to abuse "k" to mean 1024 :-)
|
||||
//
|
||||
// (2) Use of capital K for 1024 was an informal tradition predating IEC prefixes:
|
||||
// "The binary meaning of the kilobyte for 1024 bytes typically uses the symbol KB, with an
|
||||
// uppercase letter K."
|
||||
// -- https://en.wikipedia.org/wiki/Kilobyte#Base_2_(1024_bytes)
|
||||
// "Capitalization of the letter K became the de facto standard for binary notation, although this
|
||||
// could not be extended to higher powers, and use of the lowercase k did persist.[13][14][15]"
|
||||
// -- https://en.wikipedia.org/wiki/Binary_prefix#History
|
||||
// See also the extensive https://en.wikipedia.org/wiki/Timeline_of_binary_prefixes.
|
||||
if scale == 1024 {
|
||||
res["K"+suffix] = float64(scale)
|
||||
} else {
|
||||
res["k"+suffix] = float64(scale)
|
||||
res["K"+suffix] = float64(scale)
|
||||
}
|
||||
return res
|
||||
}
|
||||
138
vendor/github.com/alecthomas/units/util.go
generated
vendored
Normal file
138
vendor/github.com/alecthomas/units/util.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
package units
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
siUnits = []string{"", "K", "M", "G", "T", "P", "E"}
|
||||
)
|
||||
|
||||
func ToString(n int64, scale int64, suffix, baseSuffix string) string {
|
||||
mn := len(siUnits)
|
||||
out := make([]string, mn)
|
||||
for i, m := range siUnits {
|
||||
if n%scale != 0 || i == 0 && n == 0 {
|
||||
s := suffix
|
||||
if i == 0 {
|
||||
s = baseSuffix
|
||||
}
|
||||
out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s)
|
||||
}
|
||||
n /= scale
|
||||
if n == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return strings.Join(out, "")
|
||||
}
|
||||
|
||||
// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123
|
||||
var errLeadingInt = errors.New("units: bad [0-9]*") // never printed
|
||||
|
||||
// leadingInt consumes the leading [0-9]* from s.
|
||||
func leadingInt(s string) (x int64, rem string, err error) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
if x >= (1<<63-10)/10 {
|
||||
// overflow
|
||||
return 0, "", errLeadingInt
|
||||
}
|
||||
x = x*10 + int64(c) - '0'
|
||||
}
|
||||
return x, s[i:], nil
|
||||
}
|
||||
|
||||
func ParseUnit(s string, unitMap map[string]float64) (int64, error) {
|
||||
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
|
||||
orig := s
|
||||
f := float64(0)
|
||||
neg := false
|
||||
|
||||
// Consume [-+]?
|
||||
if s != "" {
|
||||
c := s[0]
|
||||
if c == '-' || c == '+' {
|
||||
neg = c == '-'
|
||||
s = s[1:]
|
||||
}
|
||||
}
|
||||
// Special case: if all that is left is "0", this is zero.
|
||||
if s == "0" {
|
||||
return 0, nil
|
||||
}
|
||||
if s == "" {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
for s != "" {
|
||||
g := float64(0) // this element of the sequence
|
||||
|
||||
var x int64
|
||||
var err error
|
||||
|
||||
// The next character must be [0-9.]
|
||||
if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
// Consume [0-9]*
|
||||
pl := len(s)
|
||||
x, s, err = leadingInt(s)
|
||||
if err != nil {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
g = float64(x)
|
||||
pre := pl != len(s) // whether we consumed anything before a period
|
||||
|
||||
// Consume (\.[0-9]*)?
|
||||
post := false
|
||||
if s != "" && s[0] == '.' {
|
||||
s = s[1:]
|
||||
pl := len(s)
|
||||
x, s, err = leadingInt(s)
|
||||
if err != nil {
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
scale := 1.0
|
||||
for n := pl - len(s); n > 0; n-- {
|
||||
scale *= 10
|
||||
}
|
||||
g += float64(x) / scale
|
||||
post = pl != len(s)
|
||||
}
|
||||
if !pre && !post {
|
||||
// no digits (e.g. ".s" or "-.s")
|
||||
return 0, errors.New("units: invalid " + orig)
|
||||
}
|
||||
|
||||
// Consume unit.
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
c := s[i]
|
||||
if c == '.' || ('0' <= c && c <= '9') {
|
||||
break
|
||||
}
|
||||
}
|
||||
u := s[:i]
|
||||
s = s[i:]
|
||||
unit, ok := unitMap[u]
|
||||
if !ok {
|
||||
return 0, errors.New("units: unknown unit " + u + " in " + orig)
|
||||
}
|
||||
|
||||
f += g * unit
|
||||
}
|
||||
|
||||
if neg {
|
||||
f = -f
|
||||
}
|
||||
if f < float64(-1<<63) || f > float64(1<<63-1) {
|
||||
return 0, errors.New("units: overflow parsing unit")
|
||||
}
|
||||
return int64(f), nil
|
||||
}
|
||||
202
vendor/github.com/google/shlex/COPYING
generated
vendored
Normal file
202
vendor/github.com/google/shlex/COPYING
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
2
vendor/github.com/google/shlex/README
generated
vendored
Normal file
2
vendor/github.com/google/shlex/README
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
go-shlex is a simple lexer for go that supports shell-style quoting,
|
||||
commenting, and escaping.
|
||||
3
vendor/github.com/google/shlex/go.mod
generated
vendored
Normal file
3
vendor/github.com/google/shlex/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/google/shlex
|
||||
|
||||
go 1.13
|
||||
416
vendor/github.com/google/shlex/shlex.go
generated
vendored
Normal file
416
vendor/github.com/google/shlex/shlex.go
generated
vendored
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package shlex implements a simple lexer which splits input in to tokens using
|
||||
shell-style rules for quoting and commenting.
|
||||
|
||||
The basic use case uses the default ASCII lexer to split a string into sub-strings:
|
||||
|
||||
shlex.Split("one \"two three\" four") -> []string{"one", "two three", "four"}
|
||||
|
||||
To process a stream of strings:
|
||||
|
||||
l := NewLexer(os.Stdin)
|
||||
for ; token, err := l.Next(); err != nil {
|
||||
// process token
|
||||
}
|
||||
|
||||
To access the raw token stream (which includes tokens for comments):
|
||||
|
||||
t := NewTokenizer(os.Stdin)
|
||||
for ; token, err := t.Next(); err != nil {
|
||||
// process token
|
||||
}
|
||||
|
||||
*/
|
||||
package shlex
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TokenType is a top-level token classification: A word, space, comment, unknown.
|
||||
type TokenType int
|
||||
|
||||
// runeTokenClass is the type of a UTF-8 character classification: A quote, space, escape.
|
||||
type runeTokenClass int
|
||||
|
||||
// the internal state used by the lexer state machine
|
||||
type lexerState int
|
||||
|
||||
// Token is a (type, value) pair representing a lexographical token.
|
||||
type Token struct {
|
||||
tokenType TokenType
|
||||
value string
|
||||
}
|
||||
|
||||
// Equal reports whether tokens a, and b, are equal.
|
||||
// Two tokens are equal if both their types and values are equal. A nil token can
|
||||
// never be equal to another token.
|
||||
func (a *Token) Equal(b *Token) bool {
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
if a.tokenType != b.tokenType {
|
||||
return false
|
||||
}
|
||||
return a.value == b.value
|
||||
}
|
||||
|
||||
// Named classes of UTF-8 runes
|
||||
const (
|
||||
spaceRunes = " \t\r\n"
|
||||
escapingQuoteRunes = `"`
|
||||
nonEscapingQuoteRunes = "'"
|
||||
escapeRunes = `\`
|
||||
commentRunes = "#"
|
||||
)
|
||||
|
||||
// Classes of rune token
|
||||
const (
|
||||
unknownRuneClass runeTokenClass = iota
|
||||
spaceRuneClass
|
||||
escapingQuoteRuneClass
|
||||
nonEscapingQuoteRuneClass
|
||||
escapeRuneClass
|
||||
commentRuneClass
|
||||
eofRuneClass
|
||||
)
|
||||
|
||||
// Classes of lexographic token
|
||||
const (
|
||||
UnknownToken TokenType = iota
|
||||
WordToken
|
||||
SpaceToken
|
||||
CommentToken
|
||||
)
|
||||
|
||||
// Lexer state machine states
|
||||
const (
|
||||
startState lexerState = iota // no runes have been seen
|
||||
inWordState // processing regular runes in a word
|
||||
escapingState // we have just consumed an escape rune; the next rune is literal
|
||||
escapingQuotedState // we have just consumed an escape rune within a quoted string
|
||||
quotingEscapingState // we are within a quoted string that supports escaping ("...")
|
||||
quotingState // we are within a string that does not support escaping ('...')
|
||||
commentState // we are within a comment (everything following an unquoted or unescaped #
|
||||
)
|
||||
|
||||
// tokenClassifier is used for classifying rune characters.
|
||||
type tokenClassifier map[rune]runeTokenClass
|
||||
|
||||
func (typeMap tokenClassifier) addRuneClass(runes string, tokenType runeTokenClass) {
|
||||
for _, runeChar := range runes {
|
||||
typeMap[runeChar] = tokenType
|
||||
}
|
||||
}
|
||||
|
||||
// newDefaultClassifier creates a new classifier for ASCII characters.
|
||||
func newDefaultClassifier() tokenClassifier {
|
||||
t := tokenClassifier{}
|
||||
t.addRuneClass(spaceRunes, spaceRuneClass)
|
||||
t.addRuneClass(escapingQuoteRunes, escapingQuoteRuneClass)
|
||||
t.addRuneClass(nonEscapingQuoteRunes, nonEscapingQuoteRuneClass)
|
||||
t.addRuneClass(escapeRunes, escapeRuneClass)
|
||||
t.addRuneClass(commentRunes, commentRuneClass)
|
||||
return t
|
||||
}
|
||||
|
||||
// ClassifyRune classifiees a rune
|
||||
func (t tokenClassifier) ClassifyRune(runeVal rune) runeTokenClass {
|
||||
return t[runeVal]
|
||||
}
|
||||
|
||||
// Lexer turns an input stream into a sequence of tokens. Whitespace and comments are skipped.
|
||||
type Lexer Tokenizer
|
||||
|
||||
// NewLexer creates a new lexer from an input stream.
|
||||
func NewLexer(r io.Reader) *Lexer {
|
||||
|
||||
return (*Lexer)(NewTokenizer(r))
|
||||
}
|
||||
|
||||
// Next returns the next word, or an error. If there are no more words,
|
||||
// the error will be io.EOF.
|
||||
func (l *Lexer) Next() (string, error) {
|
||||
for {
|
||||
token, err := (*Tokenizer)(l).Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
switch token.tokenType {
|
||||
case WordToken:
|
||||
return token.value, nil
|
||||
case CommentToken:
|
||||
// skip comments
|
||||
default:
|
||||
return "", fmt.Errorf("Unknown token type: %v", token.tokenType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tokenizer turns an input stream into a sequence of typed tokens
|
||||
type Tokenizer struct {
|
||||
input bufio.Reader
|
||||
classifier tokenClassifier
|
||||
}
|
||||
|
||||
// NewTokenizer creates a new tokenizer from an input stream.
|
||||
func NewTokenizer(r io.Reader) *Tokenizer {
|
||||
input := bufio.NewReader(r)
|
||||
classifier := newDefaultClassifier()
|
||||
return &Tokenizer{
|
||||
input: *input,
|
||||
classifier: classifier}
|
||||
}
|
||||
|
||||
// scanStream scans the stream for the next token using the internal state machine.
|
||||
// It will panic if it encounters a rune which it does not know how to handle.
|
||||
func (t *Tokenizer) scanStream() (*Token, error) {
|
||||
state := startState
|
||||
var tokenType TokenType
|
||||
var value []rune
|
||||
var nextRune rune
|
||||
var nextRuneType runeTokenClass
|
||||
var err error
|
||||
|
||||
for {
|
||||
nextRune, _, err = t.input.ReadRune()
|
||||
nextRuneType = t.classifier.ClassifyRune(nextRune)
|
||||
|
||||
if err == io.EOF {
|
||||
nextRuneType = eofRuneClass
|
||||
err = nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch state {
|
||||
case startState: // no runes read yet
|
||||
{
|
||||
switch nextRuneType {
|
||||
case eofRuneClass:
|
||||
{
|
||||
return nil, io.EOF
|
||||
}
|
||||
case spaceRuneClass:
|
||||
{
|
||||
}
|
||||
case escapingQuoteRuneClass:
|
||||
{
|
||||
tokenType = WordToken
|
||||
state = quotingEscapingState
|
||||
}
|
||||
case nonEscapingQuoteRuneClass:
|
||||
{
|
||||
tokenType = WordToken
|
||||
state = quotingState
|
||||
}
|
||||
case escapeRuneClass:
|
||||
{
|
||||
tokenType = WordToken
|
||||
state = escapingState
|
||||
}
|
||||
case commentRuneClass:
|
||||
{
|
||||
tokenType = CommentToken
|
||||
state = commentState
|
||||
}
|
||||
default:
|
||||
{
|
||||
tokenType = WordToken
|
||||
value = append(value, nextRune)
|
||||
state = inWordState
|
||||
}
|
||||
}
|
||||
}
|
||||
case inWordState: // in a regular word
|
||||
{
|
||||
switch nextRuneType {
|
||||
case eofRuneClass:
|
||||
{
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
}
|
||||
case spaceRuneClass:
|
||||
{
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
}
|
||||
case escapingQuoteRuneClass:
|
||||
{
|
||||
state = quotingEscapingState
|
||||
}
|
||||
case nonEscapingQuoteRuneClass:
|
||||
{
|
||||
state = quotingState
|
||||
}
|
||||
case escapeRuneClass:
|
||||
{
|
||||
state = escapingState
|
||||
}
|
||||
default:
|
||||
{
|
||||
value = append(value, nextRune)
|
||||
}
|
||||
}
|
||||
}
|
||||
case escapingState: // the rune after an escape character
|
||||
{
|
||||
switch nextRuneType {
|
||||
case eofRuneClass:
|
||||
{
|
||||
err = fmt.Errorf("EOF found after escape character")
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
}
|
||||
default:
|
||||
{
|
||||
state = inWordState
|
||||
value = append(value, nextRune)
|
||||
}
|
||||
}
|
||||
}
|
||||
case escapingQuotedState: // the next rune after an escape character, in double quotes
|
||||
{
|
||||
switch nextRuneType {
|
||||
case eofRuneClass:
|
||||
{
|
||||
err = fmt.Errorf("EOF found after escape character")
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
}
|
||||
default:
|
||||
{
|
||||
state = quotingEscapingState
|
||||
value = append(value, nextRune)
|
||||
}
|
||||
}
|
||||
}
|
||||
case quotingEscapingState: // in escaping double quotes
|
||||
{
|
||||
switch nextRuneType {
|
||||
case eofRuneClass:
|
||||
{
|
||||
err = fmt.Errorf("EOF found when expecting closing quote")
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
}
|
||||
case escapingQuoteRuneClass:
|
||||
{
|
||||
state = inWordState
|
||||
}
|
||||
case escapeRuneClass:
|
||||
{
|
||||
state = escapingQuotedState
|
||||
}
|
||||
default:
|
||||
{
|
||||
value = append(value, nextRune)
|
||||
}
|
||||
}
|
||||
}
|
||||
case quotingState: // in non-escaping single quotes
|
||||
{
|
||||
switch nextRuneType {
|
||||
case eofRuneClass:
|
||||
{
|
||||
err = fmt.Errorf("EOF found when expecting closing quote")
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
}
|
||||
case nonEscapingQuoteRuneClass:
|
||||
{
|
||||
state = inWordState
|
||||
}
|
||||
default:
|
||||
{
|
||||
value = append(value, nextRune)
|
||||
}
|
||||
}
|
||||
}
|
||||
case commentState: // in a comment
|
||||
{
|
||||
switch nextRuneType {
|
||||
case eofRuneClass:
|
||||
{
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
}
|
||||
case spaceRuneClass:
|
||||
{
|
||||
if nextRune == '\n' {
|
||||
state = startState
|
||||
token := &Token{
|
||||
tokenType: tokenType,
|
||||
value: string(value)}
|
||||
return token, err
|
||||
} else {
|
||||
value = append(value, nextRune)
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
value = append(value, nextRune)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
return nil, fmt.Errorf("Unexpected state: %v", state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Next returns the next token in the stream.
|
||||
func (t *Tokenizer) Next() (*Token, error) {
|
||||
return t.scanStream()
|
||||
}
|
||||
|
||||
// Split partitions a string into a slice of strings.
|
||||
func Split(s string) ([]string, error) {
|
||||
l := NewLexer(strings.NewReader(s))
|
||||
subStrings := make([]string, 0)
|
||||
for {
|
||||
word, err := l.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return subStrings, nil
|
||||
}
|
||||
return subStrings, err
|
||||
}
|
||||
subStrings = append(subStrings, word)
|
||||
}
|
||||
}
|
||||
30
vendor/github.com/gordonklaus/ineffassign/.gitignore
generated
vendored
Normal file
30
vendor/github.com/gordonklaus/ineffassign/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/ineffassign
|
||||
|
||||
# Created by https://www.gitignore.io/api/go
|
||||
|
||||
### Go ###
|
||||
# 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
|
||||
|
||||
21
vendor/github.com/gordonklaus/ineffassign/LICENSE
generated
vendored
Normal file
21
vendor/github.com/gordonklaus/ineffassign/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Gordon Klaus and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
21
vendor/github.com/gordonklaus/ineffassign/README.md
generated
vendored
Normal file
21
vendor/github.com/gordonklaus/ineffassign/README.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# ineffassign
|
||||
|
||||
Detect ineffectual assignments in Go code.
|
||||
|
||||
This tool misses some cases because it does not consider any type information in its analysis. (For example, assignments to struct fields are never marked as ineffectual.) It should, however, never give any false positives.
|
||||
|
||||
## Install
|
||||
|
||||
go get -u github.com/gordonklaus/ineffassign
|
||||
|
||||
## Usage
|
||||
|
||||
For basic usage, run the following command from the root of your project:
|
||||
|
||||
ineffassign ./...
|
||||
|
||||
Which will analyze all packages beneath the current directory.
|
||||
|
||||
## Exit Codes
|
||||
|
||||
ineffassign returns 1 if any problems were found in the checked files. It returns 3 if there were any invalid arguments.
|
||||
7
vendor/github.com/gordonklaus/ineffassign/bugs
generated
vendored
Normal file
7
vendor/github.com/gordonklaus/ineffassign/bugs
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
cmd/compile/internal/big/floatconv.go:367:2 m
|
||||
cmd/cover/cover_test.go:62:2 err
|
||||
cmd/pprof/internal/profile/profile.go:131:10 err
|
||||
math/big/ftoa.go:285:2 m
|
||||
net/file_unix.go:66:7 err
|
||||
golang.org/x/mobile/app/android.go:175:2 queue
|
||||
golang.org/x/net/icmp/listen_posix.go:83:6 err
|
||||
5
vendor/github.com/gordonklaus/ineffassign/go.mod
generated
vendored
Normal file
5
vendor/github.com/gordonklaus/ineffassign/go.mod
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/gordonklaus/ineffassign
|
||||
|
||||
go 1.14
|
||||
|
||||
require golang.org/x/tools v0.1.0
|
||||
26
vendor/github.com/gordonklaus/ineffassign/go.sum
generated
vendored
Normal file
26
vendor/github.com/gordonklaus/ineffassign/go.sum
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
16
vendor/github.com/gordonklaus/ineffassign/ineffassign.go
generated
vendored
Normal file
16
vendor/github.com/gordonklaus/ineffassign/ineffassign.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
|
||||
"github.com/gordonklaus/ineffassign/pkg/ineffassign"
|
||||
"golang.org/x/tools/go/analysis/singlechecker"
|
||||
)
|
||||
|
||||
func main() {
|
||||
singlechecker.Main(ineffassign.Analyzer)
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.Bool("n", false, "don't recursively check paths (deprecated)")
|
||||
}
|
||||
25
vendor/github.com/gordonklaus/ineffassign/list
generated
vendored
Normal file
25
vendor/github.com/gordonklaus/ineffassign/list
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/Users/gordon/go/src/code.google.com/p/freetype-go/freetype/truetype/truetype.go:493:5: offset assigned and not used
|
||||
/Users/gordon/go/src/code.google.com/p/freetype-go/freetype/truetype/truetype.go:289:11: offset assigned and not used
|
||||
/Users/gordon/go/src/code.google.com/p/freetype-go/freetype/truetype/truetype_test.go:224:2: prefix assigned and not used
|
||||
/Users/gordon/go/src/code.google.com/p/freetype-go/freetype/truetype/truetype_test.go:239:3: s assigned and not used
|
||||
/Users/gordon/go/src/github.com/gordonklaus/flux/go/types/resolver.go:372:2: seenPkgs assigned and not used
|
||||
/Users/gordon/go/src/github.com/gopherjs/gopherjs/compiler/package.go:195:7: recvType assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/crypto/ocsp/ocsp.go:340:2: rest assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/crypto/openpgp/packet/opaque_test.go:35:6: err assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/crypto/otr/otr.go:641:6: in assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/crypto/otr/otr_test.go:198:17: err assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/crypto/ssh/benchmark_test.go:94:17: err assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/mobile/app/android.go:175:2: queue assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/mobile/cmd/gomobile/bind.go:411:2: w assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/mobile/cmd/gomobile/build.go:231:8: err assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/net/icmp/listen_posix.go:83:6: err assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/net/ipv4/control_unix.go:99:5: b assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/net/ipv4/control_unix.go:148:4: b assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/net/ipv6/control_unix.go:90:4: b assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/net/ipv6/control_unix.go:162:4: b assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/net/websocket/hybi.go:298:3: n assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/tools/cmd/callgraph/main.go:164:2: args assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/tools/cmd/cover/cover_test.go:52:2: err assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/tools/go/gcimporter/exportdata.go:74:13: size assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/tools/oracle/oracle.go:268:2: iprog assigned and not used
|
||||
/Users/gordon/go/src/golang.org/x/tools/oracle/oracle_test.go:299:2: iprog assigned and not used
|
||||
131
vendor/github.com/gordonklaus/ineffassign/liststd
generated
vendored
Normal file
131
vendor/github.com/gordonklaus/ineffassign/liststd
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
/usr/local/go/src/bufio/scan.go:388:6: ineffectual assignment to width
|
||||
/usr/local/go/src/bufio/scan.go:396:6: ineffectual assignment to width
|
||||
/usr/local/go/src/bytes/buffer_test.go:141:6: ineffectual assignment to err
|
||||
/usr/local/go/src/bytes/buffer_test.go:164:3: ineffectual assignment to c
|
||||
/usr/local/go/src/cmd/cgo/out.go:799:3: ineffectual assignment to gccResult
|
||||
/usr/local/go/src/cmd/compile/internal/big/ratconv.go:170:4: ineffectual assignment to err
|
||||
/usr/local/go/src/cmd/compile/internal/gc/bimport.go:330:2: ineffectual assignment to file
|
||||
/usr/local/go/src/cmd/compile/internal/gc/cgen.go:3332:3: ineffectual assignment to max
|
||||
/usr/local/go/src/cmd/compile/internal/gc/export.go:379:2: ineffectual assignment to size
|
||||
/usr/local/go/src/cmd/compile/internal/gc/global_test.go:51:2: ineffectual assignment to out
|
||||
/usr/local/go/src/cmd/compile/internal/gc/lex.go:281:4: ineffectual assignment to c1
|
||||
/usr/local/go/src/cmd/compile/internal/gc/reg.go:1373:2: ineffectual assignment to firstf
|
||||
/usr/local/go/src/cmd/compile/internal/gc/reg.go:1381:3: ineffectual assignment to firstf
|
||||
/usr/local/go/src/cmd/compile/internal/s390x/peep.go:1048:3: ineffectual assignment to size
|
||||
/usr/local/go/src/cmd/compile/internal/s390x/peep.go:1139:3: ineffectual assignment to size
|
||||
/usr/local/go/src/cmd/compile/internal/ssa/loopbce.go:44:3: ineffectual assignment to entry
|
||||
/usr/local/go/src/cmd/cover/html.go:64:8: ineffectual assignment to err
|
||||
/usr/local/go/src/cmd/cover/html.go:66:8: ineffectual assignment to err
|
||||
/usr/local/go/src/cmd/go/build.go:3355:3: ineffectual assignment to cgoLDFLAGS
|
||||
/usr/local/go/src/cmd/internal/goobj/read.go:532:3: ineffectual assignment to data
|
||||
/usr/local/go/src/cmd/internal/obj/arm64/obj7.go:600:2: ineffectual assignment to aoffset
|
||||
/usr/local/go/src/cmd/internal/obj/mips/asm0.go:1049:3: ineffectual assignment to v
|
||||
/usr/local/go/src/cmd/internal/obj/mips/asm0.go:1101:3: ineffectual assignment to v
|
||||
/usr/local/go/src/cmd/internal/obj/s390x/objz.go:609:3: ineffectual assignment to pLast
|
||||
/usr/local/go/src/cmd/internal/pprof/profile/encode.go:279:12: ineffectual assignment to err
|
||||
/usr/local/go/src/cmd/link/internal/ld/dwarf.go:1426:2: ineffectual assignment to unitstart
|
||||
/usr/local/go/src/cmd/link/internal/ld/dwarf.go:1427:2: ineffectual assignment to headerstart
|
||||
/usr/local/go/src/cmd/link/internal/ld/dwarf.go:1428:2: ineffectual assignment to headerend
|
||||
/usr/local/go/src/cmd/link/internal/ld/elf.go:2272:3: ineffectual assignment to resoff
|
||||
/usr/local/go/src/cmd/vet/print.go:227:9: ineffectual assignment to w
|
||||
/usr/local/go/src/cmd/yacc/yacc.go:770:2: ineffectual assignment to val
|
||||
/usr/local/go/src/cmd/yacc/yacc.go:3127:2: ineffectual assignment to i
|
||||
/usr/local/go/src/compress/bzip2/huffman.go:114:4: ineffectual assignment to length
|
||||
/usr/local/go/src/compress/flate/reader_test.go:53:3: ineffectual assignment to buf0
|
||||
/usr/local/go/src/compress/flate/writer_test.go:29:3: ineffectual assignment to buf0
|
||||
/usr/local/go/src/compress/gzip/gzip_test.go:211:5: ineffectual assignment to err
|
||||
/usr/local/go/src/compress/lzw/reader_test.go:148:4: ineffectual assignment to buf0
|
||||
/usr/local/go/src/compress/lzw/writer_test.go:146:3: ineffectual assignment to buf0
|
||||
/usr/local/go/src/container/list/list_test.go:286:2: ineffectual assignment to e1
|
||||
/usr/local/go/src/container/list/list_test.go:286:6: ineffectual assignment to e2
|
||||
/usr/local/go/src/container/list/list_test.go:286:10: ineffectual assignment to e3
|
||||
/usr/local/go/src/container/list/list_test.go:286:14: ineffectual assignment to e4
|
||||
/usr/local/go/src/crypto/elliptic/p224.go:722:10: ineffectual assignment to bytes
|
||||
/usr/local/go/src/crypto/tls/handshake_messages.go:289:3: ineffectual assignment to z
|
||||
/usr/local/go/src/crypto/x509/verify.go:110:5: ineffectual assignment to certName
|
||||
/usr/local/go/src/database/sql/sql_test.go:1705:4: ineffectual assignment to numOpen
|
||||
/usr/local/go/src/database/sql/sql_test.go:1839:5: ineffectual assignment to err
|
||||
/usr/local/go/src/debug/dwarf/type.go:540:5: ineffectual assignment to haveBitOffset
|
||||
/usr/local/go/src/debug/elf/file.go:1014:3: ineffectual assignment to suffix
|
||||
/usr/local/go/src/debug/gosym/pclntab_test.go:256:2: ineffectual assignment to off
|
||||
/usr/local/go/src/debug/pe/file_test.go:309:2: ineffectual assignment to err
|
||||
/usr/local/go/src/encoding/base32/base32_test.go:120:4: ineffectual assignment to count
|
||||
/usr/local/go/src/encoding/base64/base64_test.go:174:4: ineffectual assignment to count
|
||||
/usr/local/go/src/encoding/gob/decgen.go:187:6: ineffectual assignment to err
|
||||
/usr/local/go/src/encoding/gob/encgen.go:166:6: ineffectual assignment to err
|
||||
/usr/local/go/src/encoding/json/encode.go:1071:2: ineffectual assignment to count
|
||||
/usr/local/go/src/encoding/json/encode.go:1169:6: ineffectual assignment to advance
|
||||
/usr/local/go/src/encoding/xml/xml.go:1030:6: ineffectual assignment to ok
|
||||
/usr/local/go/src/fmt/print.go:936:2: ineffectual assignment to afterIndex
|
||||
/usr/local/go/src/fmt/print.go:1051:15: ineffectual assignment to afterIndex
|
||||
/usr/local/go/src/go/ast/filter.go:84:3: ineffectual assignment to keepField
|
||||
/usr/local/go/src/go/internal/gcimporter/bimport.go:215:2: ineffectual assignment to file
|
||||
/usr/local/go/src/go/printer/nodes.go:439:4: ineffectual assignment to extraTabs
|
||||
/usr/local/go/src/go/printer/printer_test.go:155:8: ineffectual assignment to err
|
||||
/usr/local/go/src/go/types/conversions.go:49:2: ineffectual assignment to final
|
||||
/usr/local/go/src/html/template/css.go:160:2: ineffectual assignment to r
|
||||
/usr/local/go/src/html/template/css.go:160:5: ineffectual assignment to w
|
||||
/usr/local/go/src/html/template/html.go:141:2: ineffectual assignment to r
|
||||
/usr/local/go/src/html/template/html.go:141:5: ineffectual assignment to w
|
||||
/usr/local/go/src/html/template/js.go:249:2: ineffectual assignment to r
|
||||
/usr/local/go/src/html/template/js.go:249:5: ineffectual assignment to w
|
||||
/usr/local/go/src/image/decode_test.go:125:9: ineffectual assignment to err
|
||||
/usr/local/go/src/image/png/reader.go:689:2: ineffectual assignment to n
|
||||
/usr/local/go/src/image/png/writer.go:269:3: ineffectual assignment to best
|
||||
/usr/local/go/src/io/io_test.go:245:2: ineffectual assignment to n
|
||||
/usr/local/go/src/io/ioutil/ioutil.go:149:2: ineffectual assignment to readSize
|
||||
/usr/local/go/src/io/ioutil/ioutil_test.go:24:2: ineffectual assignment to contents
|
||||
/usr/local/go/src/log/syslog/syslog_test.go:236:5: ineffectual assignment to err
|
||||
/usr/local/go/src/log/syslog/syslog_test.go:240:5: ineffectual assignment to err
|
||||
/usr/local/go/src/math/big/ratconv.go:176:4: ineffectual assignment to err
|
||||
/usr/local/go/src/mime/multipart/multipart_test.go:408:2: ineffectual assignment to p
|
||||
/usr/local/go/src/net/dial_test.go:381:6: ineffectual assignment to err
|
||||
/usr/local/go/src/net/dnsname_test.go:36:6: ineffectual assignment to char63
|
||||
/usr/local/go/src/net/dnsname_test.go:37:6: ineffectual assignment to char64
|
||||
/usr/local/go/src/net/fd_plan9.go:64:4: ineffectual assignment to err
|
||||
/usr/local/go/src/net/fd_windows.go:166:3: ineffectual assignment to err
|
||||
/usr/local/go/src/net/http/fs.go:413:5: ineffectual assignment to name
|
||||
/usr/local/go/src/net/http/h2_bundle.go:6249:4: ineffectual assignment to n
|
||||
/usr/local/go/src/net/http/request_test.go:155:13: ineffectual assignment to err
|
||||
/usr/local/go/src/net/http/serve_test.go:4053:13: ineffectual assignment to err
|
||||
/usr/local/go/src/net/http/transport_test.go:729:8: ineffectual assignment to err
|
||||
/usr/local/go/src/net/http/transport_test.go:2345:3: ineffectual assignment to slurp
|
||||
/usr/local/go/src/net/parse.go:27:2: ineffectual assignment to i
|
||||
/usr/local/go/src/net/rpc/server.go:270:3: ineffectual assignment to str
|
||||
/usr/local/go/src/net/udpsock_plan9.go:80:16: ineffectual assignment to i
|
||||
/usr/local/go/src/os/env_test.go:109:2: ineffectual assignment to value
|
||||
/usr/local/go/src/os/os_test.go:1080:5: ineffectual assignment to err
|
||||
/usr/local/go/src/os/path_test.go:122:2: ineffectual assignment to testit
|
||||
/usr/local/go/src/reflect/type.go:2379:3: ineffectual assignment to name
|
||||
/usr/local/go/src/regexp/exec.go:123:2: ineffectual assignment to r
|
||||
/usr/local/go/src/regexp/exec.go:124:2: ineffectual assignment to width
|
||||
/usr/local/go/src/regexp/exec.go:321:2: ineffectual assignment to r
|
||||
/usr/local/go/src/regexp/exec.go:322:2: ineffectual assignment to width
|
||||
/usr/local/go/src/regexp/onepass.go:338:15: ineffectual assignment to matchArg
|
||||
/usr/local/go/src/regexp/syntax/parse.go:577:2: ineffectual assignment to start
|
||||
/usr/local/go/src/runtime/lfstack_test.go:48:2: ineffectual assignment to nodes
|
||||
/usr/local/go/src/runtime/mbitmap.go:1458:3: ineffectual assignment to i
|
||||
/usr/local/go/src/runtime/mfinal_test.go:60:4: ineffectual assignment to v
|
||||
/usr/local/go/src/runtime/mfinal_test.go:98:3: ineffectual assignment to v
|
||||
/usr/local/go/src/runtime/mgcmark.go:414:2: ineffectual assignment to stolen
|
||||
/usr/local/go/src/runtime/mgcsweep.go:188:2: ineffectual assignment to nfree
|
||||
/usr/local/go/src/runtime/os_plan9.go:307:2: ineffectual assignment to n
|
||||
/usr/local/go/src/runtime/pprof/pprof.go:465:5: ineffectual assignment to ok
|
||||
/usr/local/go/src/runtime/pprof/pprof.go:608:5: ineffectual assignment to ok
|
||||
/usr/local/go/src/runtime/pprof/pprof.go:751:5: ineffectual assignment to ok
|
||||
/usr/local/go/src/runtime/proc.go:4227:3: ineffectual assignment to xname
|
||||
/usr/local/go/src/runtime/runtime1.go:360:3: ineffectual assignment to field
|
||||
/usr/local/go/src/runtime/runtime_mmap_test.go:25:2: ineffectual assignment to p
|
||||
/usr/local/go/src/runtime/softfloat64.go:228:3: ineffectual assignment to f
|
||||
/usr/local/go/src/runtime/softfloat64.go:228:6: ineffectual assignment to g
|
||||
/usr/local/go/src/runtime/stack_test.go:106:4: ineffectual assignment to s
|
||||
/usr/local/go/src/strconv/quote.go:23:6: ineffectual assignment to width
|
||||
/usr/local/go/src/sync/atomic/atomic_test.go:1122:2: ineffectual assignment to new
|
||||
/usr/local/go/src/sync/atomic/atomic_test.go:1150:2: ineffectual assignment to new
|
||||
/usr/local/go/src/syscall/dir_plan9.go:88:2: ineffectual assignment to b
|
||||
/usr/local/go/src/syscall/dir_plan9.go:131:13: ineffectual assignment to b
|
||||
/usr/local/go/src/syscall/exec_plan9.go:281:2: ineffectual assignment to r1
|
||||
/usr/local/go/src/syscall/mksyscall_windows.go:310:2: ineffectual assignment to s
|
||||
/usr/local/go/src/syscall/syscall_bsd_test.go:23:2: ineffectual assignment to n
|
||||
/usr/local/go/src/syscall/syscall_unix_test.go:187:17: ineffectual assignment to err
|
||||
/usr/local/go/src/text/template/multi_test.go:249:9: ineffectual assignment to err
|
||||
591
vendor/github.com/gordonklaus/ineffassign/pkg/ineffassign/ineffassign.go
generated
vendored
Normal file
591
vendor/github.com/gordonklaus/ineffassign/pkg/ineffassign/ineffassign.go
generated
vendored
Normal file
@@ -0,0 +1,591 @@
|
||||
package ineffassign
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// Analyzer is the ineffassign analysis.Analyzer instance.
|
||||
var Analyzer = &analysis.Analyzer{
|
||||
Name: "ineffassign",
|
||||
Doc: "detect ineffectual assignments in Go code",
|
||||
Run: checkPath,
|
||||
}
|
||||
|
||||
func checkPath(pass *analysis.Pass) (interface{}, error) {
|
||||
for _, file := range pass.Files {
|
||||
if isGenerated(file) {
|
||||
continue
|
||||
}
|
||||
|
||||
bld := &builder{vars: map[*ast.Object]*variable{}}
|
||||
bld.walk(file)
|
||||
|
||||
chk := &checker{vars: bld.vars, seen: map[*block]bool{}}
|
||||
for _, b := range bld.roots {
|
||||
chk.check(b)
|
||||
}
|
||||
sort.Sort(chk.ineff)
|
||||
|
||||
for _, id := range chk.ineff {
|
||||
pass.Report(analysis.Diagnostic{
|
||||
Pos: id.Pos(),
|
||||
Message: fmt.Sprintf("ineffectual assignment to %s", id.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func isGenerated(file *ast.File) bool {
|
||||
for _, cg := range file.Comments {
|
||||
for _, c := range cg.List {
|
||||
if strings.HasPrefix(c.Text, "// Code generated ") && strings.HasSuffix(c.Text, " DO NOT EDIT.") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type builder struct {
|
||||
roots []*block
|
||||
block *block
|
||||
vars map[*ast.Object]*variable
|
||||
results []*ast.FieldList
|
||||
breaks branchStack
|
||||
continues branchStack
|
||||
gotos branchStack
|
||||
labelStmt *ast.LabeledStmt
|
||||
}
|
||||
|
||||
type block struct {
|
||||
children []*block
|
||||
ops map[*ast.Object][]operation
|
||||
}
|
||||
|
||||
func (b *block) addChild(c *block) {
|
||||
b.children = append(b.children, c)
|
||||
}
|
||||
|
||||
type operation struct {
|
||||
id *ast.Ident
|
||||
assign bool
|
||||
}
|
||||
|
||||
type variable struct {
|
||||
fundept int
|
||||
escapes bool
|
||||
}
|
||||
|
||||
func (bld *builder) walk(n ast.Node) {
|
||||
if n != nil {
|
||||
ast.Walk(bld, n)
|
||||
}
|
||||
}
|
||||
|
||||
func (bld *builder) Visit(n ast.Node) ast.Visitor {
|
||||
switch n := n.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if n.Body != nil {
|
||||
bld.fun(n.Type, n.Body)
|
||||
}
|
||||
case *ast.FuncLit:
|
||||
bld.fun(n.Type, n.Body)
|
||||
case *ast.IfStmt:
|
||||
bld.walk(n.Init)
|
||||
bld.walk(n.Cond)
|
||||
b0 := bld.block
|
||||
bld.newBlock(b0)
|
||||
bld.walk(n.Body)
|
||||
b1 := bld.block
|
||||
if n.Else != nil {
|
||||
bld.newBlock(b0)
|
||||
bld.walk(n.Else)
|
||||
b0 = bld.block
|
||||
}
|
||||
bld.newBlock(b0, b1)
|
||||
case *ast.ForStmt:
|
||||
lbl := bld.stmtLabel(n)
|
||||
brek := bld.breaks.push(lbl)
|
||||
continu := bld.continues.push(lbl)
|
||||
bld.walk(n.Init)
|
||||
start := bld.newBlock(bld.block)
|
||||
bld.walk(n.Cond)
|
||||
cond := bld.block
|
||||
bld.newBlock(cond)
|
||||
bld.walk(n.Body)
|
||||
continu.setDestination(bld.newBlock(bld.block))
|
||||
bld.walk(n.Post)
|
||||
bld.block.addChild(start)
|
||||
brek.setDestination(bld.newBlock(cond))
|
||||
bld.breaks.pop()
|
||||
bld.continues.pop()
|
||||
case *ast.RangeStmt:
|
||||
lbl := bld.stmtLabel(n)
|
||||
brek := bld.breaks.push(lbl)
|
||||
continu := bld.continues.push(lbl)
|
||||
bld.walk(n.X)
|
||||
pre := bld.newBlock(bld.block)
|
||||
start := bld.newBlock(pre)
|
||||
if n.Key != nil {
|
||||
lhs := []ast.Expr{n.Key}
|
||||
if n.Value != nil {
|
||||
lhs = append(lhs, n.Value)
|
||||
}
|
||||
bld.walk(&ast.AssignStmt{Lhs: lhs, Tok: n.Tok, TokPos: n.TokPos, Rhs: []ast.Expr{&ast.Ident{NamePos: n.X.End()}}})
|
||||
}
|
||||
bld.walk(n.Body)
|
||||
bld.block.addChild(start)
|
||||
continu.setDestination(pre)
|
||||
brek.setDestination(bld.newBlock(pre, bld.block))
|
||||
bld.breaks.pop()
|
||||
bld.continues.pop()
|
||||
case *ast.SwitchStmt:
|
||||
bld.walk(n.Init)
|
||||
bld.walk(n.Tag)
|
||||
bld.swtch(n, n.Body.List)
|
||||
case *ast.TypeSwitchStmt:
|
||||
bld.walk(n.Init)
|
||||
bld.walk(n.Assign)
|
||||
bld.swtch(n, n.Body.List)
|
||||
case *ast.SelectStmt:
|
||||
brek := bld.breaks.push(bld.stmtLabel(n))
|
||||
for _, c := range n.Body.List {
|
||||
c := c.(*ast.CommClause).Comm
|
||||
if s, ok := c.(*ast.AssignStmt); ok {
|
||||
bld.walk(s.Rhs[0])
|
||||
} else {
|
||||
bld.walk(c)
|
||||
}
|
||||
}
|
||||
b0 := bld.block
|
||||
exits := make([]*block, len(n.Body.List))
|
||||
dfault := false
|
||||
for i, c := range n.Body.List {
|
||||
c := c.(*ast.CommClause)
|
||||
bld.newBlock(b0)
|
||||
bld.walk(c)
|
||||
exits[i] = bld.block
|
||||
dfault = dfault || c.Comm == nil
|
||||
}
|
||||
if !dfault {
|
||||
exits = append(exits, b0)
|
||||
}
|
||||
brek.setDestination(bld.newBlock(exits...))
|
||||
bld.breaks.pop()
|
||||
case *ast.LabeledStmt:
|
||||
bld.gotos.get(n.Label).setDestination(bld.newBlock(bld.block))
|
||||
bld.labelStmt = n
|
||||
bld.walk(n.Stmt)
|
||||
case *ast.BranchStmt:
|
||||
switch n.Tok {
|
||||
case token.BREAK:
|
||||
bld.breaks.get(n.Label).addSource(bld.block)
|
||||
bld.newBlock()
|
||||
case token.CONTINUE:
|
||||
bld.continues.get(n.Label).addSource(bld.block)
|
||||
bld.newBlock()
|
||||
case token.GOTO:
|
||||
bld.gotos.get(n.Label).addSource(bld.block)
|
||||
bld.newBlock()
|
||||
}
|
||||
|
||||
case *ast.AssignStmt:
|
||||
if n.Tok == token.QUO_ASSIGN || n.Tok == token.REM_ASSIGN {
|
||||
bld.maybePanic()
|
||||
}
|
||||
|
||||
for _, x := range n.Rhs {
|
||||
bld.walk(x)
|
||||
}
|
||||
for i, x := range n.Lhs {
|
||||
if id, ok := ident(x); ok {
|
||||
if n.Tok >= token.ADD_ASSIGN && n.Tok <= token.AND_NOT_ASSIGN {
|
||||
bld.use(id)
|
||||
}
|
||||
// Don't treat explicit initialization to zero as assignment; it is often used as shorthand for a bare declaration.
|
||||
if n.Tok == token.DEFINE && i < len(n.Rhs) && isZeroInitializer(n.Rhs[i]) {
|
||||
bld.use(id)
|
||||
} else {
|
||||
bld.assign(id)
|
||||
}
|
||||
} else {
|
||||
bld.walk(x)
|
||||
}
|
||||
}
|
||||
case *ast.GenDecl:
|
||||
if n.Tok == token.VAR {
|
||||
for _, s := range n.Specs {
|
||||
s := s.(*ast.ValueSpec)
|
||||
for _, x := range s.Values {
|
||||
bld.walk(x)
|
||||
}
|
||||
for _, id := range s.Names {
|
||||
if len(s.Values) > 0 {
|
||||
bld.assign(id)
|
||||
} else {
|
||||
bld.use(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case *ast.IncDecStmt:
|
||||
if id, ok := ident(n.X); ok {
|
||||
bld.use(id)
|
||||
bld.assign(id)
|
||||
} else {
|
||||
bld.walk(n.X)
|
||||
}
|
||||
case *ast.Ident:
|
||||
bld.use(n)
|
||||
case *ast.ReturnStmt:
|
||||
for _, x := range n.Results {
|
||||
bld.walk(x)
|
||||
}
|
||||
if res := bld.results[len(bld.results)-1]; res != nil {
|
||||
for _, f := range res.List {
|
||||
for _, id := range f.Names {
|
||||
if n.Results != nil {
|
||||
bld.assign(id)
|
||||
}
|
||||
bld.use(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
bld.newBlock()
|
||||
case *ast.SendStmt:
|
||||
bld.maybePanic()
|
||||
return bld
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
if n.Op == token.EQL || n.Op == token.QUO || n.Op == token.REM {
|
||||
bld.maybePanic()
|
||||
}
|
||||
return bld
|
||||
case *ast.CallExpr:
|
||||
bld.maybePanic()
|
||||
return bld
|
||||
case *ast.IndexExpr:
|
||||
bld.maybePanic()
|
||||
return bld
|
||||
case *ast.UnaryExpr:
|
||||
id, ok := ident(n.X)
|
||||
if ix, isIx := n.X.(*ast.IndexExpr); isIx {
|
||||
// We don't care about indexing into slices, but without type information we can do no better.
|
||||
id, ok = ident(ix.X)
|
||||
}
|
||||
if ok && n.Op == token.AND {
|
||||
if v, ok := bld.vars[id.Obj]; ok {
|
||||
v.escapes = true
|
||||
}
|
||||
}
|
||||
return bld
|
||||
case *ast.SelectorExpr:
|
||||
bld.maybePanic()
|
||||
// A method call (possibly delayed via a method value) might implicitly take
|
||||
// the address of its receiver, causing it to escape.
|
||||
// We can't do any better here without knowing the variable's type.
|
||||
if id, ok := ident(n.X); ok {
|
||||
if v, ok := bld.vars[id.Obj]; ok {
|
||||
v.escapes = true
|
||||
}
|
||||
}
|
||||
return bld
|
||||
case *ast.SliceExpr:
|
||||
bld.maybePanic()
|
||||
// We don't care about slicing into slices, but without type information we can do no better.
|
||||
if id, ok := ident(n.X); ok {
|
||||
if v, ok := bld.vars[id.Obj]; ok {
|
||||
v.escapes = true
|
||||
}
|
||||
}
|
||||
return bld
|
||||
case *ast.StarExpr:
|
||||
bld.maybePanic()
|
||||
return bld
|
||||
case *ast.TypeAssertExpr:
|
||||
bld.maybePanic()
|
||||
return bld
|
||||
|
||||
default:
|
||||
return bld
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isZeroInitializer(x ast.Expr) bool {
|
||||
// Assume that a call expression of a single argument is a conversion expression. We can't do better without type information.
|
||||
if c, ok := x.(*ast.CallExpr); ok {
|
||||
switch c.Fun.(type) {
|
||||
case *ast.Ident, *ast.SelectorExpr:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
if len(c.Args) != 1 {
|
||||
return false
|
||||
}
|
||||
x = c.Args[0]
|
||||
}
|
||||
|
||||
switch x := x.(type) {
|
||||
case *ast.BasicLit:
|
||||
switch x.Value {
|
||||
case "0", "0.0", "0.", ".0", `""`:
|
||||
return true
|
||||
}
|
||||
case *ast.Ident:
|
||||
return x.Name == "false" && x.Obj == nil
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (bld *builder) fun(typ *ast.FuncType, body *ast.BlockStmt) {
|
||||
for _, v := range bld.vars {
|
||||
v.fundept++
|
||||
}
|
||||
bld.results = append(bld.results, typ.Results)
|
||||
|
||||
b := bld.block
|
||||
bld.newBlock()
|
||||
bld.roots = append(bld.roots, bld.block)
|
||||
bld.walk(typ)
|
||||
bld.walk(body)
|
||||
bld.block = b
|
||||
|
||||
bld.results = bld.results[:len(bld.results)-1]
|
||||
for _, v := range bld.vars {
|
||||
v.fundept--
|
||||
}
|
||||
}
|
||||
|
||||
func (bld *builder) swtch(stmt ast.Stmt, cases []ast.Stmt) {
|
||||
brek := bld.breaks.push(bld.stmtLabel(stmt))
|
||||
b0 := bld.block
|
||||
list := b0
|
||||
exits := make([]*block, 0, len(cases)+1)
|
||||
var dfault, fallthru *block
|
||||
for _, c := range cases {
|
||||
c := c.(*ast.CaseClause)
|
||||
|
||||
if c.List != nil {
|
||||
list = bld.newBlock(list)
|
||||
for _, x := range c.List {
|
||||
bld.walk(x)
|
||||
}
|
||||
}
|
||||
|
||||
parents := []*block{}
|
||||
if c.List != nil {
|
||||
parents = append(parents, list)
|
||||
}
|
||||
if fallthru != nil {
|
||||
parents = append(parents, fallthru)
|
||||
fallthru = nil
|
||||
}
|
||||
bld.newBlock(parents...)
|
||||
if c.List == nil {
|
||||
dfault = bld.block
|
||||
}
|
||||
for _, s := range c.Body {
|
||||
bld.walk(s)
|
||||
if s, ok := s.(*ast.BranchStmt); ok && s.Tok == token.FALLTHROUGH {
|
||||
fallthru = bld.block
|
||||
}
|
||||
}
|
||||
|
||||
if fallthru == nil {
|
||||
exits = append(exits, bld.block)
|
||||
}
|
||||
}
|
||||
if dfault != nil {
|
||||
list.addChild(dfault)
|
||||
} else {
|
||||
exits = append(exits, b0)
|
||||
}
|
||||
brek.setDestination(bld.newBlock(exits...))
|
||||
bld.breaks.pop()
|
||||
}
|
||||
|
||||
// An operation that might panic marks named function results as used.
|
||||
func (bld *builder) maybePanic() {
|
||||
if len(bld.results) == 0 {
|
||||
return
|
||||
}
|
||||
res := bld.results[len(bld.results)-1]
|
||||
if res == nil {
|
||||
return
|
||||
}
|
||||
for _, f := range res.List {
|
||||
for _, id := range f.Names {
|
||||
bld.use(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (bld *builder) newBlock(parents ...*block) *block {
|
||||
bld.block = &block{ops: map[*ast.Object][]operation{}}
|
||||
for _, b := range parents {
|
||||
b.addChild(bld.block)
|
||||
}
|
||||
return bld.block
|
||||
}
|
||||
|
||||
func (bld *builder) stmtLabel(s ast.Stmt) *ast.Object {
|
||||
if ls := bld.labelStmt; ls != nil && ls.Stmt == s {
|
||||
return ls.Label.Obj
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bld *builder) assign(id *ast.Ident) {
|
||||
bld.newOp(id, true)
|
||||
}
|
||||
|
||||
func (bld *builder) use(id *ast.Ident) {
|
||||
bld.newOp(id, false)
|
||||
}
|
||||
|
||||
func (bld *builder) newOp(id *ast.Ident, assign bool) {
|
||||
if id.Name == "_" || id.Obj == nil {
|
||||
return
|
||||
}
|
||||
|
||||
v, ok := bld.vars[id.Obj]
|
||||
if !ok {
|
||||
v = &variable{}
|
||||
bld.vars[id.Obj] = v
|
||||
}
|
||||
v.escapes = v.escapes || v.fundept > 0 || bld.block == nil
|
||||
|
||||
if b := bld.block; b != nil && !v.escapes {
|
||||
b.ops[id.Obj] = append(b.ops[id.Obj], operation{id, assign})
|
||||
}
|
||||
}
|
||||
|
||||
type branchStack []*branch
|
||||
|
||||
type branch struct {
|
||||
label *ast.Object
|
||||
srcs []*block
|
||||
dst *block
|
||||
}
|
||||
|
||||
func (s *branchStack) push(lbl *ast.Object) *branch {
|
||||
br := &branch{label: lbl}
|
||||
*s = append(*s, br)
|
||||
return br
|
||||
}
|
||||
|
||||
func (s *branchStack) get(lbl *ast.Ident) *branch {
|
||||
for i := len(*s) - 1; i >= 0; i-- {
|
||||
if br := (*s)[i]; lbl == nil || br.label == lbl.Obj {
|
||||
return br
|
||||
}
|
||||
}
|
||||
|
||||
// Guard against invalid code (break/continue outside of loop).
|
||||
if lbl == nil {
|
||||
return &branch{}
|
||||
}
|
||||
|
||||
return s.push(lbl.Obj)
|
||||
}
|
||||
|
||||
func (br *branch) addSource(src *block) {
|
||||
br.srcs = append(br.srcs, src)
|
||||
if br.dst != nil {
|
||||
src.addChild(br.dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (br *branch) setDestination(dst *block) {
|
||||
br.dst = dst
|
||||
for _, src := range br.srcs {
|
||||
src.addChild(dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *branchStack) pop() {
|
||||
*s = (*s)[:len(*s)-1]
|
||||
}
|
||||
|
||||
func ident(x ast.Expr) (*ast.Ident, bool) {
|
||||
if p, ok := x.(*ast.ParenExpr); ok {
|
||||
return ident(p.X)
|
||||
}
|
||||
id, ok := x.(*ast.Ident)
|
||||
return id, ok
|
||||
}
|
||||
|
||||
type checker struct {
|
||||
vars map[*ast.Object]*variable
|
||||
seen map[*block]bool
|
||||
ineff idents
|
||||
}
|
||||
|
||||
func (chk *checker) check(b *block) {
|
||||
if chk.seen[b] {
|
||||
return
|
||||
}
|
||||
chk.seen[b] = true
|
||||
|
||||
for obj, ops := range b.ops {
|
||||
ops:
|
||||
for i, op := range ops {
|
||||
if !op.assign {
|
||||
continue
|
||||
}
|
||||
if i+1 < len(ops) {
|
||||
if ops[i+1].assign {
|
||||
chk.ineff = append(chk.ineff, op.id)
|
||||
}
|
||||
continue
|
||||
}
|
||||
seen := map[*block]bool{}
|
||||
for _, b := range b.children {
|
||||
if used(obj, b, seen) {
|
||||
continue ops
|
||||
}
|
||||
}
|
||||
if !chk.vars[obj].escapes {
|
||||
chk.ineff = append(chk.ineff, op.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, b := range b.children {
|
||||
chk.check(b)
|
||||
}
|
||||
}
|
||||
|
||||
func used(obj *ast.Object, b *block, seen map[*block]bool) bool {
|
||||
if seen[b] {
|
||||
return false
|
||||
}
|
||||
seen[b] = true
|
||||
|
||||
if ops := b.ops[obj]; len(ops) > 0 {
|
||||
return !ops[0].assign
|
||||
}
|
||||
for _, b := range b.children {
|
||||
if used(obj, b, seen) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type idents []*ast.Ident
|
||||
|
||||
func (ids idents) Len() int { return len(ids) }
|
||||
func (ids idents) Less(i, j int) bool { return ids[i].Pos() < ids[j].Pos() }
|
||||
func (ids idents) Swap(i, j int) { ids[i], ids[j] = ids[j], ids[i] }
|
||||
388
vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go
generated
vendored
Normal file
388
vendor/golang.org/x/tools/go/analysis/internal/analysisflags/flags.go
generated
vendored
Normal file
@@ -0,0 +1,388 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package analysisflags defines helpers for processing flags of
|
||||
// analysis driver tools.
|
||||
package analysisflags
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
// flags common to all {single,multi,unit}checkers.
|
||||
var (
|
||||
JSON = false // -json
|
||||
Context = -1 // -c=N: if N>0, display offending line plus N lines of context
|
||||
)
|
||||
|
||||
// Parse creates a flag for each of the analyzer's flags,
|
||||
// including (in multi mode) a flag named after the analyzer,
|
||||
// parses the flags, then filters and returns the list of
|
||||
// analyzers enabled by flags.
|
||||
//
|
||||
// The result is intended to be passed to unitchecker.Run or checker.Run.
|
||||
// Use in unitchecker.Run will gob.Register all fact types for the returned
|
||||
// graph of analyzers but of course not the ones only reachable from
|
||||
// dropped analyzers. To avoid inconsistency about which gob types are
|
||||
// registered from run to run, Parse itself gob.Registers all the facts
|
||||
// only reachable from dropped analyzers.
|
||||
// This is not a particularly elegant API, but this is an internal package.
|
||||
func Parse(analyzers []*analysis.Analyzer, multi bool) []*analysis.Analyzer {
|
||||
// Connect each analysis flag to the command line as -analysis.flag.
|
||||
enabled := make(map[*analysis.Analyzer]*triState)
|
||||
for _, a := range analyzers {
|
||||
var prefix string
|
||||
|
||||
// Add -NAME flag to enable it.
|
||||
if multi {
|
||||
prefix = a.Name + "."
|
||||
|
||||
enable := new(triState)
|
||||
enableUsage := "enable " + a.Name + " analysis"
|
||||
flag.Var(enable, a.Name, enableUsage)
|
||||
enabled[a] = enable
|
||||
}
|
||||
|
||||
a.Flags.VisitAll(func(f *flag.Flag) {
|
||||
if !multi && flag.Lookup(f.Name) != nil {
|
||||
log.Printf("%s flag -%s would conflict with driver; skipping", a.Name, f.Name)
|
||||
return
|
||||
}
|
||||
|
||||
name := prefix + f.Name
|
||||
flag.Var(f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
|
||||
// standard flags: -flags, -V.
|
||||
printflags := flag.Bool("flags", false, "print analyzer flags in JSON")
|
||||
addVersionFlag()
|
||||
|
||||
// flags common to all checkers
|
||||
flag.BoolVar(&JSON, "json", JSON, "emit JSON output")
|
||||
flag.IntVar(&Context, "c", Context, `display offending line with this many lines of context`)
|
||||
|
||||
// Add shims for legacy vet flags to enable existing
|
||||
// scripts that run vet to continue to work.
|
||||
_ = flag.Bool("source", false, "no effect (deprecated)")
|
||||
_ = flag.Bool("v", false, "no effect (deprecated)")
|
||||
_ = flag.Bool("all", false, "no effect (deprecated)")
|
||||
_ = flag.String("tags", "", "no effect (deprecated)")
|
||||
for old, new := range vetLegacyFlags {
|
||||
newFlag := flag.Lookup(new)
|
||||
if newFlag != nil && flag.Lookup(old) == nil {
|
||||
flag.Var(newFlag.Value, old, "deprecated alias for -"+new)
|
||||
}
|
||||
}
|
||||
|
||||
flag.Parse() // (ExitOnError)
|
||||
|
||||
// -flags: print flags so that go vet knows which ones are legitimate.
|
||||
if *printflags {
|
||||
printFlags()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
everything := expand(analyzers)
|
||||
|
||||
// If any -NAME flag is true, run only those analyzers. Otherwise,
|
||||
// if any -NAME flag is false, run all but those analyzers.
|
||||
if multi {
|
||||
var hasTrue, hasFalse bool
|
||||
for _, ts := range enabled {
|
||||
switch *ts {
|
||||
case setTrue:
|
||||
hasTrue = true
|
||||
case setFalse:
|
||||
hasFalse = true
|
||||
}
|
||||
}
|
||||
|
||||
var keep []*analysis.Analyzer
|
||||
if hasTrue {
|
||||
for _, a := range analyzers {
|
||||
if *enabled[a] == setTrue {
|
||||
keep = append(keep, a)
|
||||
}
|
||||
}
|
||||
analyzers = keep
|
||||
} else if hasFalse {
|
||||
for _, a := range analyzers {
|
||||
if *enabled[a] != setFalse {
|
||||
keep = append(keep, a)
|
||||
}
|
||||
}
|
||||
analyzers = keep
|
||||
}
|
||||
}
|
||||
|
||||
// Register fact types of skipped analyzers
|
||||
// in case we encounter them in imported files.
|
||||
kept := expand(analyzers)
|
||||
for a := range everything {
|
||||
if !kept[a] {
|
||||
for _, f := range a.FactTypes {
|
||||
gob.Register(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return analyzers
|
||||
}
|
||||
|
||||
func expand(analyzers []*analysis.Analyzer) map[*analysis.Analyzer]bool {
|
||||
seen := make(map[*analysis.Analyzer]bool)
|
||||
var visitAll func([]*analysis.Analyzer)
|
||||
visitAll = func(analyzers []*analysis.Analyzer) {
|
||||
for _, a := range analyzers {
|
||||
if !seen[a] {
|
||||
seen[a] = true
|
||||
visitAll(a.Requires)
|
||||
}
|
||||
}
|
||||
}
|
||||
visitAll(analyzers)
|
||||
return seen
|
||||
}
|
||||
|
||||
func printFlags() {
|
||||
type jsonFlag struct {
|
||||
Name string
|
||||
Bool bool
|
||||
Usage string
|
||||
}
|
||||
var flags []jsonFlag = nil
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
// Don't report {single,multi}checker debugging
|
||||
// flags or fix as these have no effect on unitchecker
|
||||
// (as invoked by 'go vet').
|
||||
switch f.Name {
|
||||
case "debug", "cpuprofile", "memprofile", "trace", "fix":
|
||||
return
|
||||
}
|
||||
|
||||
b, ok := f.Value.(interface{ IsBoolFlag() bool })
|
||||
isBool := ok && b.IsBoolFlag()
|
||||
flags = append(flags, jsonFlag{f.Name, isBool, f.Usage})
|
||||
})
|
||||
data, err := json.MarshalIndent(flags, "", "\t")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
os.Stdout.Write(data)
|
||||
}
|
||||
|
||||
// addVersionFlag registers a -V flag that, if set,
|
||||
// prints the executable version and exits 0.
|
||||
//
|
||||
// If the -V flag already exists — for example, because it was already
|
||||
// registered by a call to cmd/internal/objabi.AddVersionFlag — then
|
||||
// addVersionFlag does nothing.
|
||||
func addVersionFlag() {
|
||||
if flag.Lookup("V") == nil {
|
||||
flag.Var(versionFlag{}, "V", "print version and exit")
|
||||
}
|
||||
}
|
||||
|
||||
// versionFlag minimally complies with the -V protocol required by "go vet".
|
||||
type versionFlag struct{}
|
||||
|
||||
func (versionFlag) IsBoolFlag() bool { return true }
|
||||
func (versionFlag) Get() interface{} { return nil }
|
||||
func (versionFlag) String() string { return "" }
|
||||
func (versionFlag) Set(s string) error {
|
||||
if s != "full" {
|
||||
log.Fatalf("unsupported flag value: -V=%s", s)
|
||||
}
|
||||
|
||||
// This replicates the minimal subset of
|
||||
// cmd/internal/objabi.AddVersionFlag, which is private to the
|
||||
// go tool yet forms part of our command-line interface.
|
||||
// TODO(adonovan): clarify the contract.
|
||||
|
||||
// Print the tool version so the build system can track changes.
|
||||
// Formats:
|
||||
// $progname version devel ... buildID=...
|
||||
// $progname version go1.9.1
|
||||
progname := os.Args[0]
|
||||
f, err := os.Open(progname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
fmt.Printf("%s version devel comments-go-here buildID=%02x\n",
|
||||
progname, string(h.Sum(nil)))
|
||||
os.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// A triState is a boolean that knows whether
|
||||
// it has been set to either true or false.
|
||||
// It is used to identify whether a flag appears;
|
||||
// the standard boolean flag cannot
|
||||
// distinguish missing from unset.
|
||||
// It also satisfies flag.Value.
|
||||
type triState int
|
||||
|
||||
const (
|
||||
unset triState = iota
|
||||
setTrue
|
||||
setFalse
|
||||
)
|
||||
|
||||
func triStateFlag(name string, value triState, usage string) *triState {
|
||||
flag.Var(&value, name, usage)
|
||||
return &value
|
||||
}
|
||||
|
||||
// triState implements flag.Value, flag.Getter, and flag.boolFlag.
|
||||
// They work like boolean flags: we can say vet -printf as well as vet -printf=true
|
||||
func (ts *triState) Get() interface{} {
|
||||
return *ts == setTrue
|
||||
}
|
||||
|
||||
func (ts triState) isTrue() bool {
|
||||
return ts == setTrue
|
||||
}
|
||||
|
||||
func (ts *triState) Set(value string) error {
|
||||
b, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
// This error message looks poor but package "flag" adds
|
||||
// "invalid boolean value %q for -NAME: %s"
|
||||
return fmt.Errorf("want true or false")
|
||||
}
|
||||
if b {
|
||||
*ts = setTrue
|
||||
} else {
|
||||
*ts = setFalse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *triState) String() string {
|
||||
switch *ts {
|
||||
case unset:
|
||||
return "true"
|
||||
case setTrue:
|
||||
return "true"
|
||||
case setFalse:
|
||||
return "false"
|
||||
}
|
||||
panic("not reached")
|
||||
}
|
||||
|
||||
func (ts triState) IsBoolFlag() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Legacy flag support
|
||||
|
||||
// vetLegacyFlags maps flags used by legacy vet to their corresponding
|
||||
// new names. The old names will continue to work.
|
||||
var vetLegacyFlags = map[string]string{
|
||||
// Analyzer name changes
|
||||
"bool": "bools",
|
||||
"buildtags": "buildtag",
|
||||
"methods": "stdmethods",
|
||||
"rangeloops": "loopclosure",
|
||||
|
||||
// Analyzer flags
|
||||
"compositewhitelist": "composites.whitelist",
|
||||
"printfuncs": "printf.funcs",
|
||||
"shadowstrict": "shadow.strict",
|
||||
"unusedfuncs": "unusedresult.funcs",
|
||||
"unusedstringmethods": "unusedresult.stringmethods",
|
||||
}
|
||||
|
||||
// ---- output helpers common to all drivers ----
|
||||
|
||||
// PrintPlain prints a diagnostic in plain text form,
|
||||
// with context specified by the -c flag.
|
||||
func PrintPlain(fset *token.FileSet, diag analysis.Diagnostic) {
|
||||
posn := fset.Position(diag.Pos)
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", posn, diag.Message)
|
||||
|
||||
// -c=N: show offending line plus N lines of context.
|
||||
if Context >= 0 {
|
||||
posn := fset.Position(diag.Pos)
|
||||
end := fset.Position(diag.End)
|
||||
if !end.IsValid() {
|
||||
end = posn
|
||||
}
|
||||
data, _ := ioutil.ReadFile(posn.Filename)
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for i := posn.Line - Context; i <= end.Line+Context; i++ {
|
||||
if 1 <= i && i <= len(lines) {
|
||||
fmt.Fprintf(os.Stderr, "%d\t%s\n", i, lines[i-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A JSONTree is a mapping from package ID to analysis name to result.
|
||||
// Each result is either a jsonError or a list of jsonDiagnostic.
|
||||
type JSONTree map[string]map[string]interface{}
|
||||
|
||||
// Add adds the result of analysis 'name' on package 'id'.
|
||||
// The result is either a list of diagnostics or an error.
|
||||
func (tree JSONTree) Add(fset *token.FileSet, id, name string, diags []analysis.Diagnostic, err error) {
|
||||
var v interface{}
|
||||
if err != nil {
|
||||
type jsonError struct {
|
||||
Err string `json:"error"`
|
||||
}
|
||||
v = jsonError{err.Error()}
|
||||
} else if len(diags) > 0 {
|
||||
type jsonDiagnostic struct {
|
||||
Category string `json:"category,omitempty"`
|
||||
Posn string `json:"posn"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
var diagnostics []jsonDiagnostic
|
||||
// TODO(matloob): Should the JSON diagnostics contain ranges?
|
||||
// If so, how should they be formatted?
|
||||
for _, f := range diags {
|
||||
diagnostics = append(diagnostics, jsonDiagnostic{
|
||||
Category: f.Category,
|
||||
Posn: fset.Position(f.Pos).String(),
|
||||
Message: f.Message,
|
||||
})
|
||||
}
|
||||
v = diagnostics
|
||||
}
|
||||
if v != nil {
|
||||
m, ok := tree[id]
|
||||
if !ok {
|
||||
m = make(map[string]interface{})
|
||||
tree[id] = m
|
||||
}
|
||||
m[name] = v
|
||||
}
|
||||
}
|
||||
|
||||
func (tree JSONTree) Print() {
|
||||
data, err := json.MarshalIndent(tree, "", "\t")
|
||||
if err != nil {
|
||||
log.Panicf("internal error: JSON marshaling failed: %v", err)
|
||||
}
|
||||
fmt.Printf("%s\n", data)
|
||||
}
|
||||
96
vendor/golang.org/x/tools/go/analysis/internal/analysisflags/help.go
generated
vendored
Normal file
96
vendor/golang.org/x/tools/go/analysis/internal/analysisflags/help.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package analysisflags
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
)
|
||||
|
||||
const help = `PROGNAME is a tool for static analysis of Go programs.
|
||||
|
||||
PROGNAME examines Go source code and reports suspicious constructs,
|
||||
such as Printf calls whose arguments do not align with the format
|
||||
string. It uses heuristics that do not guarantee all reports are
|
||||
genuine problems, but it can find errors not caught by the compilers.
|
||||
`
|
||||
|
||||
// Help implements the help subcommand for a multichecker or unitchecker
|
||||
// style command. The optional args specify the analyzers to describe.
|
||||
// Help calls log.Fatal if no such analyzer exists.
|
||||
func Help(progname string, analyzers []*analysis.Analyzer, args []string) {
|
||||
// No args: show summary of all analyzers.
|
||||
if len(args) == 0 {
|
||||
fmt.Println(strings.Replace(help, "PROGNAME", progname, -1))
|
||||
fmt.Println("Registered analyzers:")
|
||||
fmt.Println()
|
||||
sort.Slice(analyzers, func(i, j int) bool {
|
||||
return analyzers[i].Name < analyzers[j].Name
|
||||
})
|
||||
for _, a := range analyzers {
|
||||
title := strings.Split(a.Doc, "\n\n")[0]
|
||||
fmt.Printf(" %-12s %s\n", a.Name, title)
|
||||
}
|
||||
fmt.Println("\nBy default all analyzers are run.")
|
||||
fmt.Println("To select specific analyzers, use the -NAME flag for each one,")
|
||||
fmt.Println(" or -NAME=false to run all analyzers not explicitly disabled.")
|
||||
|
||||
// Show only the core command-line flags.
|
||||
fmt.Println("\nCore flags:")
|
||||
fmt.Println()
|
||||
fs := flag.NewFlagSet("", flag.ExitOnError)
|
||||
flag.VisitAll(func(f *flag.Flag) {
|
||||
if !strings.Contains(f.Name, ".") {
|
||||
fs.Var(f.Value, f.Name, f.Usage)
|
||||
}
|
||||
})
|
||||
fs.SetOutput(os.Stdout)
|
||||
fs.PrintDefaults()
|
||||
|
||||
fmt.Printf("\nTo see details and flags of a specific analyzer, run '%s help name'.\n", progname)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Show help on specific analyzer(s).
|
||||
outer:
|
||||
for _, arg := range args {
|
||||
for _, a := range analyzers {
|
||||
if a.Name == arg {
|
||||
paras := strings.Split(a.Doc, "\n\n")
|
||||
title := paras[0]
|
||||
fmt.Printf("%s: %s\n", a.Name, title)
|
||||
|
||||
// Show only the flags relating to this analysis,
|
||||
// properly prefixed.
|
||||
first := true
|
||||
fs := flag.NewFlagSet(a.Name, flag.ExitOnError)
|
||||
a.Flags.VisitAll(func(f *flag.Flag) {
|
||||
if first {
|
||||
first = false
|
||||
fmt.Println("\nAnalyzer flags:")
|
||||
fmt.Println()
|
||||
}
|
||||
fs.Var(f.Value, a.Name+"."+f.Name, f.Usage)
|
||||
})
|
||||
fs.SetOutput(os.Stdout)
|
||||
fs.PrintDefaults()
|
||||
|
||||
if len(paras) > 1 {
|
||||
fmt.Printf("\n%s\n", strings.Join(paras[1:], "\n\n"))
|
||||
}
|
||||
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
log.Fatalf("Analyzer %q not registered", arg)
|
||||
}
|
||||
}
|
||||
907
vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go
generated
vendored
Normal file
907
vendor/golang.org/x/tools/go/analysis/internal/checker/checker.go
generated
vendored
Normal file
@@ -0,0 +1,907 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package checker defines the implementation of the checker commands.
|
||||
// The same code drives the multi-analysis driver, the single-analysis
|
||||
// driver that is conventionally provided for convenience along with
|
||||
// each analysis package, and the test driver.
|
||||
package checker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"runtime/trace"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/tools/internal/analysisinternal"
|
||||
"golang.org/x/tools/internal/span"
|
||||
)
|
||||
|
||||
var (
|
||||
// Debug is a set of single-letter flags:
|
||||
//
|
||||
// f show [f]acts as they are created
|
||||
// p disable [p]arallel execution of analyzers
|
||||
// s do additional [s]anity checks on fact types and serialization
|
||||
// t show [t]iming info (NB: use 'p' flag to avoid GC/scheduler noise)
|
||||
// v show [v]erbose logging
|
||||
//
|
||||
Debug = ""
|
||||
|
||||
// Log files for optional performance tracing.
|
||||
CPUProfile, MemProfile, Trace string
|
||||
|
||||
// Fix determines whether to apply all suggested fixes.
|
||||
Fix bool
|
||||
)
|
||||
|
||||
// RegisterFlags registers command-line flags used by the analysis driver.
|
||||
func RegisterFlags() {
|
||||
// When adding flags here, remember to update
|
||||
// the list of suppressed flags in analysisflags.
|
||||
|
||||
flag.StringVar(&Debug, "debug", Debug, `debug flags, any subset of "fpstv"`)
|
||||
|
||||
flag.StringVar(&CPUProfile, "cpuprofile", "", "write CPU profile to this file")
|
||||
flag.StringVar(&MemProfile, "memprofile", "", "write memory profile to this file")
|
||||
flag.StringVar(&Trace, "trace", "", "write trace log to this file")
|
||||
|
||||
flag.BoolVar(&Fix, "fix", false, "apply all suggested fixes")
|
||||
}
|
||||
|
||||
// Run loads the packages specified by args using go/packages,
|
||||
// then applies the specified analyzers to them.
|
||||
// Analysis flags must already have been set.
|
||||
// It provides most of the logic for the main functions of both the
|
||||
// singlechecker and the multi-analysis commands.
|
||||
// It returns the appropriate exit code.
|
||||
func Run(args []string, analyzers []*analysis.Analyzer) (exitcode int) {
|
||||
if CPUProfile != "" {
|
||||
f, err := os.Create(CPUProfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := pprof.StartCPUProfile(f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// NB: profile won't be written in case of error.
|
||||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
if Trace != "" {
|
||||
f, err := os.Create(Trace)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := trace.Start(f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// NB: trace log won't be written in case of error.
|
||||
defer func() {
|
||||
trace.Stop()
|
||||
log.Printf("To view the trace, run:\n$ go tool trace view %s", Trace)
|
||||
}()
|
||||
}
|
||||
|
||||
if MemProfile != "" {
|
||||
f, err := os.Create(MemProfile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// NB: memprofile won't be written in case of error.
|
||||
defer func() {
|
||||
runtime.GC() // get up-to-date statistics
|
||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||
log.Fatalf("Writing memory profile: %v", err)
|
||||
}
|
||||
f.Close()
|
||||
}()
|
||||
}
|
||||
|
||||
// Load the packages.
|
||||
if dbg('v') {
|
||||
log.SetPrefix("")
|
||||
log.SetFlags(log.Lmicroseconds) // display timing
|
||||
log.Printf("load %s", args)
|
||||
}
|
||||
|
||||
// Optimization: if the selected analyzers don't produce/consume
|
||||
// facts, we need source only for the initial packages.
|
||||
allSyntax := needFacts(analyzers)
|
||||
initial, err := load(args, allSyntax)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return 1 // load errors
|
||||
}
|
||||
|
||||
// Print the results.
|
||||
roots := analyze(initial, analyzers)
|
||||
|
||||
if Fix {
|
||||
applyFixes(roots)
|
||||
}
|
||||
|
||||
return printDiagnostics(roots)
|
||||
}
|
||||
|
||||
// load loads the initial packages.
|
||||
func load(patterns []string, allSyntax bool) ([]*packages.Package, error) {
|
||||
mode := packages.LoadSyntax
|
||||
if allSyntax {
|
||||
mode = packages.LoadAllSyntax
|
||||
}
|
||||
conf := packages.Config{
|
||||
Mode: mode,
|
||||
Tests: true,
|
||||
}
|
||||
initial, err := packages.Load(&conf, patterns...)
|
||||
if err == nil {
|
||||
if n := packages.PrintErrors(initial); n > 1 {
|
||||
err = fmt.Errorf("%d errors during loading", n)
|
||||
} else if n == 1 {
|
||||
err = fmt.Errorf("error during loading")
|
||||
} else if len(initial) == 0 {
|
||||
err = fmt.Errorf("%s matched no packages", strings.Join(patterns, " "))
|
||||
}
|
||||
}
|
||||
|
||||
return initial, err
|
||||
}
|
||||
|
||||
// TestAnalyzer applies an analysis to a set of packages (and their
|
||||
// dependencies if necessary) and returns the results.
|
||||
//
|
||||
// Facts about pkg are returned in a map keyed by object; package facts
|
||||
// have a nil key.
|
||||
//
|
||||
// This entry point is used only by analysistest.
|
||||
func TestAnalyzer(a *analysis.Analyzer, pkgs []*packages.Package) []*TestAnalyzerResult {
|
||||
var results []*TestAnalyzerResult
|
||||
for _, act := range analyze(pkgs, []*analysis.Analyzer{a}) {
|
||||
facts := make(map[types.Object][]analysis.Fact)
|
||||
for key, fact := range act.objectFacts {
|
||||
if key.obj.Pkg() == act.pass.Pkg {
|
||||
facts[key.obj] = append(facts[key.obj], fact)
|
||||
}
|
||||
}
|
||||
for key, fact := range act.packageFacts {
|
||||
if key.pkg == act.pass.Pkg {
|
||||
facts[nil] = append(facts[nil], fact)
|
||||
}
|
||||
}
|
||||
|
||||
results = append(results, &TestAnalyzerResult{act.pass, act.diagnostics, facts, act.result, act.err})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
type TestAnalyzerResult struct {
|
||||
Pass *analysis.Pass
|
||||
Diagnostics []analysis.Diagnostic
|
||||
Facts map[types.Object][]analysis.Fact
|
||||
Result interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
func analyze(pkgs []*packages.Package, analyzers []*analysis.Analyzer) []*action {
|
||||
// Construct the action graph.
|
||||
if dbg('v') {
|
||||
log.Printf("building graph of analysis passes")
|
||||
}
|
||||
|
||||
// Each graph node (action) is one unit of analysis.
|
||||
// Edges express package-to-package (vertical) dependencies,
|
||||
// and analysis-to-analysis (horizontal) dependencies.
|
||||
type key struct {
|
||||
*analysis.Analyzer
|
||||
*packages.Package
|
||||
}
|
||||
actions := make(map[key]*action)
|
||||
|
||||
var mkAction func(a *analysis.Analyzer, pkg *packages.Package) *action
|
||||
mkAction = func(a *analysis.Analyzer, pkg *packages.Package) *action {
|
||||
k := key{a, pkg}
|
||||
act, ok := actions[k]
|
||||
if !ok {
|
||||
act = &action{a: a, pkg: pkg}
|
||||
|
||||
// Add a dependency on each required analyzers.
|
||||
for _, req := range a.Requires {
|
||||
act.deps = append(act.deps, mkAction(req, pkg))
|
||||
}
|
||||
|
||||
// An analysis that consumes/produces facts
|
||||
// must run on the package's dependencies too.
|
||||
if len(a.FactTypes) > 0 {
|
||||
paths := make([]string, 0, len(pkg.Imports))
|
||||
for path := range pkg.Imports {
|
||||
paths = append(paths, path)
|
||||
}
|
||||
sort.Strings(paths) // for determinism
|
||||
for _, path := range paths {
|
||||
dep := mkAction(a, pkg.Imports[path])
|
||||
act.deps = append(act.deps, dep)
|
||||
}
|
||||
}
|
||||
|
||||
actions[k] = act
|
||||
}
|
||||
return act
|
||||
}
|
||||
|
||||
// Build nodes for initial packages.
|
||||
var roots []*action
|
||||
for _, a := range analyzers {
|
||||
for _, pkg := range pkgs {
|
||||
root := mkAction(a, pkg)
|
||||
root.isroot = true
|
||||
roots = append(roots, root)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the graph in parallel.
|
||||
execAll(roots)
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
func applyFixes(roots []*action) {
|
||||
visited := make(map[*action]bool)
|
||||
var apply func(*action) error
|
||||
var visitAll func(actions []*action) error
|
||||
visitAll = func(actions []*action) error {
|
||||
for _, act := range actions {
|
||||
if !visited[act] {
|
||||
visited[act] = true
|
||||
visitAll(act.deps)
|
||||
if err := apply(act); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(matloob): Is this tree business too complicated? (After all this is Go!)
|
||||
// Just create a set (map) of edits, sort by pos and call it a day?
|
||||
type offsetedit struct {
|
||||
start, end int
|
||||
newText []byte
|
||||
} // TextEdit using byteOffsets instead of pos
|
||||
type node struct {
|
||||
edit offsetedit
|
||||
left, right *node
|
||||
}
|
||||
|
||||
var insert func(tree **node, edit offsetedit) error
|
||||
insert = func(treeptr **node, edit offsetedit) error {
|
||||
if *treeptr == nil {
|
||||
*treeptr = &node{edit, nil, nil}
|
||||
return nil
|
||||
}
|
||||
tree := *treeptr
|
||||
if edit.end <= tree.edit.start {
|
||||
return insert(&tree.left, edit)
|
||||
} else if edit.start >= tree.edit.end {
|
||||
return insert(&tree.right, edit)
|
||||
}
|
||||
|
||||
// Overlapping text edit.
|
||||
return fmt.Errorf("analyses applying overlapping text edits affecting pos range (%v, %v) and (%v, %v)",
|
||||
edit.start, edit.end, tree.edit.start, tree.edit.end)
|
||||
|
||||
}
|
||||
|
||||
editsForFile := make(map[*token.File]*node)
|
||||
|
||||
apply = func(act *action) error {
|
||||
for _, diag := range act.diagnostics {
|
||||
for _, sf := range diag.SuggestedFixes {
|
||||
for _, edit := range sf.TextEdits {
|
||||
// Validate the edit.
|
||||
if edit.Pos > edit.End {
|
||||
return fmt.Errorf(
|
||||
"diagnostic for analysis %v contains Suggested Fix with malformed edit: pos (%v) > end (%v)",
|
||||
act.a.Name, edit.Pos, edit.End)
|
||||
}
|
||||
file, endfile := act.pkg.Fset.File(edit.Pos), act.pkg.Fset.File(edit.End)
|
||||
if file == nil || endfile == nil || file != endfile {
|
||||
return (fmt.Errorf(
|
||||
"diagnostic for analysis %v contains Suggested Fix with malformed spanning files %v and %v",
|
||||
act.a.Name, file.Name(), endfile.Name()))
|
||||
}
|
||||
start, end := file.Offset(edit.Pos), file.Offset(edit.End)
|
||||
|
||||
// TODO(matloob): Validate that edits do not affect other packages.
|
||||
root := editsForFile[file]
|
||||
if err := insert(&root, offsetedit{start, end, edit.NewText}); err != nil {
|
||||
return err
|
||||
}
|
||||
editsForFile[file] = root // In case the root changed
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
visitAll(roots)
|
||||
|
||||
fset := token.NewFileSet() // Shared by parse calls below
|
||||
// Now we've got a set of valid edits for each file. Get the new file contents.
|
||||
for f, tree := range editsForFile {
|
||||
contents, err := ioutil.ReadFile(f.Name())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
cur := 0 // current position in the file
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
var recurse func(*node)
|
||||
recurse = func(node *node) {
|
||||
if node.left != nil {
|
||||
recurse(node.left)
|
||||
}
|
||||
|
||||
edit := node.edit
|
||||
if edit.start > cur {
|
||||
out.Write(contents[cur:edit.start])
|
||||
out.Write(edit.newText)
|
||||
}
|
||||
cur = edit.end
|
||||
|
||||
if node.right != nil {
|
||||
recurse(node.right)
|
||||
}
|
||||
}
|
||||
recurse(tree)
|
||||
// Write out the rest of the file.
|
||||
if cur < len(contents) {
|
||||
out.Write(contents[cur:])
|
||||
}
|
||||
|
||||
// Try to format the file.
|
||||
ff, err := parser.ParseFile(fset, f.Name(), out.Bytes(), parser.ParseComments)
|
||||
if err == nil {
|
||||
var buf bytes.Buffer
|
||||
if err = format.Node(&buf, fset, ff); err == nil {
|
||||
out = buf
|
||||
}
|
||||
}
|
||||
|
||||
ioutil.WriteFile(f.Name(), out.Bytes(), 0644)
|
||||
}
|
||||
}
|
||||
|
||||
// printDiagnostics prints the diagnostics for the root packages in either
|
||||
// plain text or JSON format. JSON format also includes errors for any
|
||||
// dependencies.
|
||||
//
|
||||
// It returns the exitcode: in plain mode, 0 for success, 1 for analysis
|
||||
// errors, and 3 for diagnostics. We avoid 2 since the flag package uses
|
||||
// it. JSON mode always succeeds at printing errors and diagnostics in a
|
||||
// structured form to stdout.
|
||||
func printDiagnostics(roots []*action) (exitcode int) {
|
||||
// Print the output.
|
||||
//
|
||||
// Print diagnostics only for root packages,
|
||||
// but errors for all packages.
|
||||
printed := make(map[*action]bool)
|
||||
var print func(*action)
|
||||
var visitAll func(actions []*action)
|
||||
visitAll = func(actions []*action) {
|
||||
for _, act := range actions {
|
||||
if !printed[act] {
|
||||
printed[act] = true
|
||||
visitAll(act.deps)
|
||||
print(act)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if analysisflags.JSON {
|
||||
// JSON output
|
||||
tree := make(analysisflags.JSONTree)
|
||||
print = func(act *action) {
|
||||
var diags []analysis.Diagnostic
|
||||
if act.isroot {
|
||||
diags = act.diagnostics
|
||||
}
|
||||
tree.Add(act.pkg.Fset, act.pkg.ID, act.a.Name, diags, act.err)
|
||||
}
|
||||
visitAll(roots)
|
||||
tree.Print()
|
||||
} else {
|
||||
// plain text output
|
||||
|
||||
// De-duplicate diagnostics by position (not token.Pos) to
|
||||
// avoid double-reporting in source files that belong to
|
||||
// multiple packages, such as foo and foo.test.
|
||||
type key struct {
|
||||
pos token.Position
|
||||
end token.Position
|
||||
*analysis.Analyzer
|
||||
message string
|
||||
}
|
||||
seen := make(map[key]bool)
|
||||
|
||||
print = func(act *action) {
|
||||
if act.err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n", act.a.Name, act.err)
|
||||
exitcode = 1 // analysis failed, at least partially
|
||||
return
|
||||
}
|
||||
if act.isroot {
|
||||
for _, diag := range act.diagnostics {
|
||||
// We don't display a.Name/f.Category
|
||||
// as most users don't care.
|
||||
|
||||
posn := act.pkg.Fset.Position(diag.Pos)
|
||||
end := act.pkg.Fset.Position(diag.End)
|
||||
k := key{posn, end, act.a, diag.Message}
|
||||
if seen[k] {
|
||||
continue // duplicate
|
||||
}
|
||||
seen[k] = true
|
||||
|
||||
analysisflags.PrintPlain(act.pkg.Fset, diag)
|
||||
}
|
||||
}
|
||||
}
|
||||
visitAll(roots)
|
||||
|
||||
if exitcode == 0 && len(seen) > 0 {
|
||||
exitcode = 3 // successfully produced diagnostics
|
||||
}
|
||||
}
|
||||
|
||||
// Print timing info.
|
||||
if dbg('t') {
|
||||
if !dbg('p') {
|
||||
log.Println("Warning: times are mostly GC/scheduler noise; use -debug=tp to disable parallelism")
|
||||
}
|
||||
var all []*action
|
||||
var total time.Duration
|
||||
for act := range printed {
|
||||
all = append(all, act)
|
||||
total += act.duration
|
||||
}
|
||||
sort.Slice(all, func(i, j int) bool {
|
||||
return all[i].duration > all[j].duration
|
||||
})
|
||||
|
||||
// Print actions accounting for 90% of the total.
|
||||
var sum time.Duration
|
||||
for _, act := range all {
|
||||
fmt.Fprintf(os.Stderr, "%s\t%s\n", act.duration, act)
|
||||
sum += act.duration
|
||||
if sum >= total*9/10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return exitcode
|
||||
}
|
||||
|
||||
// needFacts reports whether any analysis required by the specified set
|
||||
// needs facts. If so, we must load the entire program from source.
|
||||
func needFacts(analyzers []*analysis.Analyzer) bool {
|
||||
seen := make(map[*analysis.Analyzer]bool)
|
||||
var q []*analysis.Analyzer // for BFS
|
||||
q = append(q, analyzers...)
|
||||
for len(q) > 0 {
|
||||
a := q[0]
|
||||
q = q[1:]
|
||||
if !seen[a] {
|
||||
seen[a] = true
|
||||
if len(a.FactTypes) > 0 {
|
||||
return true
|
||||
}
|
||||
q = append(q, a.Requires...)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// An action represents one unit of analysis work: the application of
|
||||
// one analysis to one package. Actions form a DAG, both within a
|
||||
// package (as different analyzers are applied, either in sequence or
|
||||
// parallel), and across packages (as dependencies are analyzed).
|
||||
type action struct {
|
||||
once sync.Once
|
||||
a *analysis.Analyzer
|
||||
pkg *packages.Package
|
||||
pass *analysis.Pass
|
||||
isroot bool
|
||||
deps []*action
|
||||
objectFacts map[objectFactKey]analysis.Fact
|
||||
packageFacts map[packageFactKey]analysis.Fact
|
||||
inputs map[*analysis.Analyzer]interface{}
|
||||
result interface{}
|
||||
diagnostics []analysis.Diagnostic
|
||||
err error
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
type objectFactKey struct {
|
||||
obj types.Object
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
type packageFactKey struct {
|
||||
pkg *types.Package
|
||||
typ reflect.Type
|
||||
}
|
||||
|
||||
func (act *action) String() string {
|
||||
return fmt.Sprintf("%s@%s", act.a, act.pkg)
|
||||
}
|
||||
|
||||
func execAll(actions []*action) {
|
||||
sequential := dbg('p')
|
||||
var wg sync.WaitGroup
|
||||
for _, act := range actions {
|
||||
wg.Add(1)
|
||||
work := func(act *action) {
|
||||
act.exec()
|
||||
wg.Done()
|
||||
}
|
||||
if sequential {
|
||||
work(act)
|
||||
} else {
|
||||
go work(act)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (act *action) exec() { act.once.Do(act.execOnce) }
|
||||
|
||||
func (act *action) execOnce() {
|
||||
// Analyze dependencies.
|
||||
execAll(act.deps)
|
||||
|
||||
// TODO(adonovan): uncomment this during profiling.
|
||||
// It won't build pre-go1.11 but conditional compilation
|
||||
// using build tags isn't warranted.
|
||||
//
|
||||
// ctx, task := trace.NewTask(context.Background(), "exec")
|
||||
// trace.Log(ctx, "pass", act.String())
|
||||
// defer task.End()
|
||||
|
||||
// Record time spent in this node but not its dependencies.
|
||||
// In parallel mode, due to GC/scheduler contention, the
|
||||
// time is 5x higher than in sequential mode, even with a
|
||||
// semaphore limiting the number of threads here.
|
||||
// So use -debug=tp.
|
||||
if dbg('t') {
|
||||
t0 := time.Now()
|
||||
defer func() { act.duration = time.Since(t0) }()
|
||||
}
|
||||
|
||||
// Report an error if any dependency failed.
|
||||
var failed []string
|
||||
for _, dep := range act.deps {
|
||||
if dep.err != nil {
|
||||
failed = append(failed, dep.String())
|
||||
}
|
||||
}
|
||||
if failed != nil {
|
||||
sort.Strings(failed)
|
||||
act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
// Plumb the output values of the dependencies
|
||||
// into the inputs of this action. Also facts.
|
||||
inputs := make(map[*analysis.Analyzer]interface{})
|
||||
act.objectFacts = make(map[objectFactKey]analysis.Fact)
|
||||
act.packageFacts = make(map[packageFactKey]analysis.Fact)
|
||||
for _, dep := range act.deps {
|
||||
if dep.pkg == act.pkg {
|
||||
// Same package, different analysis (horizontal edge):
|
||||
// in-memory outputs of prerequisite analyzers
|
||||
// become inputs to this analysis pass.
|
||||
inputs[dep.a] = dep.result
|
||||
|
||||
} else if dep.a == act.a { // (always true)
|
||||
// Same analysis, different package (vertical edge):
|
||||
// serialized facts produced by prerequisite analysis
|
||||
// become available to this analysis pass.
|
||||
inheritFacts(act, dep)
|
||||
}
|
||||
}
|
||||
|
||||
// Run the analysis.
|
||||
pass := &analysis.Pass{
|
||||
Analyzer: act.a,
|
||||
Fset: act.pkg.Fset,
|
||||
Files: act.pkg.Syntax,
|
||||
OtherFiles: act.pkg.OtherFiles,
|
||||
IgnoredFiles: act.pkg.IgnoredFiles,
|
||||
Pkg: act.pkg.Types,
|
||||
TypesInfo: act.pkg.TypesInfo,
|
||||
TypesSizes: act.pkg.TypesSizes,
|
||||
ResultOf: inputs,
|
||||
Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
|
||||
ImportObjectFact: act.importObjectFact,
|
||||
ExportObjectFact: act.exportObjectFact,
|
||||
ImportPackageFact: act.importPackageFact,
|
||||
ExportPackageFact: act.exportPackageFact,
|
||||
AllObjectFacts: act.allObjectFacts,
|
||||
AllPackageFacts: act.allPackageFacts,
|
||||
}
|
||||
act.pass = pass
|
||||
|
||||
var errors []types.Error
|
||||
// Get any type errors that are attributed to the pkg.
|
||||
// This is necessary to test analyzers that provide
|
||||
// suggested fixes for compiler/type errors.
|
||||
for _, err := range act.pkg.Errors {
|
||||
if err.Kind != packages.TypeError {
|
||||
continue
|
||||
}
|
||||
// err.Pos is a string of form: "file:line:col" or "file:line" or "" or "-"
|
||||
spn := span.Parse(err.Pos)
|
||||
// Extract the token positions from the error string.
|
||||
line, col, offset := spn.Start().Line(), spn.Start().Column(), -1
|
||||
act.pkg.Fset.Iterate(func(f *token.File) bool {
|
||||
if f.Name() != spn.URI().Filename() {
|
||||
return true
|
||||
}
|
||||
offset = int(f.LineStart(line)) + col - 1
|
||||
return false
|
||||
})
|
||||
if offset == -1 {
|
||||
continue
|
||||
}
|
||||
errors = append(errors, types.Error{
|
||||
Fset: act.pkg.Fset,
|
||||
Msg: err.Msg,
|
||||
Pos: token.Pos(offset),
|
||||
})
|
||||
}
|
||||
analysisinternal.SetTypeErrors(pass, errors)
|
||||
|
||||
var err error
|
||||
if act.pkg.IllTyped && !pass.Analyzer.RunDespiteErrors {
|
||||
err = fmt.Errorf("analysis skipped due to errors in package")
|
||||
} else {
|
||||
act.result, err = pass.Analyzer.Run(pass)
|
||||
if err == nil {
|
||||
if got, want := reflect.TypeOf(act.result), pass.Analyzer.ResultType; got != want {
|
||||
err = fmt.Errorf(
|
||||
"internal error: on package %s, analyzer %s returned a result of type %v, but declared ResultType %v",
|
||||
pass.Pkg.Path(), pass.Analyzer, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
act.err = err
|
||||
|
||||
// disallow calls after Run
|
||||
pass.ExportObjectFact = nil
|
||||
pass.ExportPackageFact = nil
|
||||
}
|
||||
|
||||
// inheritFacts populates act.facts with
|
||||
// those it obtains from its dependency, dep.
|
||||
func inheritFacts(act, dep *action) {
|
||||
serialize := dbg('s')
|
||||
|
||||
for key, fact := range dep.objectFacts {
|
||||
// Filter out facts related to objects
|
||||
// that are irrelevant downstream
|
||||
// (equivalently: not in the compiler export data).
|
||||
if !exportedFrom(key.obj, dep.pkg.Types) {
|
||||
if false {
|
||||
log.Printf("%v: discarding %T fact from %s for %s: %s", act, fact, dep, key.obj, fact)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Optionally serialize/deserialize fact
|
||||
// to verify that it works across address spaces.
|
||||
if serialize {
|
||||
encodedFact, err := codeFact(fact)
|
||||
if err != nil {
|
||||
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
|
||||
}
|
||||
fact = encodedFact
|
||||
}
|
||||
|
||||
if false {
|
||||
log.Printf("%v: inherited %T fact for %s: %s", act, fact, key.obj, fact)
|
||||
}
|
||||
act.objectFacts[key] = fact
|
||||
}
|
||||
|
||||
for key, fact := range dep.packageFacts {
|
||||
// TODO: filter out facts that belong to
|
||||
// packages not mentioned in the export data
|
||||
// to prevent side channels.
|
||||
|
||||
// Optionally serialize/deserialize fact
|
||||
// to verify that it works across address spaces
|
||||
// and is deterministic.
|
||||
if serialize {
|
||||
encodedFact, err := codeFact(fact)
|
||||
if err != nil {
|
||||
log.Panicf("internal error: encoding of %T fact failed in %v", fact, act)
|
||||
}
|
||||
fact = encodedFact
|
||||
}
|
||||
|
||||
if false {
|
||||
log.Printf("%v: inherited %T fact for %s: %s", act, fact, key.pkg.Path(), fact)
|
||||
}
|
||||
act.packageFacts[key] = fact
|
||||
}
|
||||
}
|
||||
|
||||
// codeFact encodes then decodes a fact,
|
||||
// just to exercise that logic.
|
||||
func codeFact(fact analysis.Fact) (analysis.Fact, error) {
|
||||
// We encode facts one at a time.
|
||||
// A real modular driver would emit all facts
|
||||
// into one encoder to improve gob efficiency.
|
||||
var buf bytes.Buffer
|
||||
if err := gob.NewEncoder(&buf).Encode(fact); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode it twice and assert that we get the same bits.
|
||||
// This helps detect nondeterministic Gob encoding (e.g. of maps).
|
||||
var buf2 bytes.Buffer
|
||||
if err := gob.NewEncoder(&buf2).Encode(fact); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), buf2.Bytes()) {
|
||||
return nil, fmt.Errorf("encoding of %T fact is nondeterministic", fact)
|
||||
}
|
||||
|
||||
new := reflect.New(reflect.TypeOf(fact).Elem()).Interface().(analysis.Fact)
|
||||
if err := gob.NewDecoder(&buf).Decode(new); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// exportedFrom reports whether obj may be visible to a package that imports pkg.
|
||||
// This includes not just the exported members of pkg, but also unexported
|
||||
// constants, types, fields, and methods, perhaps belonging to oether packages,
|
||||
// that find there way into the API.
|
||||
// This is an overapproximation of the more accurate approach used by
|
||||
// gc export data, which walks the type graph, but it's much simpler.
|
||||
//
|
||||
// TODO(adonovan): do more accurate filtering by walking the type graph.
|
||||
func exportedFrom(obj types.Object, pkg *types.Package) bool {
|
||||
switch obj := obj.(type) {
|
||||
case *types.Func:
|
||||
return obj.Exported() && obj.Pkg() == pkg ||
|
||||
obj.Type().(*types.Signature).Recv() != nil
|
||||
case *types.Var:
|
||||
if obj.IsField() {
|
||||
return true
|
||||
}
|
||||
// we can't filter more aggressively than this because we need
|
||||
// to consider function parameters exported, but have no way
|
||||
// of telling apart function parameters from local variables.
|
||||
return obj.Pkg() == pkg
|
||||
case *types.TypeName, *types.Const:
|
||||
return true
|
||||
}
|
||||
return false // Nil, Builtin, Label, or PkgName
|
||||
}
|
||||
|
||||
// importObjectFact implements Pass.ImportObjectFact.
|
||||
// Given a non-nil pointer ptr of type *T, where *T satisfies Fact,
|
||||
// importObjectFact copies the fact value to *ptr.
|
||||
func (act *action) importObjectFact(obj types.Object, ptr analysis.Fact) bool {
|
||||
if obj == nil {
|
||||
panic("nil object")
|
||||
}
|
||||
key := objectFactKey{obj, factType(ptr)}
|
||||
if v, ok := act.objectFacts[key]; ok {
|
||||
reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// exportObjectFact implements Pass.ExportObjectFact.
|
||||
func (act *action) exportObjectFact(obj types.Object, fact analysis.Fact) {
|
||||
if act.pass.ExportObjectFact == nil {
|
||||
log.Panicf("%s: Pass.ExportObjectFact(%s, %T) called after Run", act, obj, fact)
|
||||
}
|
||||
|
||||
if obj.Pkg() != act.pkg.Types {
|
||||
log.Panicf("internal error: in analysis %s of package %s: Fact.Set(%s, %T): can't set facts on objects belonging another package",
|
||||
act.a, act.pkg, obj, fact)
|
||||
}
|
||||
|
||||
key := objectFactKey{obj, factType(fact)}
|
||||
act.objectFacts[key] = fact // clobber any existing entry
|
||||
if dbg('f') {
|
||||
objstr := types.ObjectString(obj, (*types.Package).Name)
|
||||
fmt.Fprintf(os.Stderr, "%s: object %s has fact %s\n",
|
||||
act.pkg.Fset.Position(obj.Pos()), objstr, fact)
|
||||
}
|
||||
}
|
||||
|
||||
// allObjectFacts implements Pass.AllObjectFacts.
|
||||
func (act *action) allObjectFacts() []analysis.ObjectFact {
|
||||
facts := make([]analysis.ObjectFact, 0, len(act.objectFacts))
|
||||
for k := range act.objectFacts {
|
||||
facts = append(facts, analysis.ObjectFact{k.obj, act.objectFacts[k]})
|
||||
}
|
||||
return facts
|
||||
}
|
||||
|
||||
// importPackageFact implements Pass.ImportPackageFact.
|
||||
// Given a non-nil pointer ptr of type *T, where *T satisfies Fact,
|
||||
// fact copies the fact value to *ptr.
|
||||
func (act *action) importPackageFact(pkg *types.Package, ptr analysis.Fact) bool {
|
||||
if pkg == nil {
|
||||
panic("nil package")
|
||||
}
|
||||
key := packageFactKey{pkg, factType(ptr)}
|
||||
if v, ok := act.packageFacts[key]; ok {
|
||||
reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// exportPackageFact implements Pass.ExportPackageFact.
|
||||
func (act *action) exportPackageFact(fact analysis.Fact) {
|
||||
if act.pass.ExportPackageFact == nil {
|
||||
log.Panicf("%s: Pass.ExportPackageFact(%T) called after Run", act, fact)
|
||||
}
|
||||
|
||||
key := packageFactKey{act.pass.Pkg, factType(fact)}
|
||||
act.packageFacts[key] = fact // clobber any existing entry
|
||||
if dbg('f') {
|
||||
fmt.Fprintf(os.Stderr, "%s: package %s has fact %s\n",
|
||||
act.pkg.Fset.Position(act.pass.Files[0].Pos()), act.pass.Pkg.Path(), fact)
|
||||
}
|
||||
}
|
||||
|
||||
func factType(fact analysis.Fact) reflect.Type {
|
||||
t := reflect.TypeOf(fact)
|
||||
if t.Kind() != reflect.Ptr {
|
||||
log.Fatalf("invalid Fact type: got %T, want pointer", t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// allObjectFacts implements Pass.AllObjectFacts.
|
||||
func (act *action) allPackageFacts() []analysis.PackageFact {
|
||||
facts := make([]analysis.PackageFact, 0, len(act.packageFacts))
|
||||
for k := range act.packageFacts {
|
||||
facts = append(facts, analysis.PackageFact{k.pkg, act.packageFacts[k]})
|
||||
}
|
||||
return facts
|
||||
}
|
||||
|
||||
func dbg(b byte) bool { return strings.IndexByte(Debug, b) >= 0 }
|
||||
323
vendor/golang.org/x/tools/go/analysis/internal/facts/facts.go
generated
vendored
Normal file
323
vendor/golang.org/x/tools/go/analysis/internal/facts/facts.go
generated
vendored
Normal file
@@ -0,0 +1,323 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package facts defines a serializable set of analysis.Fact.
|
||||
//
|
||||
// It provides a partial implementation of the Fact-related parts of the
|
||||
// analysis.Pass interface for use in analysis drivers such as "go vet"
|
||||
// and other build systems.
|
||||
//
|
||||
// The serial format is unspecified and may change, so the same version
|
||||
// of this package must be used for reading and writing serialized facts.
|
||||
//
|
||||
// The handling of facts in the analysis system parallels the handling
|
||||
// of type information in the compiler: during compilation of package P,
|
||||
// the compiler emits an export data file that describes the type of
|
||||
// every object (named thing) defined in package P, plus every object
|
||||
// indirectly reachable from one of those objects. Thus the downstream
|
||||
// compiler of package Q need only load one export data file per direct
|
||||
// import of Q, and it will learn everything about the API of package P
|
||||
// and everything it needs to know about the API of P's dependencies.
|
||||
//
|
||||
// Similarly, analysis of package P emits a fact set containing facts
|
||||
// about all objects exported from P, plus additional facts about only
|
||||
// those objects of P's dependencies that are reachable from the API of
|
||||
// package P; the downstream analysis of Q need only load one fact set
|
||||
// per direct import of Q.
|
||||
//
|
||||
// The notion of "exportedness" that matters here is that of the
|
||||
// compiler. According to the language spec, a method pkg.T.f is
|
||||
// unexported simply because its name starts with lowercase. But the
|
||||
// compiler must nonetheless export f so that downstream compilations can
|
||||
// accurately ascertain whether pkg.T implements an interface pkg.I
|
||||
// defined as interface{f()}. Exported thus means "described in export
|
||||
// data".
|
||||
//
|
||||
package facts
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/types/objectpath"
|
||||
)
|
||||
|
||||
const debug = false
|
||||
|
||||
// A Set is a set of analysis.Facts.
|
||||
//
|
||||
// Decode creates a Set of facts by reading from the imports of a given
|
||||
// package, and Encode writes out the set. Between these operation,
|
||||
// the Import and Export methods will query and update the set.
|
||||
//
|
||||
// All of Set's methods except String are safe to call concurrently.
|
||||
type Set struct {
|
||||
pkg *types.Package
|
||||
mu sync.Mutex
|
||||
m map[key]analysis.Fact
|
||||
}
|
||||
|
||||
type key struct {
|
||||
pkg *types.Package
|
||||
obj types.Object // (object facts only)
|
||||
t reflect.Type
|
||||
}
|
||||
|
||||
// ImportObjectFact implements analysis.Pass.ImportObjectFact.
|
||||
func (s *Set) ImportObjectFact(obj types.Object, ptr analysis.Fact) bool {
|
||||
if obj == nil {
|
||||
panic("nil object")
|
||||
}
|
||||
key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(ptr)}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if v, ok := s.m[key]; ok {
|
||||
reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ExportObjectFact implements analysis.Pass.ExportObjectFact.
|
||||
func (s *Set) ExportObjectFact(obj types.Object, fact analysis.Fact) {
|
||||
if obj.Pkg() != s.pkg {
|
||||
log.Panicf("in package %s: ExportObjectFact(%s, %T): can't set fact on object belonging another package",
|
||||
s.pkg, obj, fact)
|
||||
}
|
||||
key := key{pkg: obj.Pkg(), obj: obj, t: reflect.TypeOf(fact)}
|
||||
s.mu.Lock()
|
||||
s.m[key] = fact // clobber any existing entry
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Set) AllObjectFacts(filter map[reflect.Type]bool) []analysis.ObjectFact {
|
||||
var facts []analysis.ObjectFact
|
||||
s.mu.Lock()
|
||||
for k, v := range s.m {
|
||||
if k.obj != nil && filter[k.t] {
|
||||
facts = append(facts, analysis.ObjectFact{Object: k.obj, Fact: v})
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return facts
|
||||
}
|
||||
|
||||
// ImportPackageFact implements analysis.Pass.ImportPackageFact.
|
||||
func (s *Set) ImportPackageFact(pkg *types.Package, ptr analysis.Fact) bool {
|
||||
if pkg == nil {
|
||||
panic("nil package")
|
||||
}
|
||||
key := key{pkg: pkg, t: reflect.TypeOf(ptr)}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if v, ok := s.m[key]; ok {
|
||||
reflect.ValueOf(ptr).Elem().Set(reflect.ValueOf(v).Elem())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ExportPackageFact implements analysis.Pass.ExportPackageFact.
|
||||
func (s *Set) ExportPackageFact(fact analysis.Fact) {
|
||||
key := key{pkg: s.pkg, t: reflect.TypeOf(fact)}
|
||||
s.mu.Lock()
|
||||
s.m[key] = fact // clobber any existing entry
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *Set) AllPackageFacts(filter map[reflect.Type]bool) []analysis.PackageFact {
|
||||
var facts []analysis.PackageFact
|
||||
s.mu.Lock()
|
||||
for k, v := range s.m {
|
||||
if k.obj == nil && filter[k.t] {
|
||||
facts = append(facts, analysis.PackageFact{Package: k.pkg, Fact: v})
|
||||
}
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return facts
|
||||
}
|
||||
|
||||
// gobFact is the Gob declaration of a serialized fact.
|
||||
type gobFact struct {
|
||||
PkgPath string // path of package
|
||||
Object objectpath.Path // optional path of object relative to package itself
|
||||
Fact analysis.Fact // type and value of user-defined Fact
|
||||
}
|
||||
|
||||
// Decode decodes all the facts relevant to the analysis of package pkg.
|
||||
// The read function reads serialized fact data from an external source
|
||||
// for one of of pkg's direct imports. The empty file is a valid
|
||||
// encoding of an empty fact set.
|
||||
//
|
||||
// It is the caller's responsibility to call gob.Register on all
|
||||
// necessary fact types.
|
||||
func Decode(pkg *types.Package, read func(packagePath string) ([]byte, error)) (*Set, error) {
|
||||
// Compute the import map for this package.
|
||||
// See the package doc comment.
|
||||
packages := importMap(pkg.Imports())
|
||||
|
||||
// Read facts from imported packages.
|
||||
// Facts may describe indirectly imported packages, or their objects.
|
||||
m := make(map[key]analysis.Fact) // one big bucket
|
||||
for _, imp := range pkg.Imports() {
|
||||
logf := func(format string, args ...interface{}) {
|
||||
if debug {
|
||||
prefix := fmt.Sprintf("in %s, importing %s: ",
|
||||
pkg.Path(), imp.Path())
|
||||
log.Print(prefix, fmt.Sprintf(format, args...))
|
||||
}
|
||||
}
|
||||
|
||||
// Read the gob-encoded facts.
|
||||
data, err := read(imp.Path())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("in %s, can't import facts for package %q: %v",
|
||||
pkg.Path(), imp.Path(), err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
continue // no facts
|
||||
}
|
||||
var gobFacts []gobFact
|
||||
if err := gob.NewDecoder(bytes.NewReader(data)).Decode(&gobFacts); err != nil {
|
||||
return nil, fmt.Errorf("decoding facts for %q: %v", imp.Path(), err)
|
||||
}
|
||||
if debug {
|
||||
logf("decoded %d facts: %v", len(gobFacts), gobFacts)
|
||||
}
|
||||
|
||||
// Parse each one into a key and a Fact.
|
||||
for _, f := range gobFacts {
|
||||
factPkg := packages[f.PkgPath]
|
||||
if factPkg == nil {
|
||||
// Fact relates to a dependency that was
|
||||
// unused in this translation unit. Skip.
|
||||
logf("no package %q; discarding %v", f.PkgPath, f.Fact)
|
||||
continue
|
||||
}
|
||||
key := key{pkg: factPkg, t: reflect.TypeOf(f.Fact)}
|
||||
if f.Object != "" {
|
||||
// object fact
|
||||
obj, err := objectpath.Object(factPkg, f.Object)
|
||||
if err != nil {
|
||||
// (most likely due to unexported object)
|
||||
// TODO(adonovan): audit for other possibilities.
|
||||
logf("no object for path: %v; discarding %s", err, f.Fact)
|
||||
continue
|
||||
}
|
||||
key.obj = obj
|
||||
logf("read %T fact %s for %v", f.Fact, f.Fact, key.obj)
|
||||
} else {
|
||||
// package fact
|
||||
logf("read %T fact %s for %v", f.Fact, f.Fact, factPkg)
|
||||
}
|
||||
m[key] = f.Fact
|
||||
}
|
||||
}
|
||||
|
||||
return &Set{pkg: pkg, m: m}, nil
|
||||
}
|
||||
|
||||
// Encode encodes a set of facts to a memory buffer.
|
||||
//
|
||||
// It may fail if one of the Facts could not be gob-encoded, but this is
|
||||
// a sign of a bug in an Analyzer.
|
||||
func (s *Set) Encode() []byte {
|
||||
|
||||
// TODO(adonovan): opt: use a more efficient encoding
|
||||
// that avoids repeating PkgPath for each fact.
|
||||
|
||||
// Gather all facts, including those from imported packages.
|
||||
var gobFacts []gobFact
|
||||
|
||||
s.mu.Lock()
|
||||
for k, fact := range s.m {
|
||||
if debug {
|
||||
log.Printf("%v => %s\n", k, fact)
|
||||
}
|
||||
var object objectpath.Path
|
||||
if k.obj != nil {
|
||||
path, err := objectpath.For(k.obj)
|
||||
if err != nil {
|
||||
if debug {
|
||||
log.Printf("discarding fact %s about %s\n", fact, k.obj)
|
||||
}
|
||||
continue // object not accessible from package API; discard fact
|
||||
}
|
||||
object = path
|
||||
}
|
||||
gobFacts = append(gobFacts, gobFact{
|
||||
PkgPath: k.pkg.Path(),
|
||||
Object: object,
|
||||
Fact: fact,
|
||||
})
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
// Sort facts by (package, object, type) for determinism.
|
||||
sort.Slice(gobFacts, func(i, j int) bool {
|
||||
x, y := gobFacts[i], gobFacts[j]
|
||||
if x.PkgPath != y.PkgPath {
|
||||
return x.PkgPath < y.PkgPath
|
||||
}
|
||||
if x.Object != y.Object {
|
||||
return x.Object < y.Object
|
||||
}
|
||||
tx := reflect.TypeOf(x.Fact)
|
||||
ty := reflect.TypeOf(y.Fact)
|
||||
if tx != ty {
|
||||
return tx.String() < ty.String()
|
||||
}
|
||||
return false // equal
|
||||
})
|
||||
|
||||
var buf bytes.Buffer
|
||||
if len(gobFacts) > 0 {
|
||||
if err := gob.NewEncoder(&buf).Encode(gobFacts); err != nil {
|
||||
// Fact encoding should never fail. Identify the culprit.
|
||||
for _, gf := range gobFacts {
|
||||
if err := gob.NewEncoder(ioutil.Discard).Encode(gf); err != nil {
|
||||
fact := gf.Fact
|
||||
pkgpath := reflect.TypeOf(fact).Elem().PkgPath()
|
||||
log.Panicf("internal error: gob encoding of analysis fact %s failed: %v; please report a bug against fact %T in package %q",
|
||||
fact, err, fact, pkgpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if debug {
|
||||
log.Printf("package %q: encode %d facts, %d bytes\n",
|
||||
s.pkg.Path(), len(gobFacts), buf.Len())
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// String is provided only for debugging, and must not be called
|
||||
// concurrent with any Import/Export method.
|
||||
func (s *Set) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("{")
|
||||
for k, f := range s.m {
|
||||
if buf.Len() > 1 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
if k.obj != nil {
|
||||
buf.WriteString(k.obj.String())
|
||||
} else {
|
||||
buf.WriteString(k.pkg.Path())
|
||||
}
|
||||
fmt.Fprintf(&buf, ": %v", f)
|
||||
}
|
||||
buf.WriteString("}")
|
||||
return buf.String()
|
||||
}
|
||||
88
vendor/golang.org/x/tools/go/analysis/internal/facts/imports.go
generated
vendored
Normal file
88
vendor/golang.org/x/tools/go/analysis/internal/facts/imports.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package facts
|
||||
|
||||
import "go/types"
|
||||
|
||||
// importMap computes the import map for a package by traversing the
|
||||
// entire exported API each of its imports.
|
||||
//
|
||||
// This is a workaround for the fact that we cannot access the map used
|
||||
// internally by the types.Importer returned by go/importer. The entries
|
||||
// in this map are the packages and objects that may be relevant to the
|
||||
// current analysis unit.
|
||||
//
|
||||
// Packages in the map that are only indirectly imported may be
|
||||
// incomplete (!pkg.Complete()).
|
||||
//
|
||||
func importMap(imports []*types.Package) map[string]*types.Package {
|
||||
objects := make(map[types.Object]bool)
|
||||
packages := make(map[string]*types.Package)
|
||||
|
||||
var addObj func(obj types.Object) bool
|
||||
var addType func(T types.Type)
|
||||
|
||||
addObj = func(obj types.Object) bool {
|
||||
if !objects[obj] {
|
||||
objects[obj] = true
|
||||
addType(obj.Type())
|
||||
if pkg := obj.Pkg(); pkg != nil {
|
||||
packages[pkg.Path()] = pkg
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
addType = func(T types.Type) {
|
||||
switch T := T.(type) {
|
||||
case *types.Basic:
|
||||
// nop
|
||||
case *types.Named:
|
||||
if addObj(T.Obj()) {
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
addObj(T.Method(i))
|
||||
}
|
||||
}
|
||||
case *types.Pointer:
|
||||
addType(T.Elem())
|
||||
case *types.Slice:
|
||||
addType(T.Elem())
|
||||
case *types.Array:
|
||||
addType(T.Elem())
|
||||
case *types.Chan:
|
||||
addType(T.Elem())
|
||||
case *types.Map:
|
||||
addType(T.Key())
|
||||
addType(T.Elem())
|
||||
case *types.Signature:
|
||||
addType(T.Params())
|
||||
addType(T.Results())
|
||||
case *types.Struct:
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
addObj(T.Field(i))
|
||||
}
|
||||
case *types.Tuple:
|
||||
for i := 0; i < T.Len(); i++ {
|
||||
addObj(T.At(i))
|
||||
}
|
||||
case *types.Interface:
|
||||
for i := 0; i < T.NumMethods(); i++ {
|
||||
addObj(T.Method(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, imp := range imports {
|
||||
packages[imp.Path()] = imp
|
||||
|
||||
scope := imp.Scope()
|
||||
for _, name := range scope.Names() {
|
||||
addObj(scope.Lookup(name))
|
||||
}
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
77
vendor/golang.org/x/tools/go/analysis/singlechecker/singlechecker.go
generated
vendored
Normal file
77
vendor/golang.org/x/tools/go/analysis/singlechecker/singlechecker.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package singlechecker defines the main function for an analysis
|
||||
// driver with only a single analysis.
|
||||
// This package makes it easy for a provider of an analysis package to
|
||||
// also provide a standalone tool that runs just that analysis.
|
||||
//
|
||||
// For example, if example.org/findbadness is an analysis package,
|
||||
// all that is needed to define a standalone tool is a file,
|
||||
// example.org/findbadness/cmd/findbadness/main.go, containing:
|
||||
//
|
||||
// // The findbadness command runs an analysis.
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "example.org/findbadness"
|
||||
// "golang.org/x/tools/go/analysis/singlechecker"
|
||||
// )
|
||||
//
|
||||
// func main() { singlechecker.Main(findbadness.Analyzer) }
|
||||
//
|
||||
package singlechecker
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||
"golang.org/x/tools/go/analysis/internal/checker"
|
||||
"golang.org/x/tools/go/analysis/unitchecker"
|
||||
)
|
||||
|
||||
// Main is the main function for a checker command for a single analysis.
|
||||
func Main(a *analysis.Analyzer) {
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix(a.Name + ": ")
|
||||
|
||||
analyzers := []*analysis.Analyzer{a}
|
||||
|
||||
if err := analysis.Validate(analyzers); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
checker.RegisterFlags()
|
||||
|
||||
flag.Usage = func() {
|
||||
paras := strings.Split(a.Doc, "\n\n")
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n\n", a.Name, paras[0])
|
||||
fmt.Fprintf(os.Stderr, "Usage: %s [-flag] [package]\n\n", a.Name)
|
||||
if len(paras) > 1 {
|
||||
fmt.Fprintln(os.Stderr, strings.Join(paras[1:], "\n\n"))
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, "\nFlags:")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
analyzers = analysisflags.Parse(analyzers, false)
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(args) == 1 && strings.HasSuffix(args[0], ".cfg") {
|
||||
unitchecker.Run(args[0], analyzers)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
os.Exit(checker.Run(args, analyzers))
|
||||
}
|
||||
398
vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
generated
vendored
Normal file
398
vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker.go
generated
vendored
Normal file
@@ -0,0 +1,398 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The unitchecker package defines the main function for an analysis
|
||||
// driver that analyzes a single compilation unit during a build.
|
||||
// It is invoked by a build system such as "go vet":
|
||||
//
|
||||
// $ go vet -vettool=$(which vet)
|
||||
//
|
||||
// It supports the following command-line protocol:
|
||||
//
|
||||
// -V=full describe executable (to the build tool)
|
||||
// -flags describe flags (to the build tool)
|
||||
// foo.cfg description of compilation unit (from the build tool)
|
||||
//
|
||||
// This package does not depend on go/packages.
|
||||
// If you need a standalone tool, use multichecker,
|
||||
// which supports this mode but can also load packages
|
||||
// from source using go/packages.
|
||||
package unitchecker
|
||||
|
||||
// TODO(adonovan):
|
||||
// - with gccgo, go build does not build standard library,
|
||||
// so we will not get to analyze it. Yet we must in order
|
||||
// to create base facts for, say, the fmt package for the
|
||||
// printf checker.
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/tools/go/analysis"
|
||||
"golang.org/x/tools/go/analysis/internal/analysisflags"
|
||||
"golang.org/x/tools/go/analysis/internal/facts"
|
||||
)
|
||||
|
||||
// A Config describes a compilation unit to be analyzed.
|
||||
// It is provided to the tool in a JSON-encoded file
|
||||
// whose name ends with ".cfg".
|
||||
type Config struct {
|
||||
ID string // e.g. "fmt [fmt.test]"
|
||||
Compiler string
|
||||
Dir string
|
||||
ImportPath string
|
||||
GoFiles []string
|
||||
NonGoFiles []string
|
||||
IgnoredFiles []string
|
||||
ImportMap map[string]string
|
||||
PackageFile map[string]string
|
||||
Standard map[string]bool
|
||||
PackageVetx map[string]string
|
||||
VetxOnly bool
|
||||
VetxOutput string
|
||||
SucceedOnTypecheckFailure bool
|
||||
}
|
||||
|
||||
// Main is the main function of a vet-like analysis tool that must be
|
||||
// invoked by a build system to analyze a single package.
|
||||
//
|
||||
// The protocol required by 'go vet -vettool=...' is that the tool must support:
|
||||
//
|
||||
// -flags describe flags in JSON
|
||||
// -V=full describe executable for build caching
|
||||
// foo.cfg perform separate modular analyze on the single
|
||||
// unit described by a JSON config file foo.cfg.
|
||||
//
|
||||
func Main(analyzers ...*analysis.Analyzer) {
|
||||
progname := filepath.Base(os.Args[0])
|
||||
log.SetFlags(0)
|
||||
log.SetPrefix(progname + ": ")
|
||||
|
||||
if err := analysis.Validate(analyzers); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, `%[1]s is a tool for static analysis of Go programs.
|
||||
|
||||
Usage of %[1]s:
|
||||
%.16[1]s unit.cfg # execute analysis specified by config file
|
||||
%.16[1]s help # general help
|
||||
%.16[1]s help name # help on specific analyzer and its flags
|
||||
`, progname)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
analyzers = analysisflags.Parse(analyzers, true)
|
||||
|
||||
args := flag.Args()
|
||||
if len(args) == 0 {
|
||||
flag.Usage()
|
||||
}
|
||||
if args[0] == "help" {
|
||||
analysisflags.Help(progname, analyzers, args[1:])
|
||||
os.Exit(0)
|
||||
}
|
||||
if len(args) != 1 || !strings.HasSuffix(args[0], ".cfg") {
|
||||
log.Fatalf(`invoking "go tool vet" directly is unsupported; use "go vet"`)
|
||||
}
|
||||
Run(args[0], analyzers)
|
||||
}
|
||||
|
||||
// Run reads the *.cfg file, runs the analysis,
|
||||
// and calls os.Exit with an appropriate error code.
|
||||
// It assumes flags have already been set.
|
||||
func Run(configFile string, analyzers []*analysis.Analyzer) {
|
||||
cfg, err := readConfig(configFile)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
results, err := run(fset, cfg, analyzers)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// In VetxOnly mode, the analysis is run only for facts.
|
||||
if !cfg.VetxOnly {
|
||||
if analysisflags.JSON {
|
||||
// JSON output
|
||||
tree := make(analysisflags.JSONTree)
|
||||
for _, res := range results {
|
||||
tree.Add(fset, cfg.ID, res.a.Name, res.diagnostics, res.err)
|
||||
}
|
||||
tree.Print()
|
||||
} else {
|
||||
// plain text
|
||||
exit := 0
|
||||
for _, res := range results {
|
||||
if res.err != nil {
|
||||
log.Println(res.err)
|
||||
exit = 1
|
||||
}
|
||||
}
|
||||
for _, res := range results {
|
||||
for _, diag := range res.diagnostics {
|
||||
analysisflags.PrintPlain(fset, diag)
|
||||
exit = 1
|
||||
}
|
||||
}
|
||||
os.Exit(exit)
|
||||
}
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func readConfig(filename string) (*Config, error) {
|
||||
data, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := new(Config)
|
||||
if err := json.Unmarshal(data, cfg); err != nil {
|
||||
return nil, fmt.Errorf("cannot decode JSON config file %s: %v", filename, err)
|
||||
}
|
||||
if len(cfg.GoFiles) == 0 {
|
||||
// The go command disallows packages with no files.
|
||||
// The only exception is unsafe, but the go command
|
||||
// doesn't call vet on it.
|
||||
return nil, fmt.Errorf("package has no files: %s", cfg.ImportPath)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
var importerForCompiler = func(_ *token.FileSet, compiler string, lookup importer.Lookup) types.Importer {
|
||||
// broken legacy implementation (https://golang.org/issue/28995)
|
||||
return importer.For(compiler, lookup)
|
||||
}
|
||||
|
||||
func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]result, error) {
|
||||
// Load, parse, typecheck.
|
||||
var files []*ast.File
|
||||
for _, name := range cfg.GoFiles {
|
||||
f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
if cfg.SucceedOnTypecheckFailure {
|
||||
// Silently succeed; let the compiler
|
||||
// report parse errors.
|
||||
err = nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
compilerImporter := importerForCompiler(fset, cfg.Compiler, func(path string) (io.ReadCloser, error) {
|
||||
// path is a resolved package path, not an import path.
|
||||
file, ok := cfg.PackageFile[path]
|
||||
if !ok {
|
||||
if cfg.Compiler == "gccgo" && cfg.Standard[path] {
|
||||
return nil, nil // fall back to default gccgo lookup
|
||||
}
|
||||
return nil, fmt.Errorf("no package file for %q", path)
|
||||
}
|
||||
return os.Open(file)
|
||||
})
|
||||
importer := importerFunc(func(importPath string) (*types.Package, error) {
|
||||
path, ok := cfg.ImportMap[importPath] // resolve vendoring, etc
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("can't resolve import %q", path)
|
||||
}
|
||||
return compilerImporter.Import(path)
|
||||
})
|
||||
tc := &types.Config{
|
||||
Importer: importer,
|
||||
Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
|
||||
}
|
||||
info := &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Implicits: make(map[ast.Node]types.Object),
|
||||
Scopes: make(map[ast.Node]*types.Scope),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
}
|
||||
pkg, err := tc.Check(cfg.ImportPath, fset, files, info)
|
||||
if err != nil {
|
||||
if cfg.SucceedOnTypecheckFailure {
|
||||
// Silently succeed; let the compiler
|
||||
// report type errors.
|
||||
err = nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Register fact types with gob.
|
||||
// In VetxOnly mode, analyzers are only for their facts,
|
||||
// so we can skip any analysis that neither produces facts
|
||||
// nor depends on any analysis that produces facts.
|
||||
// Also build a map to hold working state and result.
|
||||
type action struct {
|
||||
once sync.Once
|
||||
result interface{}
|
||||
err error
|
||||
usesFacts bool // (transitively uses)
|
||||
diagnostics []analysis.Diagnostic
|
||||
}
|
||||
actions := make(map[*analysis.Analyzer]*action)
|
||||
var registerFacts func(a *analysis.Analyzer) bool
|
||||
registerFacts = func(a *analysis.Analyzer) bool {
|
||||
act, ok := actions[a]
|
||||
if !ok {
|
||||
act = new(action)
|
||||
var usesFacts bool
|
||||
for _, f := range a.FactTypes {
|
||||
usesFacts = true
|
||||
gob.Register(f)
|
||||
}
|
||||
for _, req := range a.Requires {
|
||||
if registerFacts(req) {
|
||||
usesFacts = true
|
||||
}
|
||||
}
|
||||
act.usesFacts = usesFacts
|
||||
actions[a] = act
|
||||
}
|
||||
return act.usesFacts
|
||||
}
|
||||
var filtered []*analysis.Analyzer
|
||||
for _, a := range analyzers {
|
||||
if registerFacts(a) || !cfg.VetxOnly {
|
||||
filtered = append(filtered, a)
|
||||
}
|
||||
}
|
||||
analyzers = filtered
|
||||
|
||||
// Read facts from imported packages.
|
||||
read := func(path string) ([]byte, error) {
|
||||
if vetx, ok := cfg.PackageVetx[path]; ok {
|
||||
return ioutil.ReadFile(vetx)
|
||||
}
|
||||
return nil, nil // no .vetx file, no facts
|
||||
}
|
||||
facts, err := facts.Decode(pkg, read)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// In parallel, execute the DAG of analyzers.
|
||||
var exec func(a *analysis.Analyzer) *action
|
||||
var execAll func(analyzers []*analysis.Analyzer)
|
||||
exec = func(a *analysis.Analyzer) *action {
|
||||
act := actions[a]
|
||||
act.once.Do(func() {
|
||||
execAll(a.Requires) // prefetch dependencies in parallel
|
||||
|
||||
// The inputs to this analysis are the
|
||||
// results of its prerequisites.
|
||||
inputs := make(map[*analysis.Analyzer]interface{})
|
||||
var failed []string
|
||||
for _, req := range a.Requires {
|
||||
reqact := exec(req)
|
||||
if reqact.err != nil {
|
||||
failed = append(failed, req.String())
|
||||
continue
|
||||
}
|
||||
inputs[req] = reqact.result
|
||||
}
|
||||
|
||||
// Report an error if any dependency failed.
|
||||
if failed != nil {
|
||||
sort.Strings(failed)
|
||||
act.err = fmt.Errorf("failed prerequisites: %s", strings.Join(failed, ", "))
|
||||
return
|
||||
}
|
||||
|
||||
factFilter := make(map[reflect.Type]bool)
|
||||
for _, f := range a.FactTypes {
|
||||
factFilter[reflect.TypeOf(f)] = true
|
||||
}
|
||||
|
||||
pass := &analysis.Pass{
|
||||
Analyzer: a,
|
||||
Fset: fset,
|
||||
Files: files,
|
||||
OtherFiles: cfg.NonGoFiles,
|
||||
IgnoredFiles: cfg.IgnoredFiles,
|
||||
Pkg: pkg,
|
||||
TypesInfo: info,
|
||||
TypesSizes: tc.Sizes,
|
||||
ResultOf: inputs,
|
||||
Report: func(d analysis.Diagnostic) { act.diagnostics = append(act.diagnostics, d) },
|
||||
ImportObjectFact: facts.ImportObjectFact,
|
||||
ExportObjectFact: facts.ExportObjectFact,
|
||||
AllObjectFacts: func() []analysis.ObjectFact { return facts.AllObjectFacts(factFilter) },
|
||||
ImportPackageFact: facts.ImportPackageFact,
|
||||
ExportPackageFact: facts.ExportPackageFact,
|
||||
AllPackageFacts: func() []analysis.PackageFact { return facts.AllPackageFacts(factFilter) },
|
||||
}
|
||||
|
||||
t0 := time.Now()
|
||||
act.result, act.err = a.Run(pass)
|
||||
if false {
|
||||
log.Printf("analysis %s = %s", pass, time.Since(t0))
|
||||
}
|
||||
})
|
||||
return act
|
||||
}
|
||||
execAll = func(analyzers []*analysis.Analyzer) {
|
||||
var wg sync.WaitGroup
|
||||
for _, a := range analyzers {
|
||||
wg.Add(1)
|
||||
go func(a *analysis.Analyzer) {
|
||||
_ = exec(a)
|
||||
wg.Done()
|
||||
}(a)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
execAll(analyzers)
|
||||
|
||||
// Return diagnostics and errors from root analyzers.
|
||||
results := make([]result, len(analyzers))
|
||||
for i, a := range analyzers {
|
||||
act := actions[a]
|
||||
results[i].a = a
|
||||
results[i].err = act.err
|
||||
results[i].diagnostics = act.diagnostics
|
||||
}
|
||||
|
||||
data := facts.Encode()
|
||||
if err := ioutil.WriteFile(cfg.VetxOutput, data, 0666); err != nil {
|
||||
return nil, fmt.Errorf("failed to write analysis facts: %v", err)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type result struct {
|
||||
a *analysis.Analyzer
|
||||
diagnostics []analysis.Diagnostic
|
||||
err error
|
||||
}
|
||||
|
||||
type importerFunc func(path string) (*types.Package, error)
|
||||
|
||||
func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
|
||||
13
vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker112.go
generated
vendored
Normal file
13
vendor/golang.org/x/tools/go/analysis/unitchecker/unitchecker112.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.12
|
||||
|
||||
package unitchecker
|
||||
|
||||
import "go/importer"
|
||||
|
||||
func init() {
|
||||
importerForCompiler = importer.ForCompiler
|
||||
}
|
||||
100
vendor/golang.org/x/tools/internal/span/parse.go
generated
vendored
Normal file
100
vendor/golang.org/x/tools/internal/span/parse.go
generated
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package span
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Parse returns the location represented by the input.
|
||||
// Only file paths are accepted, not URIs.
|
||||
// The returned span will be normalized, and thus if printed may produce a
|
||||
// different string.
|
||||
func Parse(input string) Span {
|
||||
// :0:0#0-0:0#0
|
||||
valid := input
|
||||
var hold, offset int
|
||||
hadCol := false
|
||||
suf := rstripSuffix(input)
|
||||
if suf.sep == "#" {
|
||||
offset = suf.num
|
||||
suf = rstripSuffix(suf.remains)
|
||||
}
|
||||
if suf.sep == ":" {
|
||||
valid = suf.remains
|
||||
hold = suf.num
|
||||
hadCol = true
|
||||
suf = rstripSuffix(suf.remains)
|
||||
}
|
||||
switch {
|
||||
case suf.sep == ":":
|
||||
return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), Point{})
|
||||
case suf.sep == "-":
|
||||
// we have a span, fall out of the case to continue
|
||||
default:
|
||||
// separator not valid, rewind to either the : or the start
|
||||
return New(URIFromPath(valid), NewPoint(hold, 0, offset), Point{})
|
||||
}
|
||||
// only the span form can get here
|
||||
// at this point we still don't know what the numbers we have mean
|
||||
// if have not yet seen a : then we might have either a line or a column depending
|
||||
// on whether start has a column or not
|
||||
// we build an end point and will fix it later if needed
|
||||
end := NewPoint(suf.num, hold, offset)
|
||||
hold, offset = 0, 0
|
||||
suf = rstripSuffix(suf.remains)
|
||||
if suf.sep == "#" {
|
||||
offset = suf.num
|
||||
suf = rstripSuffix(suf.remains)
|
||||
}
|
||||
if suf.sep != ":" {
|
||||
// turns out we don't have a span after all, rewind
|
||||
return New(URIFromPath(valid), end, Point{})
|
||||
}
|
||||
valid = suf.remains
|
||||
hold = suf.num
|
||||
suf = rstripSuffix(suf.remains)
|
||||
if suf.sep != ":" {
|
||||
// line#offset only
|
||||
return New(URIFromPath(valid), NewPoint(hold, 0, offset), end)
|
||||
}
|
||||
// we have a column, so if end only had one number, it is also the column
|
||||
if !hadCol {
|
||||
end = NewPoint(suf.num, end.v.Line, end.v.Offset)
|
||||
}
|
||||
return New(URIFromPath(suf.remains), NewPoint(suf.num, hold, offset), end)
|
||||
}
|
||||
|
||||
type suffix struct {
|
||||
remains string
|
||||
sep string
|
||||
num int
|
||||
}
|
||||
|
||||
func rstripSuffix(input string) suffix {
|
||||
if len(input) == 0 {
|
||||
return suffix{"", "", -1}
|
||||
}
|
||||
remains := input
|
||||
num := -1
|
||||
// first see if we have a number at the end
|
||||
last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' })
|
||||
if last >= 0 && last < len(remains)-1 {
|
||||
number, err := strconv.ParseInt(remains[last+1:], 10, 64)
|
||||
if err == nil {
|
||||
num = int(number)
|
||||
remains = remains[:last+1]
|
||||
}
|
||||
}
|
||||
// now see if we have a trailing separator
|
||||
r, w := utf8.DecodeLastRuneInString(remains)
|
||||
if r != ':' && r != '#' && r == '#' {
|
||||
return suffix{input, "", -1}
|
||||
}
|
||||
remains = remains[:len(remains)-w]
|
||||
return suffix{remains, string(r), num}
|
||||
}
|
||||
285
vendor/golang.org/x/tools/internal/span/span.go
generated
vendored
Normal file
285
vendor/golang.org/x/tools/internal/span/span.go
generated
vendored
Normal file
@@ -0,0 +1,285 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package span contains support for representing with positions and ranges in
|
||||
// text files.
|
||||
package span
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
)
|
||||
|
||||
// Span represents a source code range in standardized form.
|
||||
type Span struct {
|
||||
v span
|
||||
}
|
||||
|
||||
// Point represents a single point within a file.
|
||||
// In general this should only be used as part of a Span, as on its own it
|
||||
// does not carry enough information.
|
||||
type Point struct {
|
||||
v point
|
||||
}
|
||||
|
||||
type span struct {
|
||||
URI URI `json:"uri"`
|
||||
Start point `json:"start"`
|
||||
End point `json:"end"`
|
||||
}
|
||||
|
||||
type point struct {
|
||||
Line int `json:"line"`
|
||||
Column int `json:"column"`
|
||||
Offset int `json:"offset"`
|
||||
}
|
||||
|
||||
// Invalid is a span that reports false from IsValid
|
||||
var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}}
|
||||
|
||||
var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}}
|
||||
|
||||
// Converter is the interface to an object that can convert between line:column
|
||||
// and offset forms for a single file.
|
||||
type Converter interface {
|
||||
//ToPosition converts from an offset to a line:column pair.
|
||||
ToPosition(offset int) (int, int, error)
|
||||
//ToOffset converts from a line:column pair to an offset.
|
||||
ToOffset(line, col int) (int, error)
|
||||
}
|
||||
|
||||
func New(uri URI, start Point, end Point) Span {
|
||||
s := Span{v: span{URI: uri, Start: start.v, End: end.v}}
|
||||
s.v.clean()
|
||||
return s
|
||||
}
|
||||
|
||||
func NewPoint(line, col, offset int) Point {
|
||||
p := Point{v: point{Line: line, Column: col, Offset: offset}}
|
||||
p.v.clean()
|
||||
return p
|
||||
}
|
||||
|
||||
func Compare(a, b Span) int {
|
||||
if r := CompareURI(a.URI(), b.URI()); r != 0 {
|
||||
return r
|
||||
}
|
||||
if r := comparePoint(a.v.Start, b.v.Start); r != 0 {
|
||||
return r
|
||||
}
|
||||
return comparePoint(a.v.End, b.v.End)
|
||||
}
|
||||
|
||||
func ComparePoint(a, b Point) int {
|
||||
return comparePoint(a.v, b.v)
|
||||
}
|
||||
|
||||
func comparePoint(a, b point) int {
|
||||
if !a.hasPosition() {
|
||||
if a.Offset < b.Offset {
|
||||
return -1
|
||||
}
|
||||
if a.Offset > b.Offset {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if a.Line < b.Line {
|
||||
return -1
|
||||
}
|
||||
if a.Line > b.Line {
|
||||
return 1
|
||||
}
|
||||
if a.Column < b.Column {
|
||||
return -1
|
||||
}
|
||||
if a.Column > b.Column {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s Span) HasPosition() bool { return s.v.Start.hasPosition() }
|
||||
func (s Span) HasOffset() bool { return s.v.Start.hasOffset() }
|
||||
func (s Span) IsValid() bool { return s.v.Start.isValid() }
|
||||
func (s Span) IsPoint() bool { return s.v.Start == s.v.End }
|
||||
func (s Span) URI() URI { return s.v.URI }
|
||||
func (s Span) Start() Point { return Point{s.v.Start} }
|
||||
func (s Span) End() Point { return Point{s.v.End} }
|
||||
func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) }
|
||||
func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) }
|
||||
|
||||
func (p Point) HasPosition() bool { return p.v.hasPosition() }
|
||||
func (p Point) HasOffset() bool { return p.v.hasOffset() }
|
||||
func (p Point) IsValid() bool { return p.v.isValid() }
|
||||
func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
|
||||
func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
|
||||
func (p Point) Line() int {
|
||||
if !p.v.hasPosition() {
|
||||
panic(fmt.Errorf("position not set in %v", p.v))
|
||||
}
|
||||
return p.v.Line
|
||||
}
|
||||
func (p Point) Column() int {
|
||||
if !p.v.hasPosition() {
|
||||
panic(fmt.Errorf("position not set in %v", p.v))
|
||||
}
|
||||
return p.v.Column
|
||||
}
|
||||
func (p Point) Offset() int {
|
||||
if !p.v.hasOffset() {
|
||||
panic(fmt.Errorf("offset not set in %v", p.v))
|
||||
}
|
||||
return p.v.Offset
|
||||
}
|
||||
|
||||
func (p point) hasPosition() bool { return p.Line > 0 }
|
||||
func (p point) hasOffset() bool { return p.Offset >= 0 }
|
||||
func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() }
|
||||
func (p point) isZero() bool {
|
||||
return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0)
|
||||
}
|
||||
|
||||
func (s *span) clean() {
|
||||
//this presumes the points are already clean
|
||||
if !s.End.isValid() || (s.End == point{}) {
|
||||
s.End = s.Start
|
||||
}
|
||||
}
|
||||
|
||||
func (p *point) clean() {
|
||||
if p.Line < 0 {
|
||||
p.Line = 0
|
||||
}
|
||||
if p.Column <= 0 {
|
||||
if p.Line > 0 {
|
||||
p.Column = 1
|
||||
} else {
|
||||
p.Column = 0
|
||||
}
|
||||
}
|
||||
if p.Offset == 0 && (p.Line > 1 || p.Column > 1) {
|
||||
p.Offset = -1
|
||||
}
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter to print the Location in a standard form.
|
||||
// The format produced is one that can be read back in using Parse.
|
||||
func (s Span) Format(f fmt.State, c rune) {
|
||||
fullForm := f.Flag('+')
|
||||
preferOffset := f.Flag('#')
|
||||
// we should always have a uri, simplify if it is file format
|
||||
//TODO: make sure the end of the uri is unambiguous
|
||||
uri := string(s.v.URI)
|
||||
if c == 'f' {
|
||||
uri = path.Base(uri)
|
||||
} else if !fullForm {
|
||||
uri = s.v.URI.Filename()
|
||||
}
|
||||
fmt.Fprint(f, uri)
|
||||
if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) {
|
||||
return
|
||||
}
|
||||
// see which bits of start to write
|
||||
printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition())
|
||||
printLine := s.HasPosition() && (fullForm || !printOffset)
|
||||
printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1))
|
||||
fmt.Fprint(f, ":")
|
||||
if printLine {
|
||||
fmt.Fprintf(f, "%d", s.v.Start.Line)
|
||||
}
|
||||
if printColumn {
|
||||
fmt.Fprintf(f, ":%d", s.v.Start.Column)
|
||||
}
|
||||
if printOffset {
|
||||
fmt.Fprintf(f, "#%d", s.v.Start.Offset)
|
||||
}
|
||||
// start is written, do we need end?
|
||||
if s.IsPoint() {
|
||||
return
|
||||
}
|
||||
// we don't print the line if it did not change
|
||||
printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line)
|
||||
fmt.Fprint(f, "-")
|
||||
if printLine {
|
||||
fmt.Fprintf(f, "%d", s.v.End.Line)
|
||||
}
|
||||
if printColumn {
|
||||
if printLine {
|
||||
fmt.Fprint(f, ":")
|
||||
}
|
||||
fmt.Fprintf(f, "%d", s.v.End.Column)
|
||||
}
|
||||
if printOffset {
|
||||
fmt.Fprintf(f, "#%d", s.v.End.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
func (s Span) WithPosition(c Converter) (Span, error) {
|
||||
if err := s.update(c, true, false); err != nil {
|
||||
return Span{}, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s Span) WithOffset(c Converter) (Span, error) {
|
||||
if err := s.update(c, false, true); err != nil {
|
||||
return Span{}, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s Span) WithAll(c Converter) (Span, error) {
|
||||
if err := s.update(c, true, true); err != nil {
|
||||
return Span{}, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *Span) update(c Converter, withPos, withOffset bool) error {
|
||||
if !s.IsValid() {
|
||||
return fmt.Errorf("cannot add information to an invalid span")
|
||||
}
|
||||
if withPos && !s.HasPosition() {
|
||||
if err := s.v.Start.updatePosition(c); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.v.End.Offset == s.v.Start.Offset {
|
||||
s.v.End = s.v.Start
|
||||
} else if err := s.v.End.updatePosition(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) {
|
||||
if err := s.v.Start.updateOffset(c); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column {
|
||||
s.v.End.Offset = s.v.Start.Offset
|
||||
} else if err := s.v.End.updateOffset(c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *point) updatePosition(c Converter) error {
|
||||
line, col, err := c.ToPosition(p.Offset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Line = line
|
||||
p.Column = col
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *point) updateOffset(c Converter) error {
|
||||
offset, err := c.ToOffset(p.Line, p.Column)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Offset = offset
|
||||
return nil
|
||||
}
|
||||
194
vendor/golang.org/x/tools/internal/span/token.go
generated
vendored
Normal file
194
vendor/golang.org/x/tools/internal/span/token.go
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package span
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// Range represents a source code range in token.Pos form.
|
||||
// It also carries the FileSet that produced the positions, so that it is
|
||||
// self contained.
|
||||
type Range struct {
|
||||
FileSet *token.FileSet
|
||||
Start token.Pos
|
||||
End token.Pos
|
||||
Converter Converter
|
||||
}
|
||||
|
||||
type FileConverter struct {
|
||||
file *token.File
|
||||
}
|
||||
|
||||
// TokenConverter is a Converter backed by a token file set and file.
|
||||
// It uses the file set methods to work out the conversions, which
|
||||
// makes it fast and does not require the file contents.
|
||||
type TokenConverter struct {
|
||||
FileConverter
|
||||
fset *token.FileSet
|
||||
}
|
||||
|
||||
// NewRange creates a new Range from a FileSet and two positions.
|
||||
// To represent a point pass a 0 as the end pos.
|
||||
func NewRange(fset *token.FileSet, start, end token.Pos) Range {
|
||||
return Range{
|
||||
FileSet: fset,
|
||||
Start: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTokenConverter returns an implementation of Converter backed by a
|
||||
// token.File.
|
||||
func NewTokenConverter(fset *token.FileSet, f *token.File) *TokenConverter {
|
||||
return &TokenConverter{fset: fset, FileConverter: FileConverter{file: f}}
|
||||
}
|
||||
|
||||
// NewContentConverter returns an implementation of Converter for the
|
||||
// given file content.
|
||||
func NewContentConverter(filename string, content []byte) *TokenConverter {
|
||||
fset := token.NewFileSet()
|
||||
f := fset.AddFile(filename, -1, len(content))
|
||||
f.SetLinesForContent(content)
|
||||
return NewTokenConverter(fset, f)
|
||||
}
|
||||
|
||||
// IsPoint returns true if the range represents a single point.
|
||||
func (r Range) IsPoint() bool {
|
||||
return r.Start == r.End
|
||||
}
|
||||
|
||||
// Span converts a Range to a Span that represents the Range.
|
||||
// It will fill in all the members of the Span, calculating the line and column
|
||||
// information.
|
||||
func (r Range) Span() (Span, error) {
|
||||
if !r.Start.IsValid() {
|
||||
return Span{}, fmt.Errorf("start pos is not valid")
|
||||
}
|
||||
f := r.FileSet.File(r.Start)
|
||||
if f == nil {
|
||||
return Span{}, fmt.Errorf("file not found in FileSet")
|
||||
}
|
||||
return FileSpan(f, r.Converter, r.Start, r.End)
|
||||
}
|
||||
|
||||
// FileSpan returns a span within tok, using converter to translate between
|
||||
// offsets and positions.
|
||||
func FileSpan(tok *token.File, converter Converter, start, end token.Pos) (Span, error) {
|
||||
var s Span
|
||||
var err error
|
||||
var startFilename string
|
||||
startFilename, s.v.Start.Line, s.v.Start.Column, err = position(tok, start)
|
||||
if err != nil {
|
||||
return Span{}, err
|
||||
}
|
||||
s.v.URI = URIFromPath(startFilename)
|
||||
if end.IsValid() {
|
||||
var endFilename string
|
||||
endFilename, s.v.End.Line, s.v.End.Column, err = position(tok, end)
|
||||
if err != nil {
|
||||
return Span{}, err
|
||||
}
|
||||
// In the presence of line directives, a single File can have sections from
|
||||
// multiple file names.
|
||||
if endFilename != startFilename {
|
||||
return Span{}, fmt.Errorf("span begins in file %q but ends in %q", startFilename, endFilename)
|
||||
}
|
||||
}
|
||||
s.v.Start.clean()
|
||||
s.v.End.clean()
|
||||
s.v.clean()
|
||||
if converter != nil {
|
||||
return s.WithOffset(converter)
|
||||
}
|
||||
if startFilename != tok.Name() {
|
||||
return Span{}, fmt.Errorf("must supply Converter for file %q containing lines from %q", tok.Name(), startFilename)
|
||||
}
|
||||
return s.WithOffset(&FileConverter{tok})
|
||||
}
|
||||
|
||||
func position(f *token.File, pos token.Pos) (string, int, int, error) {
|
||||
off, err := offset(f, pos)
|
||||
if err != nil {
|
||||
return "", 0, 0, err
|
||||
}
|
||||
return positionFromOffset(f, off)
|
||||
}
|
||||
|
||||
func positionFromOffset(f *token.File, offset int) (string, int, int, error) {
|
||||
if offset > f.Size() {
|
||||
return "", 0, 0, fmt.Errorf("offset %v is past the end of the file %v", offset, f.Size())
|
||||
}
|
||||
pos := f.Pos(offset)
|
||||
p := f.Position(pos)
|
||||
// TODO(golang/go#41029): Consider returning line, column instead of line+1, 1 if
|
||||
// the file's last character is not a newline.
|
||||
if offset == f.Size() {
|
||||
return p.Filename, p.Line + 1, 1, nil
|
||||
}
|
||||
return p.Filename, p.Line, p.Column, nil
|
||||
}
|
||||
|
||||
// offset is a copy of the Offset function in go/token, but with the adjustment
|
||||
// that it does not panic on invalid positions.
|
||||
func offset(f *token.File, pos token.Pos) (int, error) {
|
||||
if int(pos) < f.Base() || int(pos) > f.Base()+f.Size() {
|
||||
return 0, fmt.Errorf("invalid pos")
|
||||
}
|
||||
return int(pos) - f.Base(), nil
|
||||
}
|
||||
|
||||
// Range converts a Span to a Range that represents the Span for the supplied
|
||||
// File.
|
||||
func (s Span) Range(converter *TokenConverter) (Range, error) {
|
||||
s, err := s.WithOffset(converter)
|
||||
if err != nil {
|
||||
return Range{}, err
|
||||
}
|
||||
// go/token will panic if the offset is larger than the file's size,
|
||||
// so check here to avoid panicking.
|
||||
if s.Start().Offset() > converter.file.Size() {
|
||||
return Range{}, fmt.Errorf("start offset %v is past the end of the file %v", s.Start(), converter.file.Size())
|
||||
}
|
||||
if s.End().Offset() > converter.file.Size() {
|
||||
return Range{}, fmt.Errorf("end offset %v is past the end of the file %v", s.End(), converter.file.Size())
|
||||
}
|
||||
return Range{
|
||||
FileSet: converter.fset,
|
||||
Start: converter.file.Pos(s.Start().Offset()),
|
||||
End: converter.file.Pos(s.End().Offset()),
|
||||
Converter: converter,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *FileConverter) ToPosition(offset int) (int, int, error) {
|
||||
_, line, col, err := positionFromOffset(l.file, offset)
|
||||
return line, col, err
|
||||
}
|
||||
|
||||
func (l *FileConverter) ToOffset(line, col int) (int, error) {
|
||||
if line < 0 {
|
||||
return -1, fmt.Errorf("line is not valid")
|
||||
}
|
||||
lineMax := l.file.LineCount() + 1
|
||||
if line > lineMax {
|
||||
return -1, fmt.Errorf("line is beyond end of file %v", lineMax)
|
||||
} else if line == lineMax {
|
||||
if col > 1 {
|
||||
return -1, fmt.Errorf("column is beyond end of file")
|
||||
}
|
||||
// at the end of the file, allowing for a trailing eol
|
||||
return l.file.Size(), nil
|
||||
}
|
||||
pos := lineStart(l.file, line)
|
||||
if !pos.IsValid() {
|
||||
return -1, fmt.Errorf("line is not in file")
|
||||
}
|
||||
// we assume that column is in bytes here, and that the first byte of a
|
||||
// line is at column 1
|
||||
pos += token.Pos(col - 1)
|
||||
return offset(l.file, pos)
|
||||
}
|
||||
39
vendor/golang.org/x/tools/internal/span/token111.go
generated
vendored
Normal file
39
vendor/golang.org/x/tools/internal/span/token111.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.12
|
||||
|
||||
package span
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// lineStart is the pre-Go 1.12 version of (*token.File).LineStart. For Go
|
||||
// versions <= 1.11, we borrow logic from the analysisutil package.
|
||||
// TODO(rstambler): Delete this file when we no longer support Go 1.11.
|
||||
func lineStart(f *token.File, line int) token.Pos {
|
||||
// Use binary search to find the start offset of this line.
|
||||
|
||||
min := 0 // inclusive
|
||||
max := f.Size() // exclusive
|
||||
for {
|
||||
offset := (min + max) / 2
|
||||
pos := f.Pos(offset)
|
||||
posn := f.Position(pos)
|
||||
if posn.Line == line {
|
||||
return pos - (token.Pos(posn.Column) - 1)
|
||||
}
|
||||
|
||||
if min+1 >= max {
|
||||
return token.NoPos
|
||||
}
|
||||
|
||||
if posn.Line < line {
|
||||
min = offset
|
||||
} else {
|
||||
max = offset
|
||||
}
|
||||
}
|
||||
}
|
||||
16
vendor/golang.org/x/tools/internal/span/token112.go
generated
vendored
Normal file
16
vendor/golang.org/x/tools/internal/span/token112.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.12
|
||||
|
||||
package span
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// TODO(rstambler): Delete this file when we no longer support Go 1.11.
|
||||
func lineStart(f *token.File, line int) token.Pos {
|
||||
return f.LineStart(line)
|
||||
}
|
||||
169
vendor/golang.org/x/tools/internal/span/uri.go
generated
vendored
Normal file
169
vendor/golang.org/x/tools/internal/span/uri.go
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package span
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
const fileScheme = "file"
|
||||
|
||||
// URI represents the full URI for a file.
|
||||
type URI string
|
||||
|
||||
func (uri URI) IsFile() bool {
|
||||
return strings.HasPrefix(string(uri), "file://")
|
||||
}
|
||||
|
||||
// Filename returns the file path for the given URI.
|
||||
// It is an error to call this on a URI that is not a valid filename.
|
||||
func (uri URI) Filename() string {
|
||||
filename, err := filename(uri)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return filepath.FromSlash(filename)
|
||||
}
|
||||
|
||||
func filename(uri URI) (string, error) {
|
||||
if uri == "" {
|
||||
return "", nil
|
||||
}
|
||||
u, err := url.ParseRequestURI(string(uri))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if u.Scheme != fileScheme {
|
||||
return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri)
|
||||
}
|
||||
// If the URI is a Windows URI, we trim the leading "/" and lowercase
|
||||
// the drive letter, which will never be case sensitive.
|
||||
if isWindowsDriveURIPath(u.Path) {
|
||||
u.Path = strings.ToUpper(string(u.Path[1])) + u.Path[2:]
|
||||
}
|
||||
return u.Path, nil
|
||||
}
|
||||
|
||||
func URIFromURI(s string) URI {
|
||||
if !strings.HasPrefix(s, "file://") {
|
||||
return URI(s)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(s, "file:///") {
|
||||
// VS Code sends URLs with only two slashes, which are invalid. golang/go#39789.
|
||||
s = "file:///" + s[len("file://"):]
|
||||
}
|
||||
// Even though the input is a URI, it may not be in canonical form. VS Code
|
||||
// in particular over-escapes :, @, etc. Unescape and re-encode to canonicalize.
|
||||
path, err := url.PathUnescape(s[len("file://"):])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// File URIs from Windows may have lowercase drive letters.
|
||||
// Since drive letters are guaranteed to be case insensitive,
|
||||
// we change them to uppercase to remain consistent.
|
||||
// For example, file:///c:/x/y/z becomes file:///C:/x/y/z.
|
||||
if isWindowsDriveURIPath(path) {
|
||||
path = path[:1] + strings.ToUpper(string(path[1])) + path[2:]
|
||||
}
|
||||
u := url.URL{Scheme: fileScheme, Path: path}
|
||||
return URI(u.String())
|
||||
}
|
||||
|
||||
func CompareURI(a, b URI) int {
|
||||
if equalURI(a, b) {
|
||||
return 0
|
||||
}
|
||||
if a < b {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func equalURI(a, b URI) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
// If we have the same URI basename, we may still have the same file URIs.
|
||||
if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) {
|
||||
return false
|
||||
}
|
||||
fa, err := filename(a)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
fb, err := filename(b)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Stat the files to check if they are equal.
|
||||
infoa, err := os.Stat(filepath.FromSlash(fa))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
infob, err := os.Stat(filepath.FromSlash(fb))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return os.SameFile(infoa, infob)
|
||||
}
|
||||
|
||||
// URIFromPath returns a span URI for the supplied file path.
|
||||
// It will always have the file scheme.
|
||||
func URIFromPath(path string) URI {
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
// Handle standard library paths that contain the literal "$GOROOT".
|
||||
// TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT.
|
||||
const prefix = "$GOROOT"
|
||||
if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) {
|
||||
suffix := path[len(prefix):]
|
||||
path = runtime.GOROOT() + suffix
|
||||
}
|
||||
if !isWindowsDrivePath(path) {
|
||||
if abs, err := filepath.Abs(path); err == nil {
|
||||
path = abs
|
||||
}
|
||||
}
|
||||
// Check the file path again, in case it became absolute.
|
||||
if isWindowsDrivePath(path) {
|
||||
path = "/" + strings.ToUpper(string(path[0])) + path[1:]
|
||||
}
|
||||
path = filepath.ToSlash(path)
|
||||
u := url.URL{
|
||||
Scheme: fileScheme,
|
||||
Path: path,
|
||||
}
|
||||
return URI(u.String())
|
||||
}
|
||||
|
||||
// isWindowsDrivePath returns true if the file path is of the form used by
|
||||
// Windows. We check if the path begins with a drive letter, followed by a ":".
|
||||
// For example: C:/x/y/z.
|
||||
func isWindowsDrivePath(path string) bool {
|
||||
if len(path) < 3 {
|
||||
return false
|
||||
}
|
||||
return unicode.IsLetter(rune(path[0])) && path[1] == ':'
|
||||
}
|
||||
|
||||
// isWindowsDriveURI returns true if the file URI is of the format used by
|
||||
// Windows URIs. The url.Parse package does not specially handle Windows paths
|
||||
// (see golang/go#6027), so we check if the URI path has a drive prefix (e.g. "/C:").
|
||||
func isWindowsDriveURIPath(uri string) bool {
|
||||
if len(uri) < 4 {
|
||||
return false
|
||||
}
|
||||
return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':'
|
||||
}
|
||||
91
vendor/golang.org/x/tools/internal/span/utf16.go
generated
vendored
Normal file
91
vendor/golang.org/x/tools/internal/span/utf16.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package span
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode/utf16"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ToUTF16Column calculates the utf16 column expressed by the point given the
|
||||
// supplied file contents.
|
||||
// This is used to convert from the native (always in bytes) column
|
||||
// representation and the utf16 counts used by some editors.
|
||||
func ToUTF16Column(p Point, content []byte) (int, error) {
|
||||
if !p.HasPosition() {
|
||||
return -1, fmt.Errorf("ToUTF16Column: point is missing position")
|
||||
}
|
||||
if !p.HasOffset() {
|
||||
return -1, fmt.Errorf("ToUTF16Column: point is missing offset")
|
||||
}
|
||||
offset := p.Offset() // 0-based
|
||||
colZero := p.Column() - 1 // 0-based
|
||||
if colZero == 0 {
|
||||
// 0-based column 0, so it must be chr 1
|
||||
return 1, nil
|
||||
} else if colZero < 0 {
|
||||
return -1, fmt.Errorf("ToUTF16Column: column is invalid (%v)", colZero)
|
||||
}
|
||||
// work out the offset at the start of the line using the column
|
||||
lineOffset := offset - colZero
|
||||
if lineOffset < 0 || offset > len(content) {
|
||||
return -1, fmt.Errorf("ToUTF16Column: offsets %v-%v outside file contents (%v)", lineOffset, offset, len(content))
|
||||
}
|
||||
// Use the offset to pick out the line start.
|
||||
// This cannot panic: offset > len(content) and lineOffset < offset.
|
||||
start := content[lineOffset:]
|
||||
|
||||
// Now, truncate down to the supplied column.
|
||||
start = start[:colZero]
|
||||
|
||||
// and count the number of utf16 characters
|
||||
// in theory we could do this by hand more efficiently...
|
||||
return len(utf16.Encode([]rune(string(start)))) + 1, nil
|
||||
}
|
||||
|
||||
// FromUTF16Column advances the point by the utf16 character offset given the
|
||||
// supplied line contents.
|
||||
// This is used to convert from the utf16 counts used by some editors to the
|
||||
// native (always in bytes) column representation.
|
||||
func FromUTF16Column(p Point, chr int, content []byte) (Point, error) {
|
||||
if !p.HasOffset() {
|
||||
return Point{}, fmt.Errorf("FromUTF16Column: point is missing offset")
|
||||
}
|
||||
// if chr is 1 then no adjustment needed
|
||||
if chr <= 1 {
|
||||
return p, nil
|
||||
}
|
||||
if p.Offset() >= len(content) {
|
||||
return p, fmt.Errorf("FromUTF16Column: offset (%v) greater than length of content (%v)", p.Offset(), len(content))
|
||||
}
|
||||
remains := content[p.Offset():]
|
||||
// scan forward the specified number of characters
|
||||
for count := 1; count < chr; count++ {
|
||||
if len(remains) <= 0 {
|
||||
return Point{}, fmt.Errorf("FromUTF16Column: chr goes beyond the content")
|
||||
}
|
||||
r, w := utf8.DecodeRune(remains)
|
||||
if r == '\n' {
|
||||
// Per the LSP spec:
|
||||
//
|
||||
// > If the character value is greater than the line length it
|
||||
// > defaults back to the line length.
|
||||
break
|
||||
}
|
||||
remains = remains[w:]
|
||||
if r >= 0x10000 {
|
||||
// a two point rune
|
||||
count++
|
||||
// if we finished in a two point rune, do not advance past the first
|
||||
if count >= chr {
|
||||
break
|
||||
}
|
||||
}
|
||||
p.v.Column += w
|
||||
p.v.Offset += w
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
4
vendor/gopkg.in/alecthomas/kingpin.v2/.travis.yml
generated
vendored
Normal file
4
vendor/gopkg.in/alecthomas/kingpin.v2/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
sudo: false
|
||||
language: go
|
||||
install: go get -t -v ./...
|
||||
go: 1.2
|
||||
19
vendor/gopkg.in/alecthomas/kingpin.v2/COPYING
generated
vendored
Normal file
19
vendor/gopkg.in/alecthomas/kingpin.v2/COPYING
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (C) 2014 Alec Thomas
|
||||
|
||||
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.
|
||||
674
vendor/gopkg.in/alecthomas/kingpin.v2/README.md
generated
vendored
Normal file
674
vendor/gopkg.in/alecthomas/kingpin.v2/README.md
generated
vendored
Normal file
@@ -0,0 +1,674 @@
|
||||
# Kingpin - A Go (golang) command line and flag parser
|
||||
[](http://godoc.org/github.com/alecthomas/kingpin) [](https://travis-ci.org/alecthomas/kingpin) [](https://gitter.im/alecthomas/Lobby)
|
||||
|
||||
|
||||
|
||||
<!-- MarkdownTOC -->
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [User-visible changes between v1 and v2](#user-visible-changes-between-v1-and-v2)
|
||||
- [Flags can be used at any point after their definition.](#flags-can-be-used-at-any-point-after-their-definition)
|
||||
- [Short flags can be combined with their parameters](#short-flags-can-be-combined-with-their-parameters)
|
||||
- [API changes between v1 and v2](#api-changes-between-v1-and-v2)
|
||||
- [Versions](#versions)
|
||||
- [V2 is the current stable version](#v2-is-the-current-stable-version)
|
||||
- [V1 is the OLD stable version](#v1-is-the-old-stable-version)
|
||||
- [Change History](#change-history)
|
||||
- [Examples](#examples)
|
||||
- [Simple Example](#simple-example)
|
||||
- [Complex Example](#complex-example)
|
||||
- [Reference Documentation](#reference-documentation)
|
||||
- [Displaying errors and usage information](#displaying-errors-and-usage-information)
|
||||
- [Sub-commands](#sub-commands)
|
||||
- [Custom Parsers](#custom-parsers)
|
||||
- [Repeatable flags](#repeatable-flags)
|
||||
- [Boolean Values](#boolean-values)
|
||||
- [Default Values](#default-values)
|
||||
- [Place-holders in Help](#place-holders-in-help)
|
||||
- [Consuming all remaining arguments](#consuming-all-remaining-arguments)
|
||||
- [Bash/ZSH Shell Completion](#bashzsh-shell-completion)
|
||||
- [Supporting -h for help](#supporting--h-for-help)
|
||||
- [Custom help](#custom-help)
|
||||
|
||||
<!-- /MarkdownTOC -->
|
||||
|
||||
## Overview
|
||||
|
||||
Kingpin is a [fluent-style](http://en.wikipedia.org/wiki/Fluent_interface),
|
||||
type-safe command-line parser. It supports flags, nested commands, and
|
||||
positional arguments.
|
||||
|
||||
Install it with:
|
||||
|
||||
$ go get gopkg.in/alecthomas/kingpin.v2
|
||||
|
||||
It looks like this:
|
||||
|
||||
```go
|
||||
var (
|
||||
verbose = kingpin.Flag("verbose", "Verbose mode.").Short('v').Bool()
|
||||
name = kingpin.Arg("name", "Name of user.").Required().String()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.Parse()
|
||||
fmt.Printf("%v, %s\n", *verbose, *name)
|
||||
}
|
||||
```
|
||||
|
||||
More [examples](https://github.com/alecthomas/kingpin/tree/master/_examples) are available.
|
||||
|
||||
Second to parsing, providing the user with useful help is probably the most
|
||||
important thing a command-line parser does. Kingpin tries to provide detailed
|
||||
contextual help if `--help` is encountered at any point in the command line
|
||||
(excluding after `--`).
|
||||
|
||||
## Features
|
||||
|
||||
- Help output that isn't as ugly as sin.
|
||||
- Fully [customisable help](#custom-help), via Go templates.
|
||||
- Parsed, type-safe flags (`kingpin.Flag("f", "help").Int()`)
|
||||
- Parsed, type-safe positional arguments (`kingpin.Arg("a", "help").Int()`).
|
||||
- Parsed, type-safe, arbitrarily deep commands (`kingpin.Command("c", "help")`).
|
||||
- Support for required flags and required positional arguments (`kingpin.Flag("f", "").Required().Int()`).
|
||||
- Support for arbitrarily nested default commands (`command.Default()`).
|
||||
- Callbacks per command, flag and argument (`kingpin.Command("c", "").Action(myAction)`).
|
||||
- POSIX-style short flag combining (`-a -b` -> `-ab`).
|
||||
- Short-flag+parameter combining (`-a parm` -> `-aparm`).
|
||||
- Read command-line from files (`@<file>`).
|
||||
- Automatically generate man pages (`--help-man`).
|
||||
|
||||
## User-visible changes between v1 and v2
|
||||
|
||||
### Flags can be used at any point after their definition.
|
||||
|
||||
Flags can be specified at any point after their definition, not just
|
||||
*immediately after their associated command*. From the chat example below, the
|
||||
following used to be required:
|
||||
|
||||
```
|
||||
$ chat --server=chat.server.com:8080 post --image=~/Downloads/owls.jpg pics
|
||||
```
|
||||
|
||||
But the following will now work:
|
||||
|
||||
```
|
||||
$ chat post --server=chat.server.com:8080 --image=~/Downloads/owls.jpg pics
|
||||
```
|
||||
|
||||
### Short flags can be combined with their parameters
|
||||
|
||||
Previously, if a short flag was used, any argument to that flag would have to
|
||||
be separated by a space. That is no longer the case.
|
||||
|
||||
## API changes between v1 and v2
|
||||
|
||||
- `ParseWithFileExpansion()` is gone. The new parser directly supports expanding `@<file>`.
|
||||
- Added `FatalUsage()` and `FatalUsageContext()` for displaying an error + usage and terminating.
|
||||
- `Dispatch()` renamed to `Action()`.
|
||||
- Added `ParseContext()` for parsing a command line into its intermediate context form without executing.
|
||||
- Added `Terminate()` function to override the termination function.
|
||||
- Added `UsageForContextWithTemplate()` for printing usage via a custom template.
|
||||
- Added `UsageTemplate()` for overriding the default template to use. Two templates are included:
|
||||
1. `DefaultUsageTemplate` - default template.
|
||||
2. `CompactUsageTemplate` - compact command template for larger applications.
|
||||
|
||||
## Versions
|
||||
|
||||
Kingpin uses [gopkg.in](https://gopkg.in/alecthomas/kingpin) for versioning.
|
||||
|
||||
The current stable version is [gopkg.in/alecthomas/kingpin.v2](https://gopkg.in/alecthomas/kingpin.v2). The previous version, [gopkg.in/alecthomas/kingpin.v1](https://gopkg.in/alecthomas/kingpin.v1), is deprecated and in maintenance mode.
|
||||
|
||||
### [V2](https://gopkg.in/alecthomas/kingpin.v2) is the current stable version
|
||||
|
||||
Installation:
|
||||
|
||||
```sh
|
||||
$ go get gopkg.in/alecthomas/kingpin.v2
|
||||
```
|
||||
|
||||
### [V1](https://gopkg.in/alecthomas/kingpin.v1) is the OLD stable version
|
||||
|
||||
Installation:
|
||||
|
||||
```sh
|
||||
$ go get gopkg.in/alecthomas/kingpin.v1
|
||||
```
|
||||
|
||||
## Change History
|
||||
|
||||
- *2015-09-19* -- Stable v2.1.0 release.
|
||||
- Added `command.Default()` to specify a default command to use if no other
|
||||
command matches. This allows for convenient user shortcuts.
|
||||
- Exposed `HelpFlag` and `VersionFlag` for further customisation.
|
||||
- `Action()` and `PreAction()` added and both now support an arbitrary
|
||||
number of callbacks.
|
||||
- `kingpin.SeparateOptionalFlagsUsageTemplate`.
|
||||
- `--help-long` and `--help-man` (hidden by default) flags.
|
||||
- Flags are "interspersed" by default, but can be disabled with `app.Interspersed(false)`.
|
||||
- Added flags for all simple builtin types (int8, uint16, etc.) and slice variants.
|
||||
- Use `app.Writer(os.Writer)` to specify the default writer for all output functions.
|
||||
- Dropped `os.Writer` prefix from all printf-like functions.
|
||||
|
||||
- *2015-05-22* -- Stable v2.0.0 release.
|
||||
- Initial stable release of v2.0.0.
|
||||
- Fully supports interspersed flags, commands and arguments.
|
||||
- Flags can be present at any point after their logical definition.
|
||||
- Application.Parse() terminates if commands are present and a command is not parsed.
|
||||
- Dispatch() -> Action().
|
||||
- Actions are dispatched after all values are populated.
|
||||
- Override termination function (defaults to os.Exit).
|
||||
- Override output stream (defaults to os.Stderr).
|
||||
- Templatised usage help, with default and compact templates.
|
||||
- Make error/usage functions more consistent.
|
||||
- Support argument expansion from files by default (with @<file>).
|
||||
- Fully public data model is available via .Model().
|
||||
- Parser has been completely refactored.
|
||||
- Parsing and execution has been split into distinct stages.
|
||||
- Use `go generate` to generate repeated flags.
|
||||
- Support combined short-flag+argument: -fARG.
|
||||
|
||||
- *2015-01-23* -- Stable v1.3.4 release.
|
||||
- Support "--" for separating flags from positional arguments.
|
||||
- Support loading flags from files (ParseWithFileExpansion()). Use @FILE as an argument.
|
||||
- Add post-app and post-cmd validation hooks. This allows arbitrary validation to be added.
|
||||
- A bunch of improvements to help usage and formatting.
|
||||
- Support arbitrarily nested sub-commands.
|
||||
|
||||
- *2014-07-08* -- Stable v1.2.0 release.
|
||||
- Pass any value through to `Strings()` when final argument.
|
||||
Allows for values that look like flags to be processed.
|
||||
- Allow `--help` to be used with commands.
|
||||
- Support `Hidden()` flags.
|
||||
- Parser for [units.Base2Bytes](https://github.com/alecthomas/units)
|
||||
type. Allows for flags like `--ram=512MB` or `--ram=1GB`.
|
||||
- Add an `Enum()` value, allowing only one of a set of values
|
||||
to be selected. eg. `Flag(...).Enum("debug", "info", "warning")`.
|
||||
|
||||
- *2014-06-27* -- Stable v1.1.0 release.
|
||||
- Bug fixes.
|
||||
- Always return an error (rather than panicing) when misconfigured.
|
||||
- `OpenFile(flag, perm)` value type added, for finer control over opening files.
|
||||
- Significantly improved usage formatting.
|
||||
|
||||
- *2014-06-19* -- Stable v1.0.0 release.
|
||||
- Support [cumulative positional](#consuming-all-remaining-arguments) arguments.
|
||||
- Return error rather than panic when there are fatal errors not caught by
|
||||
the type system. eg. when a default value is invalid.
|
||||
- Use gokpg.in.
|
||||
|
||||
- *2014-06-10* -- Place-holder streamlining.
|
||||
- Renamed `MetaVar` to `PlaceHolder`.
|
||||
- Removed `MetaVarFromDefault`. Kingpin now uses [heuristics](#place-holders-in-help)
|
||||
to determine what to display.
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple Example
|
||||
|
||||
Kingpin can be used for simple flag+arg applications like so:
|
||||
|
||||
```
|
||||
$ ping --help
|
||||
usage: ping [<flags>] <ip> [<count>]
|
||||
|
||||
Flags:
|
||||
--debug Enable debug mode.
|
||||
--help Show help.
|
||||
-t, --timeout=5s Timeout waiting for ping.
|
||||
|
||||
Args:
|
||||
<ip> IP address to ping.
|
||||
[<count>] Number of packets to send
|
||||
$ ping 1.2.3.4 5
|
||||
Would ping: 1.2.3.4 with timeout 5s and count 5
|
||||
```
|
||||
|
||||
From the following source:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
debug = kingpin.Flag("debug", "Enable debug mode.").Bool()
|
||||
timeout = kingpin.Flag("timeout", "Timeout waiting for ping.").Default("5s").OverrideDefaultFromEnvar("PING_TIMEOUT").Short('t').Duration()
|
||||
ip = kingpin.Arg("ip", "IP address to ping.").Required().IP()
|
||||
count = kingpin.Arg("count", "Number of packets to send").Int()
|
||||
)
|
||||
|
||||
func main() {
|
||||
kingpin.Version("0.0.1")
|
||||
kingpin.Parse()
|
||||
fmt.Printf("Would ping: %s with timeout %s and count %d\n", *ip, *timeout, *count)
|
||||
}
|
||||
```
|
||||
|
||||
### Complex Example
|
||||
|
||||
Kingpin can also produce complex command-line applications with global flags,
|
||||
subcommands, and per-subcommand flags, like this:
|
||||
|
||||
```
|
||||
$ chat --help
|
||||
usage: chat [<flags>] <command> [<flags>] [<args> ...]
|
||||
|
||||
A command-line chat application.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
--debug Enable debug mode.
|
||||
--server=127.0.0.1 Server address.
|
||||
|
||||
Commands:
|
||||
help [<command>]
|
||||
Show help for a command.
|
||||
|
||||
register <nick> <name>
|
||||
Register a new user.
|
||||
|
||||
post [<flags>] <channel> [<text>]
|
||||
Post a message to a channel.
|
||||
|
||||
$ chat help post
|
||||
usage: chat [<flags>] post [<flags>] <channel> [<text>]
|
||||
|
||||
Post a message to a channel.
|
||||
|
||||
Flags:
|
||||
--image=IMAGE Image to post.
|
||||
|
||||
Args:
|
||||
<channel> Channel to post to.
|
||||
[<text>] Text to post.
|
||||
|
||||
$ chat post --image=~/Downloads/owls.jpg pics
|
||||
...
|
||||
```
|
||||
|
||||
From this code:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
app = kingpin.New("chat", "A command-line chat application.")
|
||||
debug = app.Flag("debug", "Enable debug mode.").Bool()
|
||||
serverIP = app.Flag("server", "Server address.").Default("127.0.0.1").IP()
|
||||
|
||||
register = app.Command("register", "Register a new user.")
|
||||
registerNick = register.Arg("nick", "Nickname for user.").Required().String()
|
||||
registerName = register.Arg("name", "Name of user.").Required().String()
|
||||
|
||||
post = app.Command("post", "Post a message to a channel.")
|
||||
postImage = post.Flag("image", "Image to post.").File()
|
||||
postChannel = post.Arg("channel", "Channel to post to.").Required().String()
|
||||
postText = post.Arg("text", "Text to post.").Strings()
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch kingpin.MustParse(app.Parse(os.Args[1:])) {
|
||||
// Register user
|
||||
case register.FullCommand():
|
||||
println(*registerNick)
|
||||
|
||||
// Post message
|
||||
case post.FullCommand():
|
||||
if *postImage != nil {
|
||||
}
|
||||
text := strings.Join(*postText, " ")
|
||||
println("Post:", text)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
### Displaying errors and usage information
|
||||
|
||||
Kingpin exports a set of functions to provide consistent errors and usage
|
||||
information to the user.
|
||||
|
||||
Error messages look something like this:
|
||||
|
||||
<app>: error: <message>
|
||||
|
||||
The functions on `Application` are:
|
||||
|
||||
Function | Purpose
|
||||
---------|--------------
|
||||
`Errorf(format, args)` | Display a printf formatted error to the user.
|
||||
`Fatalf(format, args)` | As with Errorf, but also call the termination handler.
|
||||
`FatalUsage(format, args)` | As with Fatalf, but also print contextual usage information.
|
||||
`FatalUsageContext(context, format, args)` | As with Fatalf, but also print contextual usage information from a `ParseContext`.
|
||||
`FatalIfError(err, format, args)` | Conditionally print an error prefixed with format+args, then call the termination handler
|
||||
|
||||
There are equivalent global functions in the kingpin namespace for the default
|
||||
`kingpin.CommandLine` instance.
|
||||
|
||||
### Sub-commands
|
||||
|
||||
Kingpin supports nested sub-commands, with separate flag and positional
|
||||
arguments per sub-command. Note that positional arguments may only occur after
|
||||
sub-commands.
|
||||
|
||||
For example:
|
||||
|
||||
```go
|
||||
var (
|
||||
deleteCommand = kingpin.Command("delete", "Delete an object.")
|
||||
deleteUserCommand = deleteCommand.Command("user", "Delete a user.")
|
||||
deleteUserUIDFlag = deleteUserCommand.Flag("uid", "Delete user by UID rather than username.")
|
||||
deleteUserUsername = deleteUserCommand.Arg("username", "Username to delete.")
|
||||
deletePostCommand = deleteCommand.Command("post", "Delete a post.")
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch kingpin.Parse() {
|
||||
case "delete user":
|
||||
case "delete post":
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Parsers
|
||||
|
||||
Kingpin supports both flag and positional argument parsers for converting to
|
||||
Go types. For example, some included parsers are `Int()`, `Float()`,
|
||||
`Duration()` and `ExistingFile()` (see [parsers.go](./parsers.go) for a complete list of included parsers).
|
||||
|
||||
Parsers conform to Go's [`flag.Value`](http://godoc.org/flag#Value)
|
||||
interface, so any existing implementations will work.
|
||||
|
||||
For example, a parser for accumulating HTTP header values might look like this:
|
||||
|
||||
```go
|
||||
type HTTPHeaderValue http.Header
|
||||
|
||||
func (h *HTTPHeaderValue) Set(value string) error {
|
||||
parts := strings.SplitN(value, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected HEADER:VALUE got '%s'", value)
|
||||
}
|
||||
(*http.Header)(h).Add(parts[0], parts[1])
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HTTPHeaderValue) String() string {
|
||||
return ""
|
||||
}
|
||||
```
|
||||
|
||||
As a convenience, I would recommend something like this:
|
||||
|
||||
```go
|
||||
func HTTPHeader(s Settings) (target *http.Header) {
|
||||
target = &http.Header{}
|
||||
s.SetValue((*HTTPHeaderValue)(target))
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
You would use it like so:
|
||||
|
||||
```go
|
||||
headers = HTTPHeader(kingpin.Flag("header", "Add a HTTP header to the request.").Short('H'))
|
||||
```
|
||||
|
||||
### Repeatable flags
|
||||
|
||||
Depending on the `Value` they hold, some flags may be repeated. The
|
||||
`IsCumulative() bool` function on `Value` tells if it's safe to call `Set()`
|
||||
multiple times or if an error should be raised if several values are passed.
|
||||
|
||||
The built-in `Value`s returning slices and maps, as well as `Counter` are
|
||||
examples of `Value`s that make a flag repeatable.
|
||||
|
||||
### Boolean values
|
||||
|
||||
Boolean values are uniquely managed by Kingpin. Each boolean flag will have a negative complement:
|
||||
`--<name>` and `--no-<name>`.
|
||||
|
||||
### Default Values
|
||||
|
||||
The default value is the zero value for a type. This can be overridden with
|
||||
the `Default(value...)` function on flags and arguments. This function accepts
|
||||
one or several strings, which are parsed by the value itself, so they *must*
|
||||
be compliant with the format expected.
|
||||
|
||||
### Place-holders in Help
|
||||
|
||||
The place-holder value for a flag is the value used in the help to describe
|
||||
the value of a non-boolean flag.
|
||||
|
||||
The value provided to PlaceHolder() is used if provided, then the value
|
||||
provided by Default() if provided, then finally the capitalised flag name is
|
||||
used.
|
||||
|
||||
Here are some examples of flags with various permutations:
|
||||
|
||||
--name=NAME // Flag(...).String()
|
||||
--name="Harry" // Flag(...).Default("Harry").String()
|
||||
--name=FULL-NAME // Flag(...).PlaceHolder("FULL-NAME").Default("Harry").String()
|
||||
|
||||
### Consuming all remaining arguments
|
||||
|
||||
A common command-line idiom is to use all remaining arguments for some
|
||||
purpose. eg. The following command accepts an arbitrary number of
|
||||
IP addresses as positional arguments:
|
||||
|
||||
./cmd ping 10.1.1.1 192.168.1.1
|
||||
|
||||
Such arguments are similar to [repeatable flags](#repeatable-flags), but for
|
||||
arguments. Therefore they use the same `IsCumulative() bool` function on the
|
||||
underlying `Value`, so the built-in `Value`s for which the `Set()` function
|
||||
can be called several times will consume multiple arguments.
|
||||
|
||||
To implement the above example with a custom `Value`, we might do something
|
||||
like this:
|
||||
|
||||
```go
|
||||
type ipList []net.IP
|
||||
|
||||
func (i *ipList) Set(value string) error {
|
||||
if ip := net.ParseIP(value); ip == nil {
|
||||
return fmt.Errorf("'%s' is not an IP address", value)
|
||||
} else {
|
||||
*i = append(*i, ip)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ipList) String() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (i *ipList) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func IPList(s Settings) (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
s.SetValue((*ipList)(target))
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
And use it like so:
|
||||
|
||||
```go
|
||||
ips := IPList(kingpin.Arg("ips", "IP addresses to ping."))
|
||||
```
|
||||
|
||||
### Bash/ZSH Shell Completion
|
||||
|
||||
By default, all flags and commands/subcommands generate completions
|
||||
internally.
|
||||
|
||||
Out of the box, CLI tools using kingpin should be able to take advantage
|
||||
of completion hinting for flags and commands. By specifying
|
||||
`--completion-bash` as the first argument, your CLI tool will show
|
||||
possible subcommands. By ending your argv with `--`, hints for flags
|
||||
will be shown.
|
||||
|
||||
To allow your end users to take advantage you must package a
|
||||
`/etc/bash_completion.d` script with your distribution (or the equivalent
|
||||
for your target platform/shell). An alternative is to instruct your end
|
||||
user to source a script from their `bash_profile` (or equivalent).
|
||||
|
||||
Fortunately Kingpin makes it easy to generate or source a script for use
|
||||
with end users shells. `./yourtool --completion-script-bash` and
|
||||
`./yourtool --completion-script-zsh` will generate these scripts for you.
|
||||
|
||||
**Installation by Package**
|
||||
|
||||
For the best user experience, you should bundle your pre-created
|
||||
completion script with your CLI tool and install it inside
|
||||
`/etc/bash_completion.d` (or equivalent). A good suggestion is to add
|
||||
this as an automated step to your build pipeline, in the implementation
|
||||
is improved for bug fixed.
|
||||
|
||||
**Installation by `bash_profile`**
|
||||
|
||||
Alternatively, instruct your users to add an additional statement to
|
||||
their `bash_profile` (or equivalent):
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-bash)"
|
||||
```
|
||||
|
||||
Or for ZSH
|
||||
|
||||
```
|
||||
eval "$(your-cli-tool --completion-script-zsh)"
|
||||
```
|
||||
|
||||
#### Additional API
|
||||
To provide more flexibility, a completion option API has been
|
||||
exposed for flags to allow user defined completion options, to extend
|
||||
completions further than just EnumVar/Enum.
|
||||
|
||||
|
||||
**Provide Static Options**
|
||||
|
||||
When using an `Enum` or `EnumVar`, users are limited to only the options
|
||||
given. Maybe we wish to hint possible options to the user, but also
|
||||
allow them to provide their own custom option. `HintOptions` gives
|
||||
this functionality to flags.
|
||||
|
||||
```
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("port", "Provide a port to connect to").
|
||||
Required().
|
||||
HintOptions("80", "443", "8080").
|
||||
IntVar(&c.port)
|
||||
```
|
||||
|
||||
**Provide Dynamic Options**
|
||||
Consider the case that you needed to read a local database or a file to
|
||||
provide suggestions. You can dynamically generate the options
|
||||
|
||||
```
|
||||
func listHosts() []string {
|
||||
// Provide a dynamic list of hosts from a hosts file or otherwise
|
||||
// for bash completion. In this example we simply return static slice.
|
||||
|
||||
// You could use this functionality to reach into a hosts file to provide
|
||||
// completion for a list of known hosts.
|
||||
return []string{"sshhost.example", "webhost.example", "ftphost.example"}
|
||||
}
|
||||
|
||||
app := kingpin.New("completion", "My application with bash completion.")
|
||||
app.Flag("flag-1", "").HintAction(listHosts).String()
|
||||
```
|
||||
|
||||
**EnumVar/Enum**
|
||||
When using `Enum` or `EnumVar`, any provided options will be automatically
|
||||
used for bash autocompletion. However, if you wish to provide a subset or
|
||||
different options, you can use `HintOptions` or `HintAction` which will override
|
||||
the default completion options for `Enum`/`EnumVar`.
|
||||
|
||||
|
||||
**Examples**
|
||||
You can see an in depth example of the completion API within
|
||||
`examples/completion/main.go`
|
||||
|
||||
|
||||
### Supporting -h for help
|
||||
|
||||
`kingpin.CommandLine.HelpFlag.Short('h')`
|
||||
|
||||
### Custom help
|
||||
|
||||
Kingpin v2 supports templatised help using the text/template library (actually, [a fork](https://github.com/alecthomas/template)).
|
||||
|
||||
You can specify the template to use with the [Application.UsageTemplate()](http://godoc.org/gopkg.in/alecthomas/kingpin.v2#Application.UsageTemplate) function.
|
||||
|
||||
There are four included templates: `kingpin.DefaultUsageTemplate` is the default,
|
||||
`kingpin.CompactUsageTemplate` provides a more compact representation for more complex command-line structures,
|
||||
`kingpin.SeparateOptionalFlagsUsageTemplate` looks like the default template, but splits required
|
||||
and optional command flags into separate lists, and `kingpin.ManPageTemplate` is used to generate man pages.
|
||||
|
||||
See the above templates for examples of usage, and the the function [UsageForContextWithTemplate()](https://github.com/alecthomas/kingpin/blob/master/usage.go#L198) method for details on the context.
|
||||
|
||||
#### Default help template
|
||||
|
||||
```
|
||||
$ go run ./examples/curl/curl.go --help
|
||||
usage: curl [<flags>] <command> [<args> ...]
|
||||
|
||||
An example implementation of curl.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
-t, --timeout=5s Set connection timeout.
|
||||
-H, --headers=HEADER=VALUE
|
||||
Add HTTP headers to the request.
|
||||
|
||||
Commands:
|
||||
help [<command>...]
|
||||
Show help.
|
||||
|
||||
get url <url>
|
||||
Retrieve a URL.
|
||||
|
||||
get file <file>
|
||||
Retrieve a file.
|
||||
|
||||
post [<flags>] <url>
|
||||
POST a resource.
|
||||
```
|
||||
|
||||
#### Compact help template
|
||||
|
||||
```
|
||||
$ go run ./examples/curl/curl.go --help
|
||||
usage: curl [<flags>] <command> [<args> ...]
|
||||
|
||||
An example implementation of curl.
|
||||
|
||||
Flags:
|
||||
--help Show help.
|
||||
-t, --timeout=5s Set connection timeout.
|
||||
-H, --headers=HEADER=VALUE
|
||||
Add HTTP headers to the request.
|
||||
|
||||
Commands:
|
||||
help [<command>...]
|
||||
get [<flags>]
|
||||
url <url>
|
||||
file <file>
|
||||
post [<flags>] <url>
|
||||
```
|
||||
42
vendor/gopkg.in/alecthomas/kingpin.v2/actions.go
generated
vendored
Normal file
42
vendor/gopkg.in/alecthomas/kingpin.v2/actions.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
package kingpin
|
||||
|
||||
// Action callback executed at various stages after all values are populated.
|
||||
// The application, commands, arguments and flags all have corresponding
|
||||
// actions.
|
||||
type Action func(*ParseContext) error
|
||||
|
||||
type actionMixin struct {
|
||||
actions []Action
|
||||
preActions []Action
|
||||
}
|
||||
|
||||
type actionApplier interface {
|
||||
applyActions(*ParseContext) error
|
||||
applyPreActions(*ParseContext) error
|
||||
}
|
||||
|
||||
func (a *actionMixin) addAction(action Action) {
|
||||
a.actions = append(a.actions, action)
|
||||
}
|
||||
|
||||
func (a *actionMixin) addPreAction(action Action) {
|
||||
a.preActions = append(a.preActions, action)
|
||||
}
|
||||
|
||||
func (a *actionMixin) applyActions(context *ParseContext) error {
|
||||
for _, action := range a.actions {
|
||||
if err := action(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *actionMixin) applyPreActions(context *ParseContext) error {
|
||||
for _, preAction := range a.preActions {
|
||||
if err := preAction(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
688
vendor/gopkg.in/alecthomas/kingpin.v2/app.go
generated
vendored
Normal file
688
vendor/gopkg.in/alecthomas/kingpin.v2/app.go
generated
vendored
Normal file
@@ -0,0 +1,688 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCommandNotSpecified = fmt.Errorf("command not specified")
|
||||
)
|
||||
|
||||
var (
|
||||
envarTransformRegexp = regexp.MustCompile(`[^a-zA-Z0-9_]+`)
|
||||
)
|
||||
|
||||
type ApplicationValidator func(*Application) error
|
||||
|
||||
// An Application contains the definitions of flags, arguments and commands
|
||||
// for an application.
|
||||
type Application struct {
|
||||
cmdMixin
|
||||
initialized bool
|
||||
|
||||
Name string
|
||||
Help string
|
||||
|
||||
author string
|
||||
version string
|
||||
errorWriter io.Writer // Destination for errors.
|
||||
usageWriter io.Writer // Destination for usage
|
||||
usageTemplate string
|
||||
validator ApplicationValidator
|
||||
terminate func(status int) // See Terminate()
|
||||
noInterspersed bool // can flags be interspersed with args (or must they come first)
|
||||
defaultEnvars bool
|
||||
completion bool
|
||||
|
||||
// Help flag. Exposed for user customisation.
|
||||
HelpFlag *FlagClause
|
||||
// Help command. Exposed for user customisation. May be nil.
|
||||
HelpCommand *CmdClause
|
||||
// Version flag. Exposed for user customisation. May be nil.
|
||||
VersionFlag *FlagClause
|
||||
}
|
||||
|
||||
// New creates a new Kingpin application instance.
|
||||
func New(name, help string) *Application {
|
||||
a := &Application{
|
||||
Name: name,
|
||||
Help: help,
|
||||
errorWriter: os.Stderr, // Left for backwards compatibility purposes.
|
||||
usageWriter: os.Stderr,
|
||||
usageTemplate: DefaultUsageTemplate,
|
||||
terminate: os.Exit,
|
||||
}
|
||||
a.flagGroup = newFlagGroup()
|
||||
a.argGroup = newArgGroup()
|
||||
a.cmdGroup = newCmdGroup(a)
|
||||
a.HelpFlag = a.Flag("help", "Show context-sensitive help (also try --help-long and --help-man).")
|
||||
a.HelpFlag.Bool()
|
||||
a.Flag("help-long", "Generate long help.").Hidden().PreAction(a.generateLongHelp).Bool()
|
||||
a.Flag("help-man", "Generate a man page.").Hidden().PreAction(a.generateManPage).Bool()
|
||||
a.Flag("completion-bash", "Output possible completions for the given args.").Hidden().BoolVar(&a.completion)
|
||||
a.Flag("completion-script-bash", "Generate completion script for bash.").Hidden().PreAction(a.generateBashCompletionScript).Bool()
|
||||
a.Flag("completion-script-zsh", "Generate completion script for ZSH.").Hidden().PreAction(a.generateZSHCompletionScript).Bool()
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Application) generateLongHelp(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, LongHelpTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateManPage(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, ManPageTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, BashCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) generateZSHCompletionScript(c *ParseContext) error {
|
||||
a.Writer(os.Stdout)
|
||||
if err := a.UsageForContextWithTemplate(c, 2, ZshCompletionTemplate); err != nil {
|
||||
return err
|
||||
}
|
||||
a.terminate(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultEnvars configures all flags (that do not already have an associated
|
||||
// envar) to use a default environment variable in the form "<app>_<flag>".
|
||||
//
|
||||
// For example, if the application is named "foo" and a flag is named "bar-
|
||||
// waz" the environment variable: "FOO_BAR_WAZ".
|
||||
func (a *Application) DefaultEnvars() *Application {
|
||||
a.defaultEnvars = true
|
||||
return a
|
||||
}
|
||||
|
||||
// Terminate specifies the termination handler. Defaults to os.Exit(status).
|
||||
// If nil is passed, a no-op function will be used.
|
||||
func (a *Application) Terminate(terminate func(int)) *Application {
|
||||
if terminate == nil {
|
||||
terminate = func(int) {}
|
||||
}
|
||||
a.terminate = terminate
|
||||
return a
|
||||
}
|
||||
|
||||
// Writer specifies the writer to use for usage and errors. Defaults to os.Stderr.
|
||||
// DEPRECATED: See ErrorWriter and UsageWriter.
|
||||
func (a *Application) Writer(w io.Writer) *Application {
|
||||
a.errorWriter = w
|
||||
a.usageWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// ErrorWriter sets the io.Writer to use for errors.
|
||||
func (a *Application) ErrorWriter(w io.Writer) *Application {
|
||||
a.errorWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// UsageWriter sets the io.Writer to use for errors.
|
||||
func (a *Application) UsageWriter(w io.Writer) *Application {
|
||||
a.usageWriter = w
|
||||
return a
|
||||
}
|
||||
|
||||
// UsageTemplate specifies the text template to use when displaying usage
|
||||
// information. The default is UsageTemplate.
|
||||
func (a *Application) UsageTemplate(template string) *Application {
|
||||
a.usageTemplate = template
|
||||
return a
|
||||
}
|
||||
|
||||
// Validate sets a validation function to run when parsing.
|
||||
func (a *Application) Validate(validator ApplicationValidator) *Application {
|
||||
a.validator = validator
|
||||
return a
|
||||
}
|
||||
|
||||
// ParseContext parses the given command line and returns the fully populated
|
||||
// ParseContext.
|
||||
func (a *Application) ParseContext(args []string) (*ParseContext, error) {
|
||||
return a.parseContext(false, args)
|
||||
}
|
||||
|
||||
func (a *Application) parseContext(ignoreDefault bool, args []string) (*ParseContext, error) {
|
||||
if err := a.init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
context := tokenize(args, ignoreDefault)
|
||||
err := parse(context, a)
|
||||
return context, err
|
||||
}
|
||||
|
||||
// Parse parses command-line arguments. It returns the selected command and an
|
||||
// error. The selected command will be a space separated subcommand, if
|
||||
// subcommands have been configured.
|
||||
//
|
||||
// This will populate all flag and argument values, call all callbacks, and so
|
||||
// on.
|
||||
func (a *Application) Parse(args []string) (command string, err error) {
|
||||
|
||||
context, parseErr := a.ParseContext(args)
|
||||
selected := []string{}
|
||||
var setValuesErr error
|
||||
|
||||
if context == nil {
|
||||
// Since we do not throw error immediately, there could be a case
|
||||
// where a context returns nil. Protect against that.
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
if err = a.setDefaults(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
selected, setValuesErr = a.setValues(context)
|
||||
|
||||
if err = a.applyPreActions(context, !a.completion); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if a.completion {
|
||||
a.generateBashCompletion(context)
|
||||
a.terminate(0)
|
||||
} else {
|
||||
if parseErr != nil {
|
||||
return "", parseErr
|
||||
}
|
||||
|
||||
a.maybeHelp(context)
|
||||
if !context.EOL() {
|
||||
return "", fmt.Errorf("unexpected argument '%s'", context.Peek())
|
||||
}
|
||||
|
||||
if setValuesErr != nil {
|
||||
return "", setValuesErr
|
||||
}
|
||||
|
||||
command, err = a.execute(context, selected)
|
||||
if err == ErrCommandNotSpecified {
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
return command, err
|
||||
}
|
||||
|
||||
func (a *Application) writeUsage(context *ParseContext, err error) {
|
||||
if err != nil {
|
||||
a.Errorf("%s", err)
|
||||
}
|
||||
if err := a.UsageForContext(context); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err != nil {
|
||||
a.terminate(1)
|
||||
} else {
|
||||
a.terminate(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) maybeHelp(context *ParseContext) {
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok && flag == a.HelpFlag {
|
||||
// Re-parse the command-line ignoring defaults, so that help works correctly.
|
||||
context, _ = a.parseContext(true, context.rawArgs)
|
||||
a.writeUsage(context, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Version adds a --version flag for displaying the application version.
|
||||
func (a *Application) Version(version string) *Application {
|
||||
a.version = version
|
||||
a.VersionFlag = a.Flag("version", "Show application version.").PreAction(func(*ParseContext) error {
|
||||
fmt.Fprintln(a.usageWriter, version)
|
||||
a.terminate(0)
|
||||
return nil
|
||||
})
|
||||
a.VersionFlag.Bool()
|
||||
return a
|
||||
}
|
||||
|
||||
// Author sets the author output by some help templates.
|
||||
func (a *Application) Author(author string) *Application {
|
||||
a.author = author
|
||||
return a
|
||||
}
|
||||
|
||||
// Action callback to call when all values are populated and parsing is
|
||||
// complete, but before any command, flag or argument actions.
|
||||
//
|
||||
// All Action() callbacks are called in the order they are encountered on the
|
||||
// command line.
|
||||
func (a *Application) Action(action Action) *Application {
|
||||
a.addAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// Action called after parsing completes but before validation and execution.
|
||||
func (a *Application) PreAction(action Action) *Application {
|
||||
a.addPreAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// Command adds a new top-level command.
|
||||
func (a *Application) Command(name, help string) *CmdClause {
|
||||
return a.addCommand(name, help)
|
||||
}
|
||||
|
||||
// Interspersed control if flags can be interspersed with positional arguments
|
||||
//
|
||||
// true (the default) means that they can, false means that all the flags must appear before the first positional arguments.
|
||||
func (a *Application) Interspersed(interspersed bool) *Application {
|
||||
a.noInterspersed = !interspersed
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Application) defaultEnvarPrefix() string {
|
||||
if a.defaultEnvars {
|
||||
return a.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (a *Application) init() error {
|
||||
if a.initialized {
|
||||
return nil
|
||||
}
|
||||
if a.cmdGroup.have() && a.argGroup.have() {
|
||||
return fmt.Errorf("can't mix top-level Arg()s with Command()s")
|
||||
}
|
||||
|
||||
// If we have subcommands, add a help command at the top-level.
|
||||
if a.cmdGroup.have() {
|
||||
var command []string
|
||||
a.HelpCommand = a.Command("help", "Show help.").PreAction(func(context *ParseContext) error {
|
||||
a.Usage(command)
|
||||
a.terminate(0)
|
||||
return nil
|
||||
})
|
||||
a.HelpCommand.Arg("command", "Show help on command.").StringsVar(&command)
|
||||
// Make help first command.
|
||||
l := len(a.commandOrder)
|
||||
a.commandOrder = append(a.commandOrder[l-1:l], a.commandOrder[:l-1]...)
|
||||
}
|
||||
|
||||
if err := a.flagGroup.init(a.defaultEnvarPrefix()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.cmdGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := a.argGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cmd := range a.commands {
|
||||
if err := cmd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
flagGroups := []*flagGroup{a.flagGroup}
|
||||
for _, cmd := range a.commandOrder {
|
||||
if err := checkDuplicateFlags(cmd, flagGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
a.initialized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recursively check commands for duplicate flags.
|
||||
func checkDuplicateFlags(current *CmdClause, flagGroups []*flagGroup) error {
|
||||
// Check for duplicates.
|
||||
for _, flags := range flagGroups {
|
||||
for _, flag := range current.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
if _, ok := flags.short[string(flag.shorthand)]; ok {
|
||||
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||
}
|
||||
}
|
||||
if _, ok := flags.long[flag.name]; ok {
|
||||
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
flagGroups = append(flagGroups, current.flagGroup)
|
||||
// Check subcommands.
|
||||
for _, subcmd := range current.commandOrder {
|
||||
if err := checkDuplicateFlags(subcmd, flagGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) execute(context *ParseContext, selected []string) (string, error) {
|
||||
var err error
|
||||
|
||||
if err = a.validateRequired(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.applyValidators(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = a.applyActions(context); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
command := strings.Join(selected, " ")
|
||||
if command == "" && a.cmdGroup.have() {
|
||||
return "", ErrCommandNotSpecified
|
||||
}
|
||||
return command, err
|
||||
}
|
||||
|
||||
func (a *Application) setDefaults(context *ParseContext) error {
|
||||
flagElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||
if flag.name == "help" {
|
||||
return nil
|
||||
}
|
||||
flagElements[flag.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
argElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||
argElements[arg.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
// Check required flags and set defaults.
|
||||
for _, flag := range context.flags.long {
|
||||
if flagElements[flag.name] == nil {
|
||||
if err := flag.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
if err := arg.setDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) validateRequired(context *ParseContext) error {
|
||||
flagElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if flag, ok := element.Clause.(*FlagClause); ok {
|
||||
flagElements[flag.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
argElements := map[string]*ParseElement{}
|
||||
for _, element := range context.Elements {
|
||||
if arg, ok := element.Clause.(*ArgClause); ok {
|
||||
argElements[arg.name] = element
|
||||
}
|
||||
}
|
||||
|
||||
// Check required flags and set defaults.
|
||||
for _, flag := range context.flags.long {
|
||||
if flagElements[flag.name] == nil {
|
||||
// Check required flags were provided.
|
||||
if flag.needsValue() {
|
||||
return fmt.Errorf("required flag --%s not provided", flag.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, arg := range context.arguments.args {
|
||||
if argElements[arg.name] == nil {
|
||||
if arg.needsValue() {
|
||||
return fmt.Errorf("required argument '%s' not provided", arg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) setValues(context *ParseContext) (selected []string, err error) {
|
||||
// Set all arg and flag values.
|
||||
var (
|
||||
lastCmd *CmdClause
|
||||
flagSet = map[string]struct{}{}
|
||||
)
|
||||
for _, element := range context.Elements {
|
||||
switch clause := element.Clause.(type) {
|
||||
case *FlagClause:
|
||||
if _, ok := flagSet[clause.name]; ok {
|
||||
if v, ok := clause.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
return nil, fmt.Errorf("flag '%s' cannot be repeated", clause.name)
|
||||
}
|
||||
}
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
return
|
||||
}
|
||||
flagSet[clause.name] = struct{}{}
|
||||
|
||||
case *ArgClause:
|
||||
if err = clause.value.Set(*element.Value); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
case *CmdClause:
|
||||
if clause.validator != nil {
|
||||
if err = clause.validator(clause); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
selected = append(selected, clause.name)
|
||||
lastCmd = clause
|
||||
}
|
||||
}
|
||||
|
||||
if lastCmd != nil && len(lastCmd.commands) > 0 {
|
||||
return nil, fmt.Errorf("must select a subcommand of '%s'", lastCmd.FullCommand())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *Application) applyValidators(context *ParseContext) (err error) {
|
||||
// Call command validation functions.
|
||||
for _, element := range context.Elements {
|
||||
if cmd, ok := element.Clause.(*CmdClause); ok && cmd.validator != nil {
|
||||
if err = cmd.validator(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if a.validator != nil {
|
||||
err = a.validator(a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Application) applyPreActions(context *ParseContext, dispatch bool) error {
|
||||
if err := a.actionMixin.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch to actions.
|
||||
if dispatch {
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyPreActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Application) applyActions(context *ParseContext) error {
|
||||
if err := a.actionMixin.applyActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
// Dispatch to actions.
|
||||
for _, element := range context.Elements {
|
||||
if applier, ok := element.Clause.(actionApplier); ok {
|
||||
if err := applier.applyActions(context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Errorf prints an error message to w in the format "<appname>: error: <message>".
|
||||
func (a *Application) Errorf(format string, args ...interface{}) {
|
||||
fmt.Fprintf(a.errorWriter, a.Name+": error: "+format+"\n", args...)
|
||||
}
|
||||
|
||||
// Fatalf writes a formatted error to w then terminates with exit status 1.
|
||||
func (a *Application) Fatalf(format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalUsage prints an error message followed by usage information, then
|
||||
// exits with a non-zero status.
|
||||
func (a *Application) FatalUsage(format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
// Force usage to go to error output.
|
||||
a.usageWriter = a.errorWriter
|
||||
a.Usage([]string{})
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalUsageContext writes a printf formatted error message to w, then usage
|
||||
// information for the given ParseContext, before exiting.
|
||||
func (a *Application) FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||
a.Errorf(format, args...)
|
||||
if err := a.UsageForContext(context); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
a.terminate(1)
|
||||
}
|
||||
|
||||
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||
// with the given formatted string, if any.
|
||||
func (a *Application) FatalIfError(err error, format string, args ...interface{}) {
|
||||
if err != nil {
|
||||
prefix := ""
|
||||
if format != "" {
|
||||
prefix = fmt.Sprintf(format, args...) + ": "
|
||||
}
|
||||
a.Errorf(prefix+"%s", err)
|
||||
a.terminate(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Application) completionOptions(context *ParseContext) []string {
|
||||
args := context.rawArgs
|
||||
|
||||
var (
|
||||
currArg string
|
||||
prevArg string
|
||||
target cmdMixin
|
||||
)
|
||||
|
||||
numArgs := len(args)
|
||||
if numArgs > 1 {
|
||||
args = args[1:]
|
||||
currArg = args[len(args)-1]
|
||||
}
|
||||
if numArgs > 2 {
|
||||
prevArg = args[len(args)-2]
|
||||
}
|
||||
|
||||
target = a.cmdMixin
|
||||
if context.SelectedCommand != nil {
|
||||
// A subcommand was in use. We will use it as the target
|
||||
target = context.SelectedCommand.cmdMixin
|
||||
}
|
||||
|
||||
if (currArg != "" && strings.HasPrefix(currArg, "--")) || strings.HasPrefix(prevArg, "--") {
|
||||
// Perform completion for A flag. The last/current argument started with "-"
|
||||
var (
|
||||
flagName string // The name of a flag if given (could be half complete)
|
||||
flagValue string // The value assigned to a flag (if given) (could be half complete)
|
||||
)
|
||||
|
||||
if strings.HasPrefix(prevArg, "--") && !strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag value
|
||||
// Wont Match: ./myApp --flag --
|
||||
flagName = prevArg[2:] // Strip the "--"
|
||||
flagValue = currArg
|
||||
} else if strings.HasPrefix(currArg, "--") {
|
||||
// Matches: ./myApp --flag --
|
||||
// Matches: ./myApp --flag somevalue --
|
||||
// Matches: ./myApp --
|
||||
flagName = currArg[2:] // Strip the "--"
|
||||
}
|
||||
|
||||
options, flagMatched, valueMatched := target.FlagCompletion(flagName, flagValue)
|
||||
if valueMatched {
|
||||
// Value Matched. Show cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
// Add top level flags if we're not at the top level and no match was found.
|
||||
if context.SelectedCommand != nil && !flagMatched {
|
||||
topOptions, topFlagMatched, topValueMatched := a.FlagCompletion(flagName, flagValue)
|
||||
if topValueMatched {
|
||||
// Value Matched. Back to cmdCompletions
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
if topFlagMatched {
|
||||
// Top level had a flag which matched the input. Return it's options.
|
||||
options = topOptions
|
||||
} else {
|
||||
// Add top level flags
|
||||
options = append(options, topOptions...)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// Perform completion for sub commands and arguments.
|
||||
return target.CmdCompletion(context)
|
||||
}
|
||||
|
||||
func (a *Application) generateBashCompletion(context *ParseContext) {
|
||||
options := a.completionOptions(context)
|
||||
fmt.Printf("%s", strings.Join(options, "\n"))
|
||||
}
|
||||
|
||||
func envarTransform(name string) string {
|
||||
return strings.ToUpper(envarTransformRegexp.ReplaceAllString(name, "_"))
|
||||
}
|
||||
184
vendor/gopkg.in/alecthomas/kingpin.v2/args.go
generated
vendored
Normal file
184
vendor/gopkg.in/alecthomas/kingpin.v2/args.go
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type argGroup struct {
|
||||
args []*ArgClause
|
||||
}
|
||||
|
||||
func newArgGroup() *argGroup {
|
||||
return &argGroup{}
|
||||
}
|
||||
|
||||
func (a *argGroup) have() bool {
|
||||
return len(a.args) > 0
|
||||
}
|
||||
|
||||
// GetArg gets an argument definition.
|
||||
//
|
||||
// This allows existing arguments to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (a *argGroup) GetArg(name string) *ArgClause {
|
||||
for _, arg := range a.args {
|
||||
if arg.name == name {
|
||||
return arg
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *argGroup) Arg(name, help string) *ArgClause {
|
||||
arg := newArg(name, help)
|
||||
a.args = append(a.args, arg)
|
||||
return arg
|
||||
}
|
||||
|
||||
func (a *argGroup) init() error {
|
||||
required := 0
|
||||
seen := map[string]struct{}{}
|
||||
previousArgMustBeLast := false
|
||||
for i, arg := range a.args {
|
||||
if previousArgMustBeLast {
|
||||
return fmt.Errorf("Args() can't be followed by another argument '%s'", arg.name)
|
||||
}
|
||||
if arg.consumesRemainder() {
|
||||
previousArgMustBeLast = true
|
||||
}
|
||||
if _, ok := seen[arg.name]; ok {
|
||||
return fmt.Errorf("duplicate argument '%s'", arg.name)
|
||||
}
|
||||
seen[arg.name] = struct{}{}
|
||||
if arg.required && required != i {
|
||||
return fmt.Errorf("required arguments found after non-required")
|
||||
}
|
||||
if arg.required {
|
||||
required++
|
||||
}
|
||||
if err := arg.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ArgClause struct {
|
||||
actionMixin
|
||||
parserMixin
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
help string
|
||||
defaultValues []string
|
||||
required bool
|
||||
}
|
||||
|
||||
func newArg(name, help string) *ArgClause {
|
||||
a := &ArgClause{
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) setDefault() error {
|
||||
if a.HasEnvarValue() {
|
||||
if v, ok := a.value.(remainderArg); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return a.value.Set(a.GetEnvarValue())
|
||||
}
|
||||
for _, value := range a.GetSplitEnvarValue() {
|
||||
if err := a.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(a.defaultValues) > 0 {
|
||||
for _, defaultValue := range a.defaultValues {
|
||||
if err := a.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ArgClause) needsValue() bool {
|
||||
haveDefault := len(a.defaultValues) > 0
|
||||
return a.required && !(haveDefault || a.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (a *ArgClause) consumesRemainder() bool {
|
||||
if r, ok := a.value.(remainderArg); ok {
|
||||
return r.IsCumulative()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Required arguments must be input by the user. They can not have a Default() value provided.
|
||||
func (a *ArgClause) Required() *ArgClause {
|
||||
a.required = true
|
||||
return a
|
||||
}
|
||||
|
||||
// Default values for this argument. They *must* be parseable by the value of the argument.
|
||||
func (a *ArgClause) Default(values ...string) *ArgClause {
|
||||
a.defaultValues = values
|
||||
return a
|
||||
}
|
||||
|
||||
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||
// if it is set. Several default values can be provided by using new lines to
|
||||
// separate them.
|
||||
func (a *ArgClause) Envar(name string) *ArgClause {
|
||||
a.envar = name
|
||||
a.noEnvar = false
|
||||
return a
|
||||
}
|
||||
|
||||
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||
// Most useful in conjunction with app.DefaultEnvars().
|
||||
func (a *ArgClause) NoEnvar() *ArgClause {
|
||||
a.envar = ""
|
||||
a.noEnvar = true
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) Action(action Action) *ArgClause {
|
||||
a.addAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) PreAction(action Action) *ArgClause {
|
||||
a.addPreAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintAction registers a HintAction (function) for the arg to provide completions
|
||||
func (a *ArgClause) HintAction(action HintAction) *ArgClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *ArgClause) HintOptions(options ...string) *ArgClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ArgClause) init() error {
|
||||
if a.required && len(a.defaultValues) > 0 {
|
||||
return fmt.Errorf("required argument '%s' with unusable default value", a.name)
|
||||
}
|
||||
if a.value == nil {
|
||||
return fmt.Errorf("no parser defined for arg '%s'", a.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
274
vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go
generated
vendored
Normal file
274
vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go
generated
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type cmdMixin struct {
|
||||
*flagGroup
|
||||
*argGroup
|
||||
*cmdGroup
|
||||
actionMixin
|
||||
}
|
||||
|
||||
// CmdCompletion returns completion options for arguments, if that's where
|
||||
// parsing left off, or commands if there aren't any unsatisfied args.
|
||||
func (c *cmdMixin) CmdCompletion(context *ParseContext) []string {
|
||||
var options []string
|
||||
|
||||
// Count args already satisfied - we won't complete those, and add any
|
||||
// default commands' alternatives, since they weren't listed explicitly
|
||||
// and the user may want to explicitly list something else.
|
||||
argsSatisfied := 0
|
||||
for _, el := range context.Elements {
|
||||
switch clause := el.Clause.(type) {
|
||||
case *ArgClause:
|
||||
if el.Value != nil && *el.Value != "" {
|
||||
argsSatisfied++
|
||||
}
|
||||
case *CmdClause:
|
||||
options = append(options, clause.completionAlts...)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
if argsSatisfied < len(c.argGroup.args) {
|
||||
// Since not all args have been satisfied, show options for the current one
|
||||
options = append(options, c.argGroup.args[argsSatisfied].resolveCompletions()...)
|
||||
} else {
|
||||
// If all args are satisfied, then go back to completing commands
|
||||
for _, cmd := range c.cmdGroup.commandOrder {
|
||||
if !cmd.hidden {
|
||||
options = append(options, cmd.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices []string, flagMatch bool, optionMatch bool) {
|
||||
// Check if flagName matches a known flag.
|
||||
// If it does, show the options for the flag
|
||||
// Otherwise, show all flags
|
||||
|
||||
options := []string{}
|
||||
|
||||
for _, flag := range c.flagGroup.flagOrder {
|
||||
// Loop through each flag and determine if a match exists
|
||||
if flag.name == flagName {
|
||||
// User typed entire flag. Need to look for flag options.
|
||||
options = flag.resolveCompletions()
|
||||
if len(options) == 0 {
|
||||
// No Options to Choose From, Assume Match.
|
||||
return options, true, true
|
||||
}
|
||||
|
||||
// Loop options to find if the user specified value matches
|
||||
isPrefix := false
|
||||
matched := false
|
||||
|
||||
for _, opt := range options {
|
||||
if flagValue == opt {
|
||||
matched = true
|
||||
} else if strings.HasPrefix(opt, flagValue) {
|
||||
isPrefix = true
|
||||
}
|
||||
}
|
||||
|
||||
// Matched Flag Directly
|
||||
// Flag Value Not Prefixed, and Matched Directly
|
||||
return options, true, !isPrefix && matched
|
||||
}
|
||||
|
||||
if !flag.hidden {
|
||||
options = append(options, "--"+flag.name)
|
||||
}
|
||||
}
|
||||
// No Flag directly matched.
|
||||
return options, false, false
|
||||
|
||||
}
|
||||
|
||||
type cmdGroup struct {
|
||||
app *Application
|
||||
parent *CmdClause
|
||||
commands map[string]*CmdClause
|
||||
commandOrder []*CmdClause
|
||||
}
|
||||
|
||||
func (c *cmdGroup) defaultSubcommand() *CmdClause {
|
||||
for _, cmd := range c.commandOrder {
|
||||
if cmd.isDefault {
|
||||
return cmd
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdGroup) cmdNames() []string {
|
||||
names := make([]string, 0, len(c.commandOrder))
|
||||
for _, cmd := range c.commandOrder {
|
||||
names = append(names, cmd.name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// GetArg gets a command definition.
|
||||
//
|
||||
// This allows existing commands to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (c *cmdGroup) GetCommand(name string) *CmdClause {
|
||||
return c.commands[name]
|
||||
}
|
||||
|
||||
func newCmdGroup(app *Application) *cmdGroup {
|
||||
return &cmdGroup{
|
||||
app: app,
|
||||
commands: make(map[string]*CmdClause),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmdGroup) flattenedCommands() (out []*CmdClause) {
|
||||
for _, cmd := range c.commandOrder {
|
||||
if len(cmd.commands) == 0 {
|
||||
out = append(out, cmd)
|
||||
}
|
||||
out = append(out, cmd.flattenedCommands()...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cmdGroup) addCommand(name, help string) *CmdClause {
|
||||
cmd := newCommand(c.app, name, help)
|
||||
c.commands[name] = cmd
|
||||
c.commandOrder = append(c.commandOrder, cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (c *cmdGroup) init() error {
|
||||
seen := map[string]bool{}
|
||||
if c.defaultSubcommand() != nil && !c.have() {
|
||||
return fmt.Errorf("default subcommand %q provided but no subcommands defined", c.defaultSubcommand().name)
|
||||
}
|
||||
defaults := []string{}
|
||||
for _, cmd := range c.commandOrder {
|
||||
if cmd.isDefault {
|
||||
defaults = append(defaults, cmd.name)
|
||||
}
|
||||
if seen[cmd.name] {
|
||||
return fmt.Errorf("duplicate command %q", cmd.name)
|
||||
}
|
||||
seen[cmd.name] = true
|
||||
for _, alias := range cmd.aliases {
|
||||
if seen[alias] {
|
||||
return fmt.Errorf("alias duplicates existing command %q", alias)
|
||||
}
|
||||
c.commands[alias] = cmd
|
||||
}
|
||||
if err := cmd.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(defaults) > 1 {
|
||||
return fmt.Errorf("more than one default subcommand exists: %s", strings.Join(defaults, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cmdGroup) have() bool {
|
||||
return len(c.commands) > 0
|
||||
}
|
||||
|
||||
type CmdClauseValidator func(*CmdClause) error
|
||||
|
||||
// A CmdClause is a single top-level command. It encapsulates a set of flags
|
||||
// and either subcommands or positional arguments.
|
||||
type CmdClause struct {
|
||||
cmdMixin
|
||||
app *Application
|
||||
name string
|
||||
aliases []string
|
||||
help string
|
||||
isDefault bool
|
||||
validator CmdClauseValidator
|
||||
hidden bool
|
||||
completionAlts []string
|
||||
}
|
||||
|
||||
func newCommand(app *Application, name, help string) *CmdClause {
|
||||
c := &CmdClause{
|
||||
app: app,
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
c.flagGroup = newFlagGroup()
|
||||
c.argGroup = newArgGroup()
|
||||
c.cmdGroup = newCmdGroup(app)
|
||||
return c
|
||||
}
|
||||
|
||||
// Add an Alias for this command.
|
||||
func (c *CmdClause) Alias(name string) *CmdClause {
|
||||
c.aliases = append(c.aliases, name)
|
||||
return c
|
||||
}
|
||||
|
||||
// Validate sets a validation function to run when parsing.
|
||||
func (c *CmdClause) Validate(validator CmdClauseValidator) *CmdClause {
|
||||
c.validator = validator
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) FullCommand() string {
|
||||
out := []string{c.name}
|
||||
for p := c.parent; p != nil; p = p.parent {
|
||||
out = append([]string{p.name}, out...)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// Command adds a new sub-command.
|
||||
func (c *CmdClause) Command(name, help string) *CmdClause {
|
||||
cmd := c.addCommand(name, help)
|
||||
cmd.parent = c
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Default makes this command the default if commands don't match.
|
||||
func (c *CmdClause) Default() *CmdClause {
|
||||
c.isDefault = true
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) Action(action Action) *CmdClause {
|
||||
c.addAction(action)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) PreAction(action Action) *CmdClause {
|
||||
c.addPreAction(action)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *CmdClause) init() error {
|
||||
if err := c.flagGroup.init(c.app.defaultEnvarPrefix()); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.argGroup.have() && c.cmdGroup.have() {
|
||||
return fmt.Errorf("can't mix Arg()s with Command()s")
|
||||
}
|
||||
if err := c.argGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.cmdGroup.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CmdClause) Hidden() *CmdClause {
|
||||
c.hidden = true
|
||||
return c
|
||||
}
|
||||
33
vendor/gopkg.in/alecthomas/kingpin.v2/completions.go
generated
vendored
Normal file
33
vendor/gopkg.in/alecthomas/kingpin.v2/completions.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package kingpin
|
||||
|
||||
// HintAction is a function type who is expected to return a slice of possible
|
||||
// command line arguments.
|
||||
type HintAction func() []string
|
||||
type completionsMixin struct {
|
||||
hintActions []HintAction
|
||||
builtinHintActions []HintAction
|
||||
}
|
||||
|
||||
func (a *completionsMixin) addHintAction(action HintAction) {
|
||||
a.hintActions = append(a.hintActions, action)
|
||||
}
|
||||
|
||||
// Allow adding of HintActions which are added internally, ie, EnumVar
|
||||
func (a *completionsMixin) addHintActionBuiltin(action HintAction) {
|
||||
a.builtinHintActions = append(a.builtinHintActions, action)
|
||||
}
|
||||
|
||||
func (a *completionsMixin) resolveCompletions() []string {
|
||||
var hints []string
|
||||
|
||||
options := a.builtinHintActions
|
||||
if len(a.hintActions) > 0 {
|
||||
// User specified their own hintActions. Use those instead.
|
||||
options = a.hintActions
|
||||
}
|
||||
|
||||
for _, hintAction := range options {
|
||||
hints = append(hints, hintAction()...)
|
||||
}
|
||||
return hints
|
||||
}
|
||||
68
vendor/gopkg.in/alecthomas/kingpin.v2/doc.go
generated
vendored
Normal file
68
vendor/gopkg.in/alecthomas/kingpin.v2/doc.go
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
// Package kingpin provides command line interfaces like this:
|
||||
//
|
||||
// $ chat
|
||||
// usage: chat [<flags>] <command> [<flags>] [<args> ...]
|
||||
//
|
||||
// Flags:
|
||||
// --debug enable debug mode
|
||||
// --help Show help.
|
||||
// --server=127.0.0.1 server address
|
||||
//
|
||||
// Commands:
|
||||
// help <command>
|
||||
// Show help for a command.
|
||||
//
|
||||
// post [<flags>] <channel>
|
||||
// Post a message to a channel.
|
||||
//
|
||||
// register <nick> <name>
|
||||
// Register a new user.
|
||||
//
|
||||
// $ chat help post
|
||||
// usage: chat [<flags>] post [<flags>] <channel> [<text>]
|
||||
//
|
||||
// Post a message to a channel.
|
||||
//
|
||||
// Flags:
|
||||
// --image=IMAGE image to post
|
||||
//
|
||||
// Args:
|
||||
// <channel> channel to post to
|
||||
// [<text>] text to post
|
||||
// $ chat post --image=~/Downloads/owls.jpg pics
|
||||
//
|
||||
// From code like this:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import "gopkg.in/alecthomas/kingpin.v2"
|
||||
//
|
||||
// var (
|
||||
// debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool()
|
||||
// serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP()
|
||||
//
|
||||
// register = kingpin.Command("register", "Register a new user.")
|
||||
// registerNick = register.Arg("nick", "nickname for user").Required().String()
|
||||
// registerName = register.Arg("name", "name of user").Required().String()
|
||||
//
|
||||
// post = kingpin.Command("post", "Post a message to a channel.")
|
||||
// postImage = post.Flag("image", "image to post").ExistingFile()
|
||||
// postChannel = post.Arg("channel", "channel to post to").Required().String()
|
||||
// postText = post.Arg("text", "text to post").String()
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// switch kingpin.Parse() {
|
||||
// // Register user
|
||||
// case "register":
|
||||
// println(*registerNick)
|
||||
//
|
||||
// // Post message
|
||||
// case "post":
|
||||
// if *postImage != nil {
|
||||
// }
|
||||
// if *postText != "" {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
package kingpin
|
||||
45
vendor/gopkg.in/alecthomas/kingpin.v2/envar.go
generated
vendored
Normal file
45
vendor/gopkg.in/alecthomas/kingpin.v2/envar.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var (
|
||||
envVarValuesSeparator = "\r?\n"
|
||||
envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$")
|
||||
envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator)
|
||||
)
|
||||
|
||||
type envarMixin struct {
|
||||
envar string
|
||||
noEnvar bool
|
||||
}
|
||||
|
||||
func (e *envarMixin) HasEnvarValue() bool {
|
||||
return e.GetEnvarValue() != ""
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetEnvarValue() string {
|
||||
if e.noEnvar || e.envar == "" {
|
||||
return ""
|
||||
}
|
||||
return os.Getenv(e.envar)
|
||||
}
|
||||
|
||||
func (e *envarMixin) GetSplitEnvarValue() []string {
|
||||
values := make([]string, 0)
|
||||
|
||||
envarValue := e.GetEnvarValue()
|
||||
if envarValue == "" {
|
||||
return values
|
||||
}
|
||||
|
||||
// Split by new line to extract multiple values, if any.
|
||||
trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "")
|
||||
for _, value := range envVarValuesSplitter.Split(trimmed, -1) {
|
||||
values = append(values, value)
|
||||
}
|
||||
|
||||
return values
|
||||
}
|
||||
308
vendor/gopkg.in/alecthomas/kingpin.v2/flags.go
generated
vendored
Normal file
308
vendor/gopkg.in/alecthomas/kingpin.v2/flags.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type flagGroup struct {
|
||||
short map[string]*FlagClause
|
||||
long map[string]*FlagClause
|
||||
flagOrder []*FlagClause
|
||||
}
|
||||
|
||||
func newFlagGroup() *flagGroup {
|
||||
return &flagGroup{
|
||||
short: map[string]*FlagClause{},
|
||||
long: map[string]*FlagClause{},
|
||||
}
|
||||
}
|
||||
|
||||
// GetFlag gets a flag definition.
|
||||
//
|
||||
// This allows existing flags to be modified after definition but before parsing. Useful for
|
||||
// modular applications.
|
||||
func (f *flagGroup) GetFlag(name string) *FlagClause {
|
||||
return f.long[name]
|
||||
}
|
||||
|
||||
// Flag defines a new flag with the given long name and help.
|
||||
func (f *flagGroup) Flag(name, help string) *FlagClause {
|
||||
flag := newFlag(name, help)
|
||||
f.long[name] = flag
|
||||
f.flagOrder = append(f.flagOrder, flag)
|
||||
return flag
|
||||
}
|
||||
|
||||
func (f *flagGroup) init(defaultEnvarPrefix string) error {
|
||||
if err := f.checkDuplicates(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, flag := range f.long {
|
||||
if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" {
|
||||
flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name)
|
||||
}
|
||||
if err := flag.init(); err != nil {
|
||||
return err
|
||||
}
|
||||
if flag.shorthand != 0 {
|
||||
f.short[string(flag.shorthand)] = flag
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) checkDuplicates() error {
|
||||
seenShort := map[rune]bool{}
|
||||
seenLong := map[string]bool{}
|
||||
for _, flag := range f.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
if _, ok := seenShort[flag.shorthand]; ok {
|
||||
return fmt.Errorf("duplicate short flag -%c", flag.shorthand)
|
||||
}
|
||||
seenShort[flag.shorthand] = true
|
||||
}
|
||||
if _, ok := seenLong[flag.name]; ok {
|
||||
return fmt.Errorf("duplicate long flag --%s", flag.name)
|
||||
}
|
||||
seenLong[flag.name] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) {
|
||||
var token *Token
|
||||
|
||||
loop:
|
||||
for {
|
||||
token = context.Peek()
|
||||
switch token.Type {
|
||||
case TokenEOL:
|
||||
break loop
|
||||
|
||||
case TokenLong, TokenShort:
|
||||
flagToken := token
|
||||
defaultValue := ""
|
||||
var flag *FlagClause
|
||||
var ok bool
|
||||
invert := false
|
||||
|
||||
name := token.Value
|
||||
if token.Type == TokenLong {
|
||||
flag, ok = f.long[name]
|
||||
if !ok {
|
||||
if strings.HasPrefix(name, "no-") {
|
||||
name = name[3:]
|
||||
invert = true
|
||||
}
|
||||
flag, ok = f.long[name]
|
||||
}
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||
}
|
||||
} else {
|
||||
flag, ok = f.short[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown short flag '%s'", flagToken)
|
||||
}
|
||||
}
|
||||
|
||||
context.Next()
|
||||
|
||||
fb, ok := flag.value.(boolFlag)
|
||||
if ok && fb.IsBoolFlag() {
|
||||
if invert {
|
||||
defaultValue = "false"
|
||||
} else {
|
||||
defaultValue = "true"
|
||||
}
|
||||
} else {
|
||||
if invert {
|
||||
context.Push(token)
|
||||
return nil, fmt.Errorf("unknown long flag '%s'", flagToken)
|
||||
}
|
||||
token = context.Peek()
|
||||
if token.Type != TokenArg {
|
||||
context.Push(token)
|
||||
return nil, fmt.Errorf("expected argument for flag '%s'", flagToken)
|
||||
}
|
||||
context.Next()
|
||||
defaultValue = token.Value
|
||||
}
|
||||
|
||||
context.matchedFlag(flag, defaultValue)
|
||||
return flag, nil
|
||||
|
||||
default:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// FlagClause is a fluid interface used to build flags.
|
||||
type FlagClause struct {
|
||||
parserMixin
|
||||
actionMixin
|
||||
completionsMixin
|
||||
envarMixin
|
||||
name string
|
||||
shorthand rune
|
||||
help string
|
||||
defaultValues []string
|
||||
placeholder string
|
||||
hidden bool
|
||||
}
|
||||
|
||||
func newFlag(name, help string) *FlagClause {
|
||||
f := &FlagClause{
|
||||
name: name,
|
||||
help: help,
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FlagClause) setDefault() error {
|
||||
if f.HasEnvarValue() {
|
||||
if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() {
|
||||
// Use the value as-is
|
||||
return f.value.Set(f.GetEnvarValue())
|
||||
} else {
|
||||
for _, value := range f.GetSplitEnvarValue() {
|
||||
if err := f.value.Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(f.defaultValues) > 0 {
|
||||
for _, defaultValue := range f.defaultValues {
|
||||
if err := f.value.Set(defaultValue); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlagClause) needsValue() bool {
|
||||
haveDefault := len(f.defaultValues) > 0
|
||||
return f.required && !(haveDefault || f.HasEnvarValue())
|
||||
}
|
||||
|
||||
func (f *FlagClause) init() error {
|
||||
if f.required && len(f.defaultValues) > 0 {
|
||||
return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name)
|
||||
}
|
||||
if f.value == nil {
|
||||
return fmt.Errorf("no type defined for --%s (eg. .String())", f.name)
|
||||
}
|
||||
if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 {
|
||||
return fmt.Errorf("invalid default for '--%s', expecting single value", f.name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispatch to the given function after the flag is parsed and validated.
|
||||
func (f *FlagClause) Action(action Action) *FlagClause {
|
||||
f.addAction(action)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FlagClause) PreAction(action Action) *FlagClause {
|
||||
f.addPreAction(action)
|
||||
return f
|
||||
}
|
||||
|
||||
// HintAction registers a HintAction (function) for the flag to provide completions
|
||||
func (a *FlagClause) HintAction(action HintAction) *FlagClause {
|
||||
a.addHintAction(action)
|
||||
return a
|
||||
}
|
||||
|
||||
// HintOptions registers any number of options for the flag to provide completions
|
||||
func (a *FlagClause) HintOptions(options ...string) *FlagClause {
|
||||
a.addHintAction(func() []string {
|
||||
return options
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *FlagClause) EnumVar(target *string, options ...string) {
|
||||
a.parserMixin.EnumVar(target, options...)
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
}
|
||||
|
||||
func (a *FlagClause) Enum(options ...string) (target *string) {
|
||||
a.addHintActionBuiltin(func() []string {
|
||||
return options
|
||||
})
|
||||
return a.parserMixin.Enum(options...)
|
||||
}
|
||||
|
||||
// Default values for this flag. They *must* be parseable by the value of the flag.
|
||||
func (f *FlagClause) Default(values ...string) *FlagClause {
|
||||
f.defaultValues = values
|
||||
return f
|
||||
}
|
||||
|
||||
// DEPRECATED: Use Envar(name) instead.
|
||||
func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause {
|
||||
return f.Envar(envar)
|
||||
}
|
||||
|
||||
// Envar overrides the default value(s) for a flag from an environment variable,
|
||||
// if it is set. Several default values can be provided by using new lines to
|
||||
// separate them.
|
||||
func (f *FlagClause) Envar(name string) *FlagClause {
|
||||
f.envar = name
|
||||
f.noEnvar = false
|
||||
return f
|
||||
}
|
||||
|
||||
// NoEnvar forces environment variable defaults to be disabled for this flag.
|
||||
// Most useful in conjunction with app.DefaultEnvars().
|
||||
func (f *FlagClause) NoEnvar() *FlagClause {
|
||||
f.envar = ""
|
||||
f.noEnvar = true
|
||||
return f
|
||||
}
|
||||
|
||||
// PlaceHolder sets the place-holder string used for flag values in the help. The
|
||||
// default behaviour is to use the value provided by Default() if provided,
|
||||
// then fall back on the capitalized flag name.
|
||||
func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause {
|
||||
f.placeholder = placeholder
|
||||
return f
|
||||
}
|
||||
|
||||
// Hidden hides a flag from usage but still allows it to be used.
|
||||
func (f *FlagClause) Hidden() *FlagClause {
|
||||
f.hidden = true
|
||||
return f
|
||||
}
|
||||
|
||||
// Required makes the flag required. You can not provide a Default() value to a Required() flag.
|
||||
func (f *FlagClause) Required() *FlagClause {
|
||||
f.required = true
|
||||
return f
|
||||
}
|
||||
|
||||
// Short sets the short flag name.
|
||||
func (f *FlagClause) Short(name rune) *FlagClause {
|
||||
f.shorthand = name
|
||||
return f
|
||||
}
|
||||
|
||||
// Bool makes this flag a boolean flag.
|
||||
func (f *FlagClause) Bool() (target *bool) {
|
||||
target = new(bool)
|
||||
f.SetValue(newBoolValue(target))
|
||||
return
|
||||
}
|
||||
94
vendor/gopkg.in/alecthomas/kingpin.v2/global.go
generated
vendored
Normal file
94
vendor/gopkg.in/alecthomas/kingpin.v2/global.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
// CommandLine is the default Kingpin parser.
|
||||
CommandLine = New(filepath.Base(os.Args[0]), "")
|
||||
// Global help flag. Exposed for user customisation.
|
||||
HelpFlag = CommandLine.HelpFlag
|
||||
// Top-level help command. Exposed for user customisation. May be nil.
|
||||
HelpCommand = CommandLine.HelpCommand
|
||||
// Global version flag. Exposed for user customisation. May be nil.
|
||||
VersionFlag = CommandLine.VersionFlag
|
||||
)
|
||||
|
||||
// Command adds a new command to the default parser.
|
||||
func Command(name, help string) *CmdClause {
|
||||
return CommandLine.Command(name, help)
|
||||
}
|
||||
|
||||
// Flag adds a new flag to the default parser.
|
||||
func Flag(name, help string) *FlagClause {
|
||||
return CommandLine.Flag(name, help)
|
||||
}
|
||||
|
||||
// Arg adds a new argument to the top-level of the default parser.
|
||||
func Arg(name, help string) *ArgClause {
|
||||
return CommandLine.Arg(name, help)
|
||||
}
|
||||
|
||||
// Parse and return the selected command. Will call the termination handler if
|
||||
// an error is encountered.
|
||||
func Parse() string {
|
||||
selected := MustParse(CommandLine.Parse(os.Args[1:]))
|
||||
if selected == "" && CommandLine.cmdGroup.have() {
|
||||
Usage()
|
||||
CommandLine.terminate(0)
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
// Errorf prints an error message to stderr.
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
CommandLine.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Fatalf prints an error message to stderr and exits.
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
CommandLine.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
// FatalIfError prints an error and exits if err is not nil. The error is printed
|
||||
// with the given prefix.
|
||||
func FatalIfError(err error, format string, args ...interface{}) {
|
||||
CommandLine.FatalIfError(err, format, args...)
|
||||
}
|
||||
|
||||
// FatalUsage prints an error message followed by usage information, then
|
||||
// exits with a non-zero status.
|
||||
func FatalUsage(format string, args ...interface{}) {
|
||||
CommandLine.FatalUsage(format, args...)
|
||||
}
|
||||
|
||||
// FatalUsageContext writes a printf formatted error message to stderr, then
|
||||
// usage information for the given ParseContext, before exiting.
|
||||
func FatalUsageContext(context *ParseContext, format string, args ...interface{}) {
|
||||
CommandLine.FatalUsageContext(context, format, args...)
|
||||
}
|
||||
|
||||
// Usage prints usage to stderr.
|
||||
func Usage() {
|
||||
CommandLine.Usage(os.Args[1:])
|
||||
}
|
||||
|
||||
// Set global usage template to use (defaults to DefaultUsageTemplate).
|
||||
func UsageTemplate(template string) *Application {
|
||||
return CommandLine.UsageTemplate(template)
|
||||
}
|
||||
|
||||
// MustParse can be used with app.Parse(args) to exit with an error if parsing fails.
|
||||
func MustParse(command string, err error) string {
|
||||
if err != nil {
|
||||
Fatalf("%s, try --help", err)
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// Version adds a flag for displaying the application version number.
|
||||
func Version(version string) *Application {
|
||||
return CommandLine.Version(version)
|
||||
}
|
||||
9
vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth.go
generated
vendored
Normal file
9
vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth.go
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
import "io"
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
return 80
|
||||
}
|
||||
38
vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth_unix.go
generated
vendored
Normal file
38
vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth_unix.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
|
||||
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
// check if COLUMNS env is set to comply with
|
||||
// http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html
|
||||
colsStr := os.Getenv("COLUMNS")
|
||||
if colsStr != "" {
|
||||
if cols, err := strconv.Atoi(colsStr); err == nil {
|
||||
return cols
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := w.(*os.File); ok {
|
||||
fd := t.Fd()
|
||||
var dimensions [4]uint16
|
||||
|
||||
if _, _, err := syscall.Syscall6(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(syscall.TIOCGWINSZ),
|
||||
uintptr(unsafe.Pointer(&dimensions)),
|
||||
0, 0, 0,
|
||||
); err == 0 {
|
||||
return int(dimensions[1])
|
||||
}
|
||||
}
|
||||
return 80
|
||||
}
|
||||
227
vendor/gopkg.in/alecthomas/kingpin.v2/model.go
generated
vendored
Normal file
227
vendor/gopkg.in/alecthomas/kingpin.v2/model.go
generated
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Data model for Kingpin command-line structure.
|
||||
|
||||
type FlagGroupModel struct {
|
||||
Flags []*FlagModel
|
||||
}
|
||||
|
||||
func (f *FlagGroupModel) FlagSummary() string {
|
||||
out := []string{}
|
||||
count := 0
|
||||
for _, flag := range f.Flags {
|
||||
if flag.Name != "help" {
|
||||
count++
|
||||
}
|
||||
if flag.Required {
|
||||
if flag.IsBoolFlag() {
|
||||
out = append(out, fmt.Sprintf("--[no-]%s", flag.Name))
|
||||
} else {
|
||||
out = append(out, fmt.Sprintf("--%s=%s", flag.Name, flag.FormatPlaceHolder()))
|
||||
}
|
||||
}
|
||||
}
|
||||
if count != len(out) {
|
||||
out = append(out, "[<flags>]")
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
type FlagModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Short rune
|
||||
Default []string
|
||||
Envar string
|
||||
PlaceHolder string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Value Value
|
||||
}
|
||||
|
||||
func (f *FlagModel) String() string {
|
||||
return f.Value.String()
|
||||
}
|
||||
|
||||
func (f *FlagModel) IsBoolFlag() bool {
|
||||
if fl, ok := f.Value.(boolFlag); ok {
|
||||
return fl.IsBoolFlag()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FlagModel) FormatPlaceHolder() string {
|
||||
if f.PlaceHolder != "" {
|
||||
return f.PlaceHolder
|
||||
}
|
||||
if len(f.Default) > 0 {
|
||||
ellipsis := ""
|
||||
if len(f.Default) > 1 {
|
||||
ellipsis = "..."
|
||||
}
|
||||
if _, ok := f.Value.(*stringValue); ok {
|
||||
return strconv.Quote(f.Default[0]) + ellipsis
|
||||
}
|
||||
return f.Default[0] + ellipsis
|
||||
}
|
||||
return strings.ToUpper(f.Name)
|
||||
}
|
||||
|
||||
type ArgGroupModel struct {
|
||||
Args []*ArgModel
|
||||
}
|
||||
|
||||
func (a *ArgGroupModel) ArgSummary() string {
|
||||
depth := 0
|
||||
out := []string{}
|
||||
for _, arg := range a.Args {
|
||||
h := "<" + arg.Name + ">"
|
||||
if !arg.Required {
|
||||
h = "[" + h
|
||||
depth++
|
||||
}
|
||||
out = append(out, h)
|
||||
}
|
||||
out[len(out)-1] = out[len(out)-1] + strings.Repeat("]", depth)
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
type ArgModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Default []string
|
||||
Envar string
|
||||
Required bool
|
||||
Value Value
|
||||
}
|
||||
|
||||
func (a *ArgModel) String() string {
|
||||
return a.Value.String()
|
||||
}
|
||||
|
||||
type CmdGroupModel struct {
|
||||
Commands []*CmdModel
|
||||
}
|
||||
|
||||
func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) {
|
||||
for _, cmd := range c.Commands {
|
||||
if len(cmd.Commands) == 0 {
|
||||
out = append(out, cmd)
|
||||
}
|
||||
out = append(out, cmd.FlattenedCommands()...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type CmdModel struct {
|
||||
Name string
|
||||
Aliases []string
|
||||
Help string
|
||||
FullCommand string
|
||||
Depth int
|
||||
Hidden bool
|
||||
Default bool
|
||||
*FlagGroupModel
|
||||
*ArgGroupModel
|
||||
*CmdGroupModel
|
||||
}
|
||||
|
||||
func (c *CmdModel) String() string {
|
||||
return c.FullCommand
|
||||
}
|
||||
|
||||
type ApplicationModel struct {
|
||||
Name string
|
||||
Help string
|
||||
Version string
|
||||
Author string
|
||||
*ArgGroupModel
|
||||
*CmdGroupModel
|
||||
*FlagGroupModel
|
||||
}
|
||||
|
||||
func (a *Application) Model() *ApplicationModel {
|
||||
return &ApplicationModel{
|
||||
Name: a.Name,
|
||||
Help: a.Help,
|
||||
Version: a.version,
|
||||
Author: a.author,
|
||||
FlagGroupModel: a.flagGroup.Model(),
|
||||
ArgGroupModel: a.argGroup.Model(),
|
||||
CmdGroupModel: a.cmdGroup.Model(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *argGroup) Model() *ArgGroupModel {
|
||||
m := &ArgGroupModel{}
|
||||
for _, arg := range a.args {
|
||||
m.Args = append(m.Args, arg.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (a *ArgClause) Model() *ArgModel {
|
||||
return &ArgModel{
|
||||
Name: a.name,
|
||||
Help: a.help,
|
||||
Default: a.defaultValues,
|
||||
Envar: a.envar,
|
||||
Required: a.required,
|
||||
Value: a.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *flagGroup) Model() *FlagGroupModel {
|
||||
m := &FlagGroupModel{}
|
||||
for _, fl := range f.flagOrder {
|
||||
m.Flags = append(m.Flags, fl.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (f *FlagClause) Model() *FlagModel {
|
||||
return &FlagModel{
|
||||
Name: f.name,
|
||||
Help: f.help,
|
||||
Short: rune(f.shorthand),
|
||||
Default: f.defaultValues,
|
||||
Envar: f.envar,
|
||||
PlaceHolder: f.placeholder,
|
||||
Required: f.required,
|
||||
Hidden: f.hidden,
|
||||
Value: f.value,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cmdGroup) Model() *CmdGroupModel {
|
||||
m := &CmdGroupModel{}
|
||||
for _, cm := range c.commandOrder {
|
||||
m.Commands = append(m.Commands, cm.Model())
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *CmdClause) Model() *CmdModel {
|
||||
depth := 0
|
||||
for i := c; i != nil; i = i.parent {
|
||||
depth++
|
||||
}
|
||||
return &CmdModel{
|
||||
Name: c.name,
|
||||
Aliases: c.aliases,
|
||||
Help: c.help,
|
||||
Depth: depth,
|
||||
Hidden: c.hidden,
|
||||
Default: c.isDefault,
|
||||
FullCommand: c.FullCommand(),
|
||||
FlagGroupModel: c.flagGroup.Model(),
|
||||
ArgGroupModel: c.argGroup.Model(),
|
||||
CmdGroupModel: c.cmdGroup.Model(),
|
||||
}
|
||||
}
|
||||
396
vendor/gopkg.in/alecthomas/kingpin.v2/parser.go
generated
vendored
Normal file
396
vendor/gopkg.in/alecthomas/kingpin.v2/parser.go
generated
vendored
Normal file
@@ -0,0 +1,396 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type TokenType int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
TokenShort TokenType = iota
|
||||
TokenLong
|
||||
TokenArg
|
||||
TokenError
|
||||
TokenEOL
|
||||
)
|
||||
|
||||
func (t TokenType) String() string {
|
||||
switch t {
|
||||
case TokenShort:
|
||||
return "short flag"
|
||||
case TokenLong:
|
||||
return "long flag"
|
||||
case TokenArg:
|
||||
return "argument"
|
||||
case TokenError:
|
||||
return "error"
|
||||
case TokenEOL:
|
||||
return "<EOL>"
|
||||
}
|
||||
return "?"
|
||||
}
|
||||
|
||||
var (
|
||||
TokenEOLMarker = Token{-1, TokenEOL, ""}
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Index int
|
||||
Type TokenType
|
||||
Value string
|
||||
}
|
||||
|
||||
func (t *Token) Equal(o *Token) bool {
|
||||
return t.Index == o.Index
|
||||
}
|
||||
|
||||
func (t *Token) IsFlag() bool {
|
||||
return t.Type == TokenShort || t.Type == TokenLong
|
||||
}
|
||||
|
||||
func (t *Token) IsEOF() bool {
|
||||
return t.Type == TokenEOL
|
||||
}
|
||||
|
||||
func (t *Token) String() string {
|
||||
switch t.Type {
|
||||
case TokenShort:
|
||||
return "-" + t.Value
|
||||
case TokenLong:
|
||||
return "--" + t.Value
|
||||
case TokenArg:
|
||||
return t.Value
|
||||
case TokenError:
|
||||
return "error: " + t.Value
|
||||
case TokenEOL:
|
||||
return "<EOL>"
|
||||
default:
|
||||
panic("unhandled type")
|
||||
}
|
||||
}
|
||||
|
||||
// A union of possible elements in a parse stack.
|
||||
type ParseElement struct {
|
||||
// Clause is either *CmdClause, *ArgClause or *FlagClause.
|
||||
Clause interface{}
|
||||
// Value is corresponding value for an ArgClause or FlagClause (if any).
|
||||
Value *string
|
||||
}
|
||||
|
||||
// ParseContext holds the current context of the parser. When passed to
|
||||
// Action() callbacks Elements will be fully populated with *FlagClause,
|
||||
// *ArgClause and *CmdClause values and their corresponding arguments (if
|
||||
// any).
|
||||
type ParseContext struct {
|
||||
SelectedCommand *CmdClause
|
||||
ignoreDefault bool
|
||||
argsOnly bool
|
||||
peek []*Token
|
||||
argi int // Index of current command-line arg we're processing.
|
||||
args []string
|
||||
rawArgs []string
|
||||
flags *flagGroup
|
||||
arguments *argGroup
|
||||
argumenti int // Cursor into arguments
|
||||
// Flags, arguments and commands encountered and collected during parse.
|
||||
Elements []*ParseElement
|
||||
}
|
||||
|
||||
func (p *ParseContext) nextArg() *ArgClause {
|
||||
if p.argumenti >= len(p.arguments.args) {
|
||||
return nil
|
||||
}
|
||||
arg := p.arguments.args[p.argumenti]
|
||||
if !arg.consumesRemainder() {
|
||||
p.argumenti++
|
||||
}
|
||||
return arg
|
||||
}
|
||||
|
||||
func (p *ParseContext) next() {
|
||||
p.argi++
|
||||
p.args = p.args[1:]
|
||||
}
|
||||
|
||||
// HasTrailingArgs returns true if there are unparsed command-line arguments.
|
||||
// This can occur if the parser can not match remaining arguments.
|
||||
func (p *ParseContext) HasTrailingArgs() bool {
|
||||
return len(p.args) > 0
|
||||
}
|
||||
|
||||
func tokenize(args []string, ignoreDefault bool) *ParseContext {
|
||||
return &ParseContext{
|
||||
ignoreDefault: ignoreDefault,
|
||||
args: args,
|
||||
rawArgs: args,
|
||||
flags: newFlagGroup(),
|
||||
arguments: newArgGroup(),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) mergeFlags(flags *flagGroup) {
|
||||
for _, flag := range flags.flagOrder {
|
||||
if flag.shorthand != 0 {
|
||||
p.flags.short[string(flag.shorthand)] = flag
|
||||
}
|
||||
p.flags.long[flag.name] = flag
|
||||
p.flags.flagOrder = append(p.flags.flagOrder, flag)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) mergeArgs(args *argGroup) {
|
||||
for _, arg := range args.args {
|
||||
p.arguments.args = append(p.arguments.args, arg)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ParseContext) EOL() bool {
|
||||
return p.Peek().Type == TokenEOL
|
||||
}
|
||||
|
||||
func (p *ParseContext) Error() bool {
|
||||
return p.Peek().Type == TokenError
|
||||
}
|
||||
|
||||
// Next token in the parse context.
|
||||
func (p *ParseContext) Next() *Token {
|
||||
if len(p.peek) > 0 {
|
||||
return p.pop()
|
||||
}
|
||||
|
||||
// End of tokens.
|
||||
if len(p.args) == 0 {
|
||||
return &Token{Index: p.argi, Type: TokenEOL}
|
||||
}
|
||||
|
||||
arg := p.args[0]
|
||||
p.next()
|
||||
|
||||
if p.argsOnly {
|
||||
return &Token{p.argi, TokenArg, arg}
|
||||
}
|
||||
|
||||
// All remaining args are passed directly.
|
||||
if arg == "--" {
|
||||
p.argsOnly = true
|
||||
return p.Next()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
parts := strings.SplitN(arg[2:], "=", 2)
|
||||
token := &Token{p.argi, TokenLong, parts[0]}
|
||||
if len(parts) == 2 {
|
||||
p.Push(&Token{p.argi, TokenArg, parts[1]})
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
if len(arg) == 1 {
|
||||
return &Token{Index: p.argi, Type: TokenShort}
|
||||
}
|
||||
shortRune, size := utf8.DecodeRuneInString(arg[1:])
|
||||
short := string(shortRune)
|
||||
flag, ok := p.flags.short[short]
|
||||
// Not a known short flag, we'll just return it anyway.
|
||||
if !ok {
|
||||
} else if fb, ok := flag.value.(boolFlag); ok && fb.IsBoolFlag() {
|
||||
// Bool short flag.
|
||||
} else {
|
||||
// Short flag with combined argument: -fARG
|
||||
token := &Token{p.argi, TokenShort, short}
|
||||
if len(arg) > size+1 {
|
||||
p.Push(&Token{p.argi, TokenArg, arg[size+1:]})
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
if len(arg) > size+1 {
|
||||
p.args = append([]string{"-" + arg[size+1:]}, p.args...)
|
||||
}
|
||||
return &Token{p.argi, TokenShort, short}
|
||||
} else if strings.HasPrefix(arg, "@") {
|
||||
expanded, err := ExpandArgsFromFile(arg[1:])
|
||||
if err != nil {
|
||||
return &Token{p.argi, TokenError, err.Error()}
|
||||
}
|
||||
if len(p.args) == 0 {
|
||||
p.args = expanded
|
||||
} else {
|
||||
p.args = append(expanded, p.args...)
|
||||
}
|
||||
return p.Next()
|
||||
}
|
||||
|
||||
return &Token{p.argi, TokenArg, arg}
|
||||
}
|
||||
|
||||
func (p *ParseContext) Peek() *Token {
|
||||
if len(p.peek) == 0 {
|
||||
return p.Push(p.Next())
|
||||
}
|
||||
return p.peek[len(p.peek)-1]
|
||||
}
|
||||
|
||||
func (p *ParseContext) Push(token *Token) *Token {
|
||||
p.peek = append(p.peek, token)
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *ParseContext) pop() *Token {
|
||||
end := len(p.peek) - 1
|
||||
token := p.peek[end]
|
||||
p.peek = p.peek[0:end]
|
||||
return token
|
||||
}
|
||||
|
||||
func (p *ParseContext) String() string {
|
||||
return p.SelectedCommand.FullCommand()
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedFlag(flag *FlagClause, value string) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: flag, Value: &value})
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedArg(arg *ArgClause, value string) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: arg, Value: &value})
|
||||
}
|
||||
|
||||
func (p *ParseContext) matchedCmd(cmd *CmdClause) {
|
||||
p.Elements = append(p.Elements, &ParseElement{Clause: cmd})
|
||||
p.mergeFlags(cmd.flagGroup)
|
||||
p.mergeArgs(cmd.argGroup)
|
||||
p.SelectedCommand = cmd
|
||||
}
|
||||
|
||||
// Expand arguments from a file. Lines starting with # will be treated as comments.
|
||||
func ExpandArgsFromFile(filename string) (out []string, err error) {
|
||||
if filename == "" {
|
||||
return nil, fmt.Errorf("expected @ file to expand arguments from")
|
||||
}
|
||||
r, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open arguments file %q: %s", filename, err)
|
||||
}
|
||||
defer r.Close()
|
||||
scanner := bufio.NewScanner(r)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
err = scanner.Err()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read arguments from %q: %s", filename, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parse(context *ParseContext, app *Application) (err error) {
|
||||
context.mergeFlags(app.flagGroup)
|
||||
context.mergeArgs(app.argGroup)
|
||||
|
||||
cmds := app.cmdGroup
|
||||
ignoreDefault := context.ignoreDefault
|
||||
|
||||
loop:
|
||||
for !context.EOL() && !context.Error() {
|
||||
token := context.Peek()
|
||||
|
||||
switch token.Type {
|
||||
case TokenLong, TokenShort:
|
||||
if flag, err := context.flags.parse(context); err != nil {
|
||||
if !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
} else if flag == HelpFlag {
|
||||
ignoreDefault = true
|
||||
}
|
||||
|
||||
case TokenArg:
|
||||
if cmds.have() {
|
||||
selectedDefault := false
|
||||
cmd, ok := cmds.commands[token.String()]
|
||||
if !ok {
|
||||
if !ignoreDefault {
|
||||
if cmd = cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
selectedDefault = true
|
||||
}
|
||||
}
|
||||
if cmd == nil {
|
||||
return fmt.Errorf("expected command but got %q", token)
|
||||
}
|
||||
}
|
||||
if cmd == HelpCommand {
|
||||
ignoreDefault = true
|
||||
}
|
||||
cmd.completionAlts = nil
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
if !selectedDefault {
|
||||
context.Next()
|
||||
}
|
||||
} else if context.arguments.have() {
|
||||
if app.noInterspersed {
|
||||
// no more flags
|
||||
context.argsOnly = true
|
||||
}
|
||||
arg := context.nextArg()
|
||||
if arg == nil {
|
||||
break loop
|
||||
}
|
||||
context.matchedArg(arg, token.String())
|
||||
context.Next()
|
||||
} else {
|
||||
break loop
|
||||
}
|
||||
|
||||
case TokenEOL:
|
||||
break loop
|
||||
}
|
||||
}
|
||||
|
||||
// Move to innermost default command.
|
||||
for !ignoreDefault {
|
||||
if cmd := cmds.defaultSubcommand(); cmd != nil {
|
||||
cmd.completionAlts = cmds.cmdNames()
|
||||
context.matchedCmd(cmd)
|
||||
cmds = cmd.cmdGroup
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if context.Error() {
|
||||
return fmt.Errorf("%s", context.Peek().Value)
|
||||
}
|
||||
|
||||
if !context.EOL() {
|
||||
return fmt.Errorf("unexpected %s", context.Peek())
|
||||
}
|
||||
|
||||
// Set defaults for all remaining args.
|
||||
for arg := context.nextArg(); arg != nil && !arg.consumesRemainder(); arg = context.nextArg() {
|
||||
for _, defaultValue := range arg.defaultValues {
|
||||
if err := arg.value.Set(defaultValue); err != nil {
|
||||
return fmt.Errorf("invalid default value '%s' for argument '%s'", defaultValue, arg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
212
vendor/gopkg.in/alecthomas/kingpin.v2/parsers.go
generated
vendored
Normal file
212
vendor/gopkg.in/alecthomas/kingpin.v2/parsers.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
type Settings interface {
|
||||
SetValue(value Value)
|
||||
}
|
||||
|
||||
type parserMixin struct {
|
||||
value Value
|
||||
required bool
|
||||
}
|
||||
|
||||
func (p *parserMixin) SetValue(value Value) {
|
||||
p.value = value
|
||||
}
|
||||
|
||||
// StringMap provides key=value parsing into a map.
|
||||
func (p *parserMixin) StringMap() (target *map[string]string) {
|
||||
target = &(map[string]string{})
|
||||
p.StringMapVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// Duration sets the parser to a time.Duration parser.
|
||||
func (p *parserMixin) Duration() (target *time.Duration) {
|
||||
target = new(time.Duration)
|
||||
p.DurationVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// Bytes parses numeric byte units. eg. 1.5KB
|
||||
func (p *parserMixin) Bytes() (target *units.Base2Bytes) {
|
||||
target = new(units.Base2Bytes)
|
||||
p.BytesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// IP sets the parser to a net.IP parser.
|
||||
func (p *parserMixin) IP() (target *net.IP) {
|
||||
target = new(net.IP)
|
||||
p.IPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// TCP (host:port) address.
|
||||
func (p *parserMixin) TCP() (target **net.TCPAddr) {
|
||||
target = new(*net.TCPAddr)
|
||||
p.TCPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// TCPVar (host:port) address.
|
||||
func (p *parserMixin) TCPVar(target **net.TCPAddr) {
|
||||
p.SetValue(newTCPAddrValue(target))
|
||||
}
|
||||
|
||||
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||
func (p *parserMixin) ExistingFile() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingFileVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingDir() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingDirVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// ExistingFileOrDir sets the parser to one that requires and returns an existing file OR directory.
|
||||
func (p *parserMixin) ExistingFileOrDir() (target *string) {
|
||||
target = new(string)
|
||||
p.ExistingFileOrDirVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// File returns an os.File against an existing file.
|
||||
func (p *parserMixin) File() (target **os.File) {
|
||||
target = new(*os.File)
|
||||
p.FileVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// File attempts to open a File with os.OpenFile(flag, perm).
|
||||
func (p *parserMixin) OpenFile(flag int, perm os.FileMode) (target **os.File) {
|
||||
target = new(*os.File)
|
||||
p.OpenFileVar(target, flag, perm)
|
||||
return
|
||||
}
|
||||
|
||||
// URL provides a valid, parsed url.URL.
|
||||
func (p *parserMixin) URL() (target **url.URL) {
|
||||
target = new(*url.URL)
|
||||
p.URLVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// StringMap provides key=value parsing into a map.
|
||||
func (p *parserMixin) StringMapVar(target *map[string]string) {
|
||||
p.SetValue(newStringMapValue(target))
|
||||
}
|
||||
|
||||
// Float sets the parser to a float64 parser.
|
||||
func (p *parserMixin) Float() (target *float64) {
|
||||
return p.Float64()
|
||||
}
|
||||
|
||||
// Float sets the parser to a float64 parser.
|
||||
func (p *parserMixin) FloatVar(target *float64) {
|
||||
p.Float64Var(target)
|
||||
}
|
||||
|
||||
// Duration sets the parser to a time.Duration parser.
|
||||
func (p *parserMixin) DurationVar(target *time.Duration) {
|
||||
p.SetValue(newDurationValue(target))
|
||||
}
|
||||
|
||||
// BytesVar parses numeric byte units. eg. 1.5KB
|
||||
func (p *parserMixin) BytesVar(target *units.Base2Bytes) {
|
||||
p.SetValue(newBytesValue(target))
|
||||
}
|
||||
|
||||
// IP sets the parser to a net.IP parser.
|
||||
func (p *parserMixin) IPVar(target *net.IP) {
|
||||
p.SetValue(newIPValue(target))
|
||||
}
|
||||
|
||||
// ExistingFile sets the parser to one that requires and returns an existing file.
|
||||
func (p *parserMixin) ExistingFileVar(target *string) {
|
||||
p.SetValue(newExistingFileValue(target))
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingDirVar(target *string) {
|
||||
p.SetValue(newExistingDirValue(target))
|
||||
}
|
||||
|
||||
// ExistingDir sets the parser to one that requires and returns an existing directory.
|
||||
func (p *parserMixin) ExistingFileOrDirVar(target *string) {
|
||||
p.SetValue(newExistingFileOrDirValue(target))
|
||||
}
|
||||
|
||||
// FileVar opens an existing file.
|
||||
func (p *parserMixin) FileVar(target **os.File) {
|
||||
p.SetValue(newFileValue(target, os.O_RDONLY, 0))
|
||||
}
|
||||
|
||||
// OpenFileVar calls os.OpenFile(flag, perm)
|
||||
func (p *parserMixin) OpenFileVar(target **os.File, flag int, perm os.FileMode) {
|
||||
p.SetValue(newFileValue(target, flag, perm))
|
||||
}
|
||||
|
||||
// URL provides a valid, parsed url.URL.
|
||||
func (p *parserMixin) URLVar(target **url.URL) {
|
||||
p.SetValue(newURLValue(target))
|
||||
}
|
||||
|
||||
// URLList provides a parsed list of url.URL values.
|
||||
func (p *parserMixin) URLList() (target *[]*url.URL) {
|
||||
target = new([]*url.URL)
|
||||
p.URLListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
// URLListVar provides a parsed list of url.URL values.
|
||||
func (p *parserMixin) URLListVar(target *[]*url.URL) {
|
||||
p.SetValue(newURLListValue(target))
|
||||
}
|
||||
|
||||
// Enum allows a value from a set of options.
|
||||
func (p *parserMixin) Enum(options ...string) (target *string) {
|
||||
target = new(string)
|
||||
p.EnumVar(target, options...)
|
||||
return
|
||||
}
|
||||
|
||||
// EnumVar allows a value from a set of options.
|
||||
func (p *parserMixin) EnumVar(target *string, options ...string) {
|
||||
p.SetValue(newEnumFlag(target, options...))
|
||||
}
|
||||
|
||||
// Enums allows a set of values from a set of options.
|
||||
func (p *parserMixin) Enums(options ...string) (target *[]string) {
|
||||
target = new([]string)
|
||||
p.EnumsVar(target, options...)
|
||||
return
|
||||
}
|
||||
|
||||
// EnumVar allows a value from a set of options.
|
||||
func (p *parserMixin) EnumsVar(target *[]string, options ...string) {
|
||||
p.SetValue(newEnumsFlag(target, options...))
|
||||
}
|
||||
|
||||
// A Counter increments a number each time it is encountered.
|
||||
func (p *parserMixin) Counter() (target *int) {
|
||||
target = new(int)
|
||||
p.CounterVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) CounterVar(target *int) {
|
||||
p.SetValue(newCounterValue(target))
|
||||
}
|
||||
262
vendor/gopkg.in/alecthomas/kingpin.v2/templates.go
generated
vendored
Normal file
262
vendor/gopkg.in/alecthomas/kingpin.v2/templates.go
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
package kingpin
|
||||
|
||||
// Default usage template.
|
||||
var DefaultUsageTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
|
||||
{{if .Context.SelectedCommand}}\
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else}}\
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end}}\
|
||||
{{if .Context.Flags}}\
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
{{if len .Context.SelectedCommand.Commands}}\
|
||||
Subcommands:
|
||||
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||
{{end}}\
|
||||
{{else if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
// Usage template where command's optional flags are listed separately
|
||||
var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else}}\
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end}}\
|
||||
|
||||
{{if .Context.Flags|RequiredFlags}}\
|
||||
Required flags:
|
||||
{{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Flags|OptionalFlags}}\
|
||||
Optional flags:
|
||||
{{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
Subcommands:
|
||||
{{if .Context.SelectedCommand.Commands}}\
|
||||
{{template "FormatCommands" .Context.SelectedCommand}}
|
||||
{{end}}\
|
||||
{{else if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
// Usage template with compactly formatted commands.
|
||||
var CompactUsageTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommandList"}}\
|
||||
{{range .}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||
{{end}}\
|
||||
{{template "FormatCommandList" .Commands}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
|
||||
{{if .Context.SelectedCommand}}\
|
||||
usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}}
|
||||
{{else}}\
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{end}}\
|
||||
{{if .Context.Flags}}\
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.SelectedCommand}}\
|
||||
{{if .Context.SelectedCommand.Commands}}\
|
||||
Commands:
|
||||
{{.Context.SelectedCommand}}
|
||||
{{template "FormatCommandList" .Context.SelectedCommand.Commands}}
|
||||
{{end}}\
|
||||
{{else if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommandList" .App.Commands}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
var ManPageTemplate = `{{define "FormatFlags"}}\
|
||||
{{range .Flags}}\
|
||||
{{if not .Hidden}}\
|
||||
.TP
|
||||
\fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end}}\\fR
|
||||
{{.Help}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}{{if .Default}}*{{end}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
.SS
|
||||
\fB{{.FullCommand}}{{template "FormatCommand" .}}\\fR
|
||||
.PP
|
||||
{{.Help}}
|
||||
{{template "FormatFlags" .}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}\\fR
|
||||
{{end}}\
|
||||
|
||||
.TH {{.App.Name}} 1 {{.App.Version}} "{{.App.Author}}"
|
||||
.SH "NAME"
|
||||
{{.App.Name}}
|
||||
.SH "SYNOPSIS"
|
||||
.TP
|
||||
\fB{{.App.Name}}{{template "FormatUsage" .App}}
|
||||
.SH "DESCRIPTION"
|
||||
{{.App.Help}}
|
||||
.SH "OPTIONS"
|
||||
{{template "FormatFlags" .App}}\
|
||||
{{if .App.Commands}}\
|
||||
.SH "COMMANDS"
|
||||
{{template "FormatCommands" .App}}\
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
// Default usage template.
|
||||
var LongHelpTemplate = `{{define "FormatCommand"}}\
|
||||
{{if .FlagSummary}} {{.FlagSummary}}{{end}}\
|
||||
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatCommands"}}\
|
||||
{{range .FlattenedCommands}}\
|
||||
{{if not .Hidden}}\
|
||||
{{.FullCommand}}{{template "FormatCommand" .}}
|
||||
{{.Help|Wrap 4}}
|
||||
{{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}}
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
{{end}}\
|
||||
|
||||
{{define "FormatUsage"}}\
|
||||
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||
{{if .Help}}
|
||||
{{.Help|Wrap 0}}\
|
||||
{{end}}\
|
||||
|
||||
{{end}}\
|
||||
|
||||
usage: {{.App.Name}}{{template "FormatUsage" .App}}
|
||||
{{if .Context.Flags}}\
|
||||
Flags:
|
||||
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .Context.Args}}\
|
||||
Args:
|
||||
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||
{{end}}\
|
||||
{{if .App.Commands}}\
|
||||
Commands:
|
||||
{{template "FormatCommands" .App}}
|
||||
{{end}}\
|
||||
`
|
||||
|
||||
var BashCompletionTemplate = `
|
||||
_{{.App.Name}}_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||
|
||||
`
|
||||
|
||||
var ZshCompletionTemplate = `
|
||||
#compdef {{.App.Name}}
|
||||
autoload -U compinit && compinit
|
||||
autoload -U bashcompinit && bashcompinit
|
||||
|
||||
_{{.App.Name}}_bash_autocomplete() {
|
||||
local cur prev opts base
|
||||
COMPREPLY=()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} )
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
|
||||
return 0
|
||||
}
|
||||
complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}}
|
||||
`
|
||||
211
vendor/gopkg.in/alecthomas/kingpin.v2/usage.go
generated
vendored
Normal file
211
vendor/gopkg.in/alecthomas/kingpin.v2/usage.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/doc"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/template"
|
||||
)
|
||||
|
||||
var (
|
||||
preIndent = " "
|
||||
)
|
||||
|
||||
func formatTwoColumns(w io.Writer, indent, padding, width int, rows [][2]string) {
|
||||
// Find size of first column.
|
||||
s := 0
|
||||
for _, row := range rows {
|
||||
if c := len(row[0]); c > s && c < 30 {
|
||||
s = c
|
||||
}
|
||||
}
|
||||
|
||||
indentStr := strings.Repeat(" ", indent)
|
||||
offsetStr := strings.Repeat(" ", s+padding)
|
||||
|
||||
for _, row := range rows {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
doc.ToText(buf, row[1], "", preIndent, width-s-padding-indent)
|
||||
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
|
||||
fmt.Fprintf(w, "%s%-*s%*s", indentStr, s, row[0], padding, "")
|
||||
if len(row[0]) >= 30 {
|
||||
fmt.Fprintf(w, "\n%s%s", indentStr, offsetStr)
|
||||
}
|
||||
fmt.Fprintf(w, "%s\n", lines[0])
|
||||
for _, line := range lines[1:] {
|
||||
fmt.Fprintf(w, "%s%s%s\n", indentStr, offsetStr, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Usage writes application usage to w. It parses args to determine
|
||||
// appropriate help context, such as which command to show help for.
|
||||
func (a *Application) Usage(args []string) {
|
||||
context, err := a.parseContext(true, args)
|
||||
a.FatalIfError(err, "")
|
||||
if err := a.UsageForContextWithTemplate(context, 2, a.usageTemplate); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func formatAppUsage(app *ApplicationModel) string {
|
||||
s := []string{app.Name}
|
||||
if len(app.Flags) > 0 {
|
||||
s = append(s, app.FlagSummary())
|
||||
}
|
||||
if len(app.Args) > 0 {
|
||||
s = append(s, app.ArgSummary())
|
||||
}
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
||||
func formatCmdUsage(app *ApplicationModel, cmd *CmdModel) string {
|
||||
s := []string{app.Name, cmd.String()}
|
||||
if len(app.Flags) > 0 {
|
||||
s = append(s, app.FlagSummary())
|
||||
}
|
||||
if len(app.Args) > 0 {
|
||||
s = append(s, app.ArgSummary())
|
||||
}
|
||||
return strings.Join(s, " ")
|
||||
}
|
||||
|
||||
func formatFlag(haveShort bool, flag *FlagModel) string {
|
||||
flagString := ""
|
||||
if flag.Short != 0 {
|
||||
flagString += fmt.Sprintf("-%c, --%s", flag.Short, flag.Name)
|
||||
} else {
|
||||
if haveShort {
|
||||
flagString += fmt.Sprintf(" --%s", flag.Name)
|
||||
} else {
|
||||
flagString += fmt.Sprintf("--%s", flag.Name)
|
||||
}
|
||||
}
|
||||
if !flag.IsBoolFlag() {
|
||||
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
|
||||
}
|
||||
if v, ok := flag.Value.(repeatableFlag); ok && v.IsCumulative() {
|
||||
flagString += " ..."
|
||||
}
|
||||
return flagString
|
||||
}
|
||||
|
||||
type templateParseContext struct {
|
||||
SelectedCommand *CmdModel
|
||||
*FlagGroupModel
|
||||
*ArgGroupModel
|
||||
}
|
||||
|
||||
type templateContext struct {
|
||||
App *ApplicationModel
|
||||
Width int
|
||||
Context *templateParseContext
|
||||
}
|
||||
|
||||
// UsageForContext displays usage information from a ParseContext (obtained from
|
||||
// Application.ParseContext() or Action(f) callbacks).
|
||||
func (a *Application) UsageForContext(context *ParseContext) error {
|
||||
return a.UsageForContextWithTemplate(context, 2, a.usageTemplate)
|
||||
}
|
||||
|
||||
// UsageForContextWithTemplate is the base usage function. You generally don't need to use this.
|
||||
func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent int, tmpl string) error {
|
||||
width := guessWidth(a.usageWriter)
|
||||
funcs := template.FuncMap{
|
||||
"Indent": func(level int) string {
|
||||
return strings.Repeat(" ", level*indent)
|
||||
},
|
||||
"Wrap": func(indent int, s string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
indentText := strings.Repeat(" ", indent)
|
||||
doc.ToText(buf, s, indentText, " "+indentText, width-indent)
|
||||
return buf.String()
|
||||
},
|
||||
"FormatFlag": formatFlag,
|
||||
"FlagsToTwoColumns": func(f []*FlagModel) [][2]string {
|
||||
rows := [][2]string{}
|
||||
haveShort := false
|
||||
for _, flag := range f {
|
||||
if flag.Short != 0 {
|
||||
haveShort = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, flag := range f {
|
||||
if !flag.Hidden {
|
||||
rows = append(rows, [2]string{formatFlag(haveShort, flag), flag.Help})
|
||||
}
|
||||
}
|
||||
return rows
|
||||
},
|
||||
"RequiredFlags": func(f []*FlagModel) []*FlagModel {
|
||||
requiredFlags := []*FlagModel{}
|
||||
for _, flag := range f {
|
||||
if flag.Required {
|
||||
requiredFlags = append(requiredFlags, flag)
|
||||
}
|
||||
}
|
||||
return requiredFlags
|
||||
},
|
||||
"OptionalFlags": func(f []*FlagModel) []*FlagModel {
|
||||
optionalFlags := []*FlagModel{}
|
||||
for _, flag := range f {
|
||||
if !flag.Required {
|
||||
optionalFlags = append(optionalFlags, flag)
|
||||
}
|
||||
}
|
||||
return optionalFlags
|
||||
},
|
||||
"ArgsToTwoColumns": func(a []*ArgModel) [][2]string {
|
||||
rows := [][2]string{}
|
||||
for _, arg := range a {
|
||||
s := "<" + arg.Name + ">"
|
||||
if !arg.Required {
|
||||
s = "[" + s + "]"
|
||||
}
|
||||
rows = append(rows, [2]string{s, arg.Help})
|
||||
}
|
||||
return rows
|
||||
},
|
||||
"FormatTwoColumns": func(rows [][2]string) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
formatTwoColumns(buf, indent, indent, width, rows)
|
||||
return buf.String()
|
||||
},
|
||||
"FormatTwoColumnsWithIndent": func(rows [][2]string, indent, padding int) string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
formatTwoColumns(buf, indent, padding, width, rows)
|
||||
return buf.String()
|
||||
},
|
||||
"FormatAppUsage": formatAppUsage,
|
||||
"FormatCommandUsage": formatCmdUsage,
|
||||
"IsCumulative": func(value Value) bool {
|
||||
r, ok := value.(remainderArg)
|
||||
return ok && r.IsCumulative()
|
||||
},
|
||||
"Char": func(c rune) string {
|
||||
return string(c)
|
||||
},
|
||||
}
|
||||
t, err := template.New("usage").Funcs(funcs).Parse(tmpl)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var selectedCommand *CmdModel
|
||||
if context.SelectedCommand != nil {
|
||||
selectedCommand = context.SelectedCommand.Model()
|
||||
}
|
||||
ctx := templateContext{
|
||||
App: a.Model(),
|
||||
Width: width,
|
||||
Context: &templateParseContext{
|
||||
SelectedCommand: selectedCommand,
|
||||
FlagGroupModel: context.flags.Model(),
|
||||
ArgGroupModel: context.arguments.Model(),
|
||||
},
|
||||
}
|
||||
return t.Execute(a.usageWriter, ctx)
|
||||
}
|
||||
470
vendor/gopkg.in/alecthomas/kingpin.v2/values.go
generated
vendored
Normal file
470
vendor/gopkg.in/alecthomas/kingpin.v2/values.go
generated
vendored
Normal file
@@ -0,0 +1,470 @@
|
||||
package kingpin
|
||||
|
||||
//go:generate go run ./cmd/genvalues/main.go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
// NOTE: Most of the base type values were lifted from:
|
||||
// http://golang.org/src/pkg/flag/flag.go?s=20146:20222
|
||||
|
||||
// Value is the interface to the dynamic value stored in a flag.
|
||||
// (The default value is represented as a string.)
|
||||
//
|
||||
// If a Value has an IsBoolFlag() bool method returning true, the command-line
|
||||
// parser makes --name equivalent to -name=true rather than using the next
|
||||
// command-line argument, and adds a --no-name counterpart for negating the
|
||||
// flag.
|
||||
type Value interface {
|
||||
String() string
|
||||
Set(string) error
|
||||
}
|
||||
|
||||
// Getter is an interface that allows the contents of a Value to be retrieved.
|
||||
// It wraps the Value interface, rather than being part of it, because it
|
||||
// appeared after Go 1 and its compatibility rules. All Value types provided
|
||||
// by this package satisfy the Getter interface.
|
||||
type Getter interface {
|
||||
Value
|
||||
Get() interface{}
|
||||
}
|
||||
|
||||
// Optional interface to indicate boolean flags that don't accept a value, and
|
||||
// implicitly have a --no-<x> negation counterpart.
|
||||
type boolFlag interface {
|
||||
Value
|
||||
IsBoolFlag() bool
|
||||
}
|
||||
|
||||
// Optional interface for arguments that cumulatively consume all remaining
|
||||
// input.
|
||||
type remainderArg interface {
|
||||
Value
|
||||
IsCumulative() bool
|
||||
}
|
||||
|
||||
// Optional interface for flags that can be repeated.
|
||||
type repeatableFlag interface {
|
||||
Value
|
||||
IsCumulative() bool
|
||||
}
|
||||
|
||||
type accumulator struct {
|
||||
element func(value interface{}) Value
|
||||
typ reflect.Type
|
||||
slice reflect.Value
|
||||
}
|
||||
|
||||
// Use reflection to accumulate values into a slice.
|
||||
//
|
||||
// target := []string{}
|
||||
// newAccumulator(&target, func (value interface{}) Value {
|
||||
// return newStringValue(value.(*string))
|
||||
// })
|
||||
func newAccumulator(slice interface{}, element func(value interface{}) Value) *accumulator {
|
||||
typ := reflect.TypeOf(slice)
|
||||
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Slice {
|
||||
panic("expected a pointer to a slice")
|
||||
}
|
||||
return &accumulator{
|
||||
element: element,
|
||||
typ: typ.Elem().Elem(),
|
||||
slice: reflect.ValueOf(slice),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *accumulator) String() string {
|
||||
out := []string{}
|
||||
s := a.slice.Elem()
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
out = append(out, a.element(s.Index(i).Addr().Interface()).String())
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
func (a *accumulator) Set(value string) error {
|
||||
e := reflect.New(a.typ)
|
||||
if err := a.element(e.Interface()).Set(value); err != nil {
|
||||
return err
|
||||
}
|
||||
slice := reflect.Append(a.slice.Elem(), e.Elem())
|
||||
a.slice.Elem().Set(slice)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *accumulator) Get() interface{} {
|
||||
return a.slice.Interface()
|
||||
}
|
||||
|
||||
func (a *accumulator) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *boolValue) IsBoolFlag() bool { return true }
|
||||
|
||||
// -- time.Duration Value
|
||||
type durationValue time.Duration
|
||||
|
||||
func newDurationValue(p *time.Duration) *durationValue {
|
||||
return (*durationValue)(p)
|
||||
}
|
||||
|
||||
func (d *durationValue) Set(s string) error {
|
||||
v, err := time.ParseDuration(s)
|
||||
*d = durationValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *durationValue) Get() interface{} { return time.Duration(*d) }
|
||||
|
||||
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
|
||||
|
||||
// -- map[string]string Value
|
||||
type stringMapValue map[string]string
|
||||
|
||||
func newStringMapValue(p *map[string]string) *stringMapValue {
|
||||
return (*stringMapValue)(p)
|
||||
}
|
||||
|
||||
var stringMapRegex = regexp.MustCompile("[:=]")
|
||||
|
||||
func (s *stringMapValue) Set(value string) error {
|
||||
parts := stringMapRegex.Split(value, 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("expected KEY=VALUE got '%s'", value)
|
||||
}
|
||||
(*s)[parts[0]] = parts[1]
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringMapValue) Get() interface{} {
|
||||
return (map[string]string)(*s)
|
||||
}
|
||||
|
||||
func (s *stringMapValue) String() string {
|
||||
return fmt.Sprintf("%s", map[string]string(*s))
|
||||
}
|
||||
|
||||
func (s *stringMapValue) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// -- net.IP Value
|
||||
type ipValue net.IP
|
||||
|
||||
func newIPValue(p *net.IP) *ipValue {
|
||||
return (*ipValue)(p)
|
||||
}
|
||||
|
||||
func (i *ipValue) Set(value string) error {
|
||||
if ip := net.ParseIP(value); ip == nil {
|
||||
return fmt.Errorf("'%s' is not an IP address", value)
|
||||
} else {
|
||||
*i = *(*ipValue)(&ip)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (i *ipValue) Get() interface{} {
|
||||
return (net.IP)(*i)
|
||||
}
|
||||
|
||||
func (i *ipValue) String() string {
|
||||
return (*net.IP)(i).String()
|
||||
}
|
||||
|
||||
// -- *net.TCPAddr Value
|
||||
type tcpAddrValue struct {
|
||||
addr **net.TCPAddr
|
||||
}
|
||||
|
||||
func newTCPAddrValue(p **net.TCPAddr) *tcpAddrValue {
|
||||
return &tcpAddrValue{p}
|
||||
}
|
||||
|
||||
func (i *tcpAddrValue) Set(value string) error {
|
||||
if addr, err := net.ResolveTCPAddr("tcp", value); err != nil {
|
||||
return fmt.Errorf("'%s' is not a valid TCP address: %s", value, err)
|
||||
} else {
|
||||
*i.addr = addr
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tcpAddrValue) Get() interface{} {
|
||||
return (*net.TCPAddr)(*t.addr)
|
||||
}
|
||||
|
||||
func (i *tcpAddrValue) String() string {
|
||||
return (*i.addr).String()
|
||||
}
|
||||
|
||||
// -- existingFile Value
|
||||
|
||||
type fileStatValue struct {
|
||||
path *string
|
||||
predicate func(os.FileInfo) error
|
||||
}
|
||||
|
||||
func newFileStatValue(p *string, predicate func(os.FileInfo) error) *fileStatValue {
|
||||
return &fileStatValue{
|
||||
path: p,
|
||||
predicate: predicate,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *fileStatValue) Set(value string) error {
|
||||
if s, err := os.Stat(value); os.IsNotExist(err) {
|
||||
return fmt.Errorf("path '%s' does not exist", value)
|
||||
} else if err != nil {
|
||||
return err
|
||||
} else if err := e.predicate(s); err != nil {
|
||||
return err
|
||||
}
|
||||
*e.path = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fileStatValue) Get() interface{} {
|
||||
return (string)(*f.path)
|
||||
}
|
||||
|
||||
func (e *fileStatValue) String() string {
|
||||
return *e.path
|
||||
}
|
||||
|
||||
// -- os.File value
|
||||
|
||||
type fileValue struct {
|
||||
f **os.File
|
||||
flag int
|
||||
perm os.FileMode
|
||||
}
|
||||
|
||||
func newFileValue(p **os.File, flag int, perm os.FileMode) *fileValue {
|
||||
return &fileValue{p, flag, perm}
|
||||
}
|
||||
|
||||
func (f *fileValue) Set(value string) error {
|
||||
if fd, err := os.OpenFile(value, f.flag, f.perm); err != nil {
|
||||
return err
|
||||
} else {
|
||||
*f.f = fd
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileValue) Get() interface{} {
|
||||
return (*os.File)(*f.f)
|
||||
}
|
||||
|
||||
func (f *fileValue) String() string {
|
||||
if *f.f == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return (*f.f).Name()
|
||||
}
|
||||
|
||||
// -- url.URL Value
|
||||
type urlValue struct {
|
||||
u **url.URL
|
||||
}
|
||||
|
||||
func newURLValue(p **url.URL) *urlValue {
|
||||
return &urlValue{p}
|
||||
}
|
||||
|
||||
func (u *urlValue) Set(value string) error {
|
||||
if url, err := url.Parse(value); err != nil {
|
||||
return fmt.Errorf("invalid URL: %s", err)
|
||||
} else {
|
||||
*u.u = url
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *urlValue) Get() interface{} {
|
||||
return (*url.URL)(*u.u)
|
||||
}
|
||||
|
||||
func (u *urlValue) String() string {
|
||||
if *u.u == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return (*u.u).String()
|
||||
}
|
||||
|
||||
// -- []*url.URL Value
|
||||
type urlListValue []*url.URL
|
||||
|
||||
func newURLListValue(p *[]*url.URL) *urlListValue {
|
||||
return (*urlListValue)(p)
|
||||
}
|
||||
|
||||
func (u *urlListValue) Set(value string) error {
|
||||
if url, err := url.Parse(value); err != nil {
|
||||
return fmt.Errorf("invalid URL: %s", err)
|
||||
} else {
|
||||
*u = append(*u, url)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *urlListValue) Get() interface{} {
|
||||
return ([]*url.URL)(*u)
|
||||
}
|
||||
|
||||
func (u *urlListValue) String() string {
|
||||
out := []string{}
|
||||
for _, url := range *u {
|
||||
out = append(out, url.String())
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
func (u *urlListValue) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// A flag whose value must be in a set of options.
|
||||
type enumValue struct {
|
||||
value *string
|
||||
options []string
|
||||
}
|
||||
|
||||
func newEnumFlag(target *string, options ...string) *enumValue {
|
||||
return &enumValue{
|
||||
value: target,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *enumValue) String() string {
|
||||
return *a.value
|
||||
}
|
||||
|
||||
func (a *enumValue) Set(value string) error {
|
||||
for _, v := range a.options {
|
||||
if v == value {
|
||||
*a.value = value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(a.options, ","), value)
|
||||
}
|
||||
|
||||
func (e *enumValue) Get() interface{} {
|
||||
return (string)(*e.value)
|
||||
}
|
||||
|
||||
// -- []string Enum Value
|
||||
type enumsValue struct {
|
||||
value *[]string
|
||||
options []string
|
||||
}
|
||||
|
||||
func newEnumsFlag(target *[]string, options ...string) *enumsValue {
|
||||
return &enumsValue{
|
||||
value: target,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *enumsValue) Set(value string) error {
|
||||
for _, v := range s.options {
|
||||
if v == value {
|
||||
*s.value = append(*s.value, value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(s.options, ","), value)
|
||||
}
|
||||
|
||||
func (e *enumsValue) Get() interface{} {
|
||||
return ([]string)(*e.value)
|
||||
}
|
||||
|
||||
func (s *enumsValue) String() string {
|
||||
return strings.Join(*s.value, ",")
|
||||
}
|
||||
|
||||
func (s *enumsValue) IsCumulative() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// -- units.Base2Bytes Value
|
||||
type bytesValue units.Base2Bytes
|
||||
|
||||
func newBytesValue(p *units.Base2Bytes) *bytesValue {
|
||||
return (*bytesValue)(p)
|
||||
}
|
||||
|
||||
func (d *bytesValue) Set(s string) error {
|
||||
v, err := units.ParseBase2Bytes(s)
|
||||
*d = bytesValue(v)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *bytesValue) Get() interface{} { return units.Base2Bytes(*d) }
|
||||
|
||||
func (d *bytesValue) String() string { return (*units.Base2Bytes)(d).String() }
|
||||
|
||||
func newExistingFileValue(target *string) *fileStatValue {
|
||||
return newFileStatValue(target, func(s os.FileInfo) error {
|
||||
if s.IsDir() {
|
||||
return fmt.Errorf("'%s' is a directory", s.Name())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func newExistingDirValue(target *string) *fileStatValue {
|
||||
return newFileStatValue(target, func(s os.FileInfo) error {
|
||||
if !s.IsDir() {
|
||||
return fmt.Errorf("'%s' is a file", s.Name())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func newExistingFileOrDirValue(target *string) *fileStatValue {
|
||||
return newFileStatValue(target, func(s os.FileInfo) error { return nil })
|
||||
}
|
||||
|
||||
type counterValue int
|
||||
|
||||
func newCounterValue(n *int) *counterValue {
|
||||
return (*counterValue)(n)
|
||||
}
|
||||
|
||||
func (c *counterValue) Set(s string) error {
|
||||
*c++
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *counterValue) Get() interface{} { return (int)(*c) }
|
||||
func (c *counterValue) IsBoolFlag() bool { return true }
|
||||
func (c *counterValue) String() string { return fmt.Sprintf("%d", *c) }
|
||||
func (c *counterValue) IsCumulative() bool { return true }
|
||||
|
||||
func resolveHost(value string) (net.IP, error) {
|
||||
if ip := net.ParseIP(value); ip != nil {
|
||||
return ip, nil
|
||||
} else {
|
||||
if addr, err := net.ResolveIPAddr("ip", value); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return addr.IP, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
25
vendor/gopkg.in/alecthomas/kingpin.v2/values.json
generated
vendored
Normal file
25
vendor/gopkg.in/alecthomas/kingpin.v2/values.json
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
[
|
||||
{"type": "bool", "parser": "strconv.ParseBool(s)"},
|
||||
{"type": "string", "parser": "s, error(nil)", "format": "string(*f.v)", "plural": "Strings"},
|
||||
{"type": "uint", "parser": "strconv.ParseUint(s, 0, 64)", "plural": "Uints"},
|
||||
{"type": "uint8", "parser": "strconv.ParseUint(s, 0, 8)"},
|
||||
{"type": "uint16", "parser": "strconv.ParseUint(s, 0, 16)"},
|
||||
{"type": "uint32", "parser": "strconv.ParseUint(s, 0, 32)"},
|
||||
{"type": "uint64", "parser": "strconv.ParseUint(s, 0, 64)"},
|
||||
{"type": "int", "parser": "strconv.ParseFloat(s, 64)", "plural": "Ints"},
|
||||
{"type": "int8", "parser": "strconv.ParseInt(s, 0, 8)"},
|
||||
{"type": "int16", "parser": "strconv.ParseInt(s, 0, 16)"},
|
||||
{"type": "int32", "parser": "strconv.ParseInt(s, 0, 32)"},
|
||||
{"type": "int64", "parser": "strconv.ParseInt(s, 0, 64)"},
|
||||
{"type": "float64", "parser": "strconv.ParseFloat(s, 64)"},
|
||||
{"type": "float32", "parser": "strconv.ParseFloat(s, 32)"},
|
||||
{"name": "Duration", "type": "time.Duration", "no_value_parser": true},
|
||||
{"name": "IP", "type": "net.IP", "no_value_parser": true},
|
||||
{"name": "TCPAddr", "Type": "*net.TCPAddr", "plural": "TCPList", "no_value_parser": true},
|
||||
{"name": "ExistingFile", "Type": "string", "plural": "ExistingFiles", "no_value_parser": true},
|
||||
{"name": "ExistingDir", "Type": "string", "plural": "ExistingDirs", "no_value_parser": true},
|
||||
{"name": "ExistingFileOrDir", "Type": "string", "plural": "ExistingFilesOrDirs", "no_value_parser": true},
|
||||
{"name": "Regexp", "Type": "*regexp.Regexp", "parser": "regexp.Compile(s)"},
|
||||
{"name": "ResolvedIP", "Type": "net.IP", "parser": "resolveHost(s)", "help": "Resolve a hostname or IP to an IP."},
|
||||
{"name": "HexBytes", "Type": "[]byte", "parser": "hex.DecodeString(s)", "help": "Bytes as a hex string."}
|
||||
]
|
||||
821
vendor/gopkg.in/alecthomas/kingpin.v2/values_generated.go
generated
vendored
Normal file
821
vendor/gopkg.in/alecthomas/kingpin.v2/values_generated.go
generated
vendored
Normal file
@@ -0,0 +1,821 @@
|
||||
package kingpin
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// This file is autogenerated by "go generate .". Do not modify.
|
||||
|
||||
// -- bool Value
|
||||
type boolValue struct{ v *bool }
|
||||
|
||||
func newBoolValue(p *bool) *boolValue {
|
||||
return &boolValue{p}
|
||||
}
|
||||
|
||||
func (f *boolValue) Set(s string) error {
|
||||
v, err := strconv.ParseBool(s)
|
||||
if err == nil {
|
||||
*f.v = (bool)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *boolValue) Get() interface{} { return (bool)(*f.v) }
|
||||
|
||||
func (f *boolValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Bool parses the next command-line value as bool.
|
||||
func (p *parserMixin) Bool() (target *bool) {
|
||||
target = new(bool)
|
||||
p.BoolVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) BoolVar(target *bool) {
|
||||
p.SetValue(newBoolValue(target))
|
||||
}
|
||||
|
||||
// BoolList accumulates bool values into a slice.
|
||||
func (p *parserMixin) BoolList() (target *[]bool) {
|
||||
target = new([]bool)
|
||||
p.BoolListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) BoolListVar(target *[]bool) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newBoolValue(v.(*bool))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- string Value
|
||||
type stringValue struct{ v *string }
|
||||
|
||||
func newStringValue(p *string) *stringValue {
|
||||
return &stringValue{p}
|
||||
}
|
||||
|
||||
func (f *stringValue) Set(s string) error {
|
||||
v, err := s, error(nil)
|
||||
if err == nil {
|
||||
*f.v = (string)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *stringValue) Get() interface{} { return (string)(*f.v) }
|
||||
|
||||
func (f *stringValue) String() string { return string(*f.v) }
|
||||
|
||||
// String parses the next command-line value as string.
|
||||
func (p *parserMixin) String() (target *string) {
|
||||
target = new(string)
|
||||
p.StringVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) StringVar(target *string) {
|
||||
p.SetValue(newStringValue(target))
|
||||
}
|
||||
|
||||
// Strings accumulates string values into a slice.
|
||||
func (p *parserMixin) Strings() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.StringsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) StringsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newStringValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint Value
|
||||
type uintValue struct{ v *uint }
|
||||
|
||||
func newUintValue(p *uint) *uintValue {
|
||||
return &uintValue{p}
|
||||
}
|
||||
|
||||
func (f *uintValue) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
if err == nil {
|
||||
*f.v = (uint)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uintValue) Get() interface{} { return (uint)(*f.v) }
|
||||
|
||||
func (f *uintValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint parses the next command-line value as uint.
|
||||
func (p *parserMixin) Uint() (target *uint) {
|
||||
target = new(uint)
|
||||
p.UintVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) UintVar(target *uint) {
|
||||
p.SetValue(newUintValue(target))
|
||||
}
|
||||
|
||||
// Uints accumulates uint values into a slice.
|
||||
func (p *parserMixin) Uints() (target *[]uint) {
|
||||
target = new([]uint)
|
||||
p.UintsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) UintsVar(target *[]uint) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUintValue(v.(*uint))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint8 Value
|
||||
type uint8Value struct{ v *uint8 }
|
||||
|
||||
func newUint8Value(p *uint8) *uint8Value {
|
||||
return &uint8Value{p}
|
||||
}
|
||||
|
||||
func (f *uint8Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 8)
|
||||
if err == nil {
|
||||
*f.v = (uint8)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint8Value) Get() interface{} { return (uint8)(*f.v) }
|
||||
|
||||
func (f *uint8Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint8 parses the next command-line value as uint8.
|
||||
func (p *parserMixin) Uint8() (target *uint8) {
|
||||
target = new(uint8)
|
||||
p.Uint8Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint8Var(target *uint8) {
|
||||
p.SetValue(newUint8Value(target))
|
||||
}
|
||||
|
||||
// Uint8List accumulates uint8 values into a slice.
|
||||
func (p *parserMixin) Uint8List() (target *[]uint8) {
|
||||
target = new([]uint8)
|
||||
p.Uint8ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint8ListVar(target *[]uint8) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint8Value(v.(*uint8))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint16 Value
|
||||
type uint16Value struct{ v *uint16 }
|
||||
|
||||
func newUint16Value(p *uint16) *uint16Value {
|
||||
return &uint16Value{p}
|
||||
}
|
||||
|
||||
func (f *uint16Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 16)
|
||||
if err == nil {
|
||||
*f.v = (uint16)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint16Value) Get() interface{} { return (uint16)(*f.v) }
|
||||
|
||||
func (f *uint16Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint16 parses the next command-line value as uint16.
|
||||
func (p *parserMixin) Uint16() (target *uint16) {
|
||||
target = new(uint16)
|
||||
p.Uint16Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint16Var(target *uint16) {
|
||||
p.SetValue(newUint16Value(target))
|
||||
}
|
||||
|
||||
// Uint16List accumulates uint16 values into a slice.
|
||||
func (p *parserMixin) Uint16List() (target *[]uint16) {
|
||||
target = new([]uint16)
|
||||
p.Uint16ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint16ListVar(target *[]uint16) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint16Value(v.(*uint16))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint32 Value
|
||||
type uint32Value struct{ v *uint32 }
|
||||
|
||||
func newUint32Value(p *uint32) *uint32Value {
|
||||
return &uint32Value{p}
|
||||
}
|
||||
|
||||
func (f *uint32Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 32)
|
||||
if err == nil {
|
||||
*f.v = (uint32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint32Value) Get() interface{} { return (uint32)(*f.v) }
|
||||
|
||||
func (f *uint32Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint32 parses the next command-line value as uint32.
|
||||
func (p *parserMixin) Uint32() (target *uint32) {
|
||||
target = new(uint32)
|
||||
p.Uint32Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint32Var(target *uint32) {
|
||||
p.SetValue(newUint32Value(target))
|
||||
}
|
||||
|
||||
// Uint32List accumulates uint32 values into a slice.
|
||||
func (p *parserMixin) Uint32List() (target *[]uint32) {
|
||||
target = new([]uint32)
|
||||
p.Uint32ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint32ListVar(target *[]uint32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint32Value(v.(*uint32))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- uint64 Value
|
||||
type uint64Value struct{ v *uint64 }
|
||||
|
||||
func newUint64Value(p *uint64) *uint64Value {
|
||||
return &uint64Value{p}
|
||||
}
|
||||
|
||||
func (f *uint64Value) Set(s string) error {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
if err == nil {
|
||||
*f.v = (uint64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *uint64Value) Get() interface{} { return (uint64)(*f.v) }
|
||||
|
||||
func (f *uint64Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Uint64 parses the next command-line value as uint64.
|
||||
func (p *parserMixin) Uint64() (target *uint64) {
|
||||
target = new(uint64)
|
||||
p.Uint64Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint64Var(target *uint64) {
|
||||
p.SetValue(newUint64Value(target))
|
||||
}
|
||||
|
||||
// Uint64List accumulates uint64 values into a slice.
|
||||
func (p *parserMixin) Uint64List() (target *[]uint64) {
|
||||
target = new([]uint64)
|
||||
p.Uint64ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Uint64ListVar(target *[]uint64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newUint64Value(v.(*uint64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int Value
|
||||
type intValue struct{ v *int }
|
||||
|
||||
func newIntValue(p *int) *intValue {
|
||||
return &intValue{p}
|
||||
}
|
||||
|
||||
func (f *intValue) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
*f.v = (int)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *intValue) Get() interface{} { return (int)(*f.v) }
|
||||
|
||||
func (f *intValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int parses the next command-line value as int.
|
||||
func (p *parserMixin) Int() (target *int) {
|
||||
target = new(int)
|
||||
p.IntVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) IntVar(target *int) {
|
||||
p.SetValue(newIntValue(target))
|
||||
}
|
||||
|
||||
// Ints accumulates int values into a slice.
|
||||
func (p *parserMixin) Ints() (target *[]int) {
|
||||
target = new([]int)
|
||||
p.IntsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) IntsVar(target *[]int) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newIntValue(v.(*int))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int8 Value
|
||||
type int8Value struct{ v *int8 }
|
||||
|
||||
func newInt8Value(p *int8) *int8Value {
|
||||
return &int8Value{p}
|
||||
}
|
||||
|
||||
func (f *int8Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 8)
|
||||
if err == nil {
|
||||
*f.v = (int8)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int8Value) Get() interface{} { return (int8)(*f.v) }
|
||||
|
||||
func (f *int8Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int8 parses the next command-line value as int8.
|
||||
func (p *parserMixin) Int8() (target *int8) {
|
||||
target = new(int8)
|
||||
p.Int8Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int8Var(target *int8) {
|
||||
p.SetValue(newInt8Value(target))
|
||||
}
|
||||
|
||||
// Int8List accumulates int8 values into a slice.
|
||||
func (p *parserMixin) Int8List() (target *[]int8) {
|
||||
target = new([]int8)
|
||||
p.Int8ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int8ListVar(target *[]int8) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt8Value(v.(*int8))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int16 Value
|
||||
type int16Value struct{ v *int16 }
|
||||
|
||||
func newInt16Value(p *int16) *int16Value {
|
||||
return &int16Value{p}
|
||||
}
|
||||
|
||||
func (f *int16Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 16)
|
||||
if err == nil {
|
||||
*f.v = (int16)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int16Value) Get() interface{} { return (int16)(*f.v) }
|
||||
|
||||
func (f *int16Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int16 parses the next command-line value as int16.
|
||||
func (p *parserMixin) Int16() (target *int16) {
|
||||
target = new(int16)
|
||||
p.Int16Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int16Var(target *int16) {
|
||||
p.SetValue(newInt16Value(target))
|
||||
}
|
||||
|
||||
// Int16List accumulates int16 values into a slice.
|
||||
func (p *parserMixin) Int16List() (target *[]int16) {
|
||||
target = new([]int16)
|
||||
p.Int16ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int16ListVar(target *[]int16) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt16Value(v.(*int16))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int32 Value
|
||||
type int32Value struct{ v *int32 }
|
||||
|
||||
func newInt32Value(p *int32) *int32Value {
|
||||
return &int32Value{p}
|
||||
}
|
||||
|
||||
func (f *int32Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 32)
|
||||
if err == nil {
|
||||
*f.v = (int32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int32Value) Get() interface{} { return (int32)(*f.v) }
|
||||
|
||||
func (f *int32Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int32 parses the next command-line value as int32.
|
||||
func (p *parserMixin) Int32() (target *int32) {
|
||||
target = new(int32)
|
||||
p.Int32Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int32Var(target *int32) {
|
||||
p.SetValue(newInt32Value(target))
|
||||
}
|
||||
|
||||
// Int32List accumulates int32 values into a slice.
|
||||
func (p *parserMixin) Int32List() (target *[]int32) {
|
||||
target = new([]int32)
|
||||
p.Int32ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int32ListVar(target *[]int32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt32Value(v.(*int32))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- int64 Value
|
||||
type int64Value struct{ v *int64 }
|
||||
|
||||
func newInt64Value(p *int64) *int64Value {
|
||||
return &int64Value{p}
|
||||
}
|
||||
|
||||
func (f *int64Value) Set(s string) error {
|
||||
v, err := strconv.ParseInt(s, 0, 64)
|
||||
if err == nil {
|
||||
*f.v = (int64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *int64Value) Get() interface{} { return (int64)(*f.v) }
|
||||
|
||||
func (f *int64Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Int64 parses the next command-line value as int64.
|
||||
func (p *parserMixin) Int64() (target *int64) {
|
||||
target = new(int64)
|
||||
p.Int64Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int64Var(target *int64) {
|
||||
p.SetValue(newInt64Value(target))
|
||||
}
|
||||
|
||||
// Int64List accumulates int64 values into a slice.
|
||||
func (p *parserMixin) Int64List() (target *[]int64) {
|
||||
target = new([]int64)
|
||||
p.Int64ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Int64ListVar(target *[]int64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newInt64Value(v.(*int64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- float64 Value
|
||||
type float64Value struct{ v *float64 }
|
||||
|
||||
func newFloat64Value(p *float64) *float64Value {
|
||||
return &float64Value{p}
|
||||
}
|
||||
|
||||
func (f *float64Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
if err == nil {
|
||||
*f.v = (float64)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float64Value) Get() interface{} { return (float64)(*f.v) }
|
||||
|
||||
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Float64 parses the next command-line value as float64.
|
||||
func (p *parserMixin) Float64() (target *float64) {
|
||||
target = new(float64)
|
||||
p.Float64Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float64Var(target *float64) {
|
||||
p.SetValue(newFloat64Value(target))
|
||||
}
|
||||
|
||||
// Float64List accumulates float64 values into a slice.
|
||||
func (p *parserMixin) Float64List() (target *[]float64) {
|
||||
target = new([]float64)
|
||||
p.Float64ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float64ListVar(target *[]float64) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newFloat64Value(v.(*float64))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- float32 Value
|
||||
type float32Value struct{ v *float32 }
|
||||
|
||||
func newFloat32Value(p *float32) *float32Value {
|
||||
return &float32Value{p}
|
||||
}
|
||||
|
||||
func (f *float32Value) Set(s string) error {
|
||||
v, err := strconv.ParseFloat(s, 32)
|
||||
if err == nil {
|
||||
*f.v = (float32)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *float32Value) Get() interface{} { return (float32)(*f.v) }
|
||||
|
||||
func (f *float32Value) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Float32 parses the next command-line value as float32.
|
||||
func (p *parserMixin) Float32() (target *float32) {
|
||||
target = new(float32)
|
||||
p.Float32Var(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float32Var(target *float32) {
|
||||
p.SetValue(newFloat32Value(target))
|
||||
}
|
||||
|
||||
// Float32List accumulates float32 values into a slice.
|
||||
func (p *parserMixin) Float32List() (target *[]float32) {
|
||||
target = new([]float32)
|
||||
p.Float32ListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) Float32ListVar(target *[]float32) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newFloat32Value(v.(*float32))
|
||||
}))
|
||||
}
|
||||
|
||||
// DurationList accumulates time.Duration values into a slice.
|
||||
func (p *parserMixin) DurationList() (target *[]time.Duration) {
|
||||
target = new([]time.Duration)
|
||||
p.DurationListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) DurationListVar(target *[]time.Duration) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newDurationValue(v.(*time.Duration))
|
||||
}))
|
||||
}
|
||||
|
||||
// IPList accumulates net.IP values into a slice.
|
||||
func (p *parserMixin) IPList() (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
p.IPListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) IPListVar(target *[]net.IP) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newIPValue(v.(*net.IP))
|
||||
}))
|
||||
}
|
||||
|
||||
// TCPList accumulates *net.TCPAddr values into a slice.
|
||||
func (p *parserMixin) TCPList() (target *[]*net.TCPAddr) {
|
||||
target = new([]*net.TCPAddr)
|
||||
p.TCPListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) TCPListVar(target *[]*net.TCPAddr) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newTCPAddrValue(v.(**net.TCPAddr))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingFiles accumulates string values into a slice.
|
||||
func (p *parserMixin) ExistingFiles() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.ExistingFilesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingFilesVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingFileValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingDirs accumulates string values into a slice.
|
||||
func (p *parserMixin) ExistingDirs() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.ExistingDirsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingDirsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingDirValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// ExistingFilesOrDirs accumulates string values into a slice.
|
||||
func (p *parserMixin) ExistingFilesOrDirs() (target *[]string) {
|
||||
target = new([]string)
|
||||
p.ExistingFilesOrDirsVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ExistingFilesOrDirsVar(target *[]string) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newExistingFileOrDirValue(v.(*string))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- *regexp.Regexp Value
|
||||
type regexpValue struct{ v **regexp.Regexp }
|
||||
|
||||
func newRegexpValue(p **regexp.Regexp) *regexpValue {
|
||||
return ®expValue{p}
|
||||
}
|
||||
|
||||
func (f *regexpValue) Set(s string) error {
|
||||
v, err := regexp.Compile(s)
|
||||
if err == nil {
|
||||
*f.v = (*regexp.Regexp)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *regexpValue) Get() interface{} { return (*regexp.Regexp)(*f.v) }
|
||||
|
||||
func (f *regexpValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Regexp parses the next command-line value as *regexp.Regexp.
|
||||
func (p *parserMixin) Regexp() (target **regexp.Regexp) {
|
||||
target = new(*regexp.Regexp)
|
||||
p.RegexpVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) RegexpVar(target **regexp.Regexp) {
|
||||
p.SetValue(newRegexpValue(target))
|
||||
}
|
||||
|
||||
// RegexpList accumulates *regexp.Regexp values into a slice.
|
||||
func (p *parserMixin) RegexpList() (target *[]*regexp.Regexp) {
|
||||
target = new([]*regexp.Regexp)
|
||||
p.RegexpListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) RegexpListVar(target *[]*regexp.Regexp) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newRegexpValue(v.(**regexp.Regexp))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- net.IP Value
|
||||
type resolvedIPValue struct{ v *net.IP }
|
||||
|
||||
func newResolvedIPValue(p *net.IP) *resolvedIPValue {
|
||||
return &resolvedIPValue{p}
|
||||
}
|
||||
|
||||
func (f *resolvedIPValue) Set(s string) error {
|
||||
v, err := resolveHost(s)
|
||||
if err == nil {
|
||||
*f.v = (net.IP)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *resolvedIPValue) Get() interface{} { return (net.IP)(*f.v) }
|
||||
|
||||
func (f *resolvedIPValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Resolve a hostname or IP to an IP.
|
||||
func (p *parserMixin) ResolvedIP() (target *net.IP) {
|
||||
target = new(net.IP)
|
||||
p.ResolvedIPVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ResolvedIPVar(target *net.IP) {
|
||||
p.SetValue(newResolvedIPValue(target))
|
||||
}
|
||||
|
||||
// ResolvedIPList accumulates net.IP values into a slice.
|
||||
func (p *parserMixin) ResolvedIPList() (target *[]net.IP) {
|
||||
target = new([]net.IP)
|
||||
p.ResolvedIPListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) ResolvedIPListVar(target *[]net.IP) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newResolvedIPValue(v.(*net.IP))
|
||||
}))
|
||||
}
|
||||
|
||||
// -- []byte Value
|
||||
type hexBytesValue struct{ v *[]byte }
|
||||
|
||||
func newHexBytesValue(p *[]byte) *hexBytesValue {
|
||||
return &hexBytesValue{p}
|
||||
}
|
||||
|
||||
func (f *hexBytesValue) Set(s string) error {
|
||||
v, err := hex.DecodeString(s)
|
||||
if err == nil {
|
||||
*f.v = ([]byte)(v)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *hexBytesValue) Get() interface{} { return ([]byte)(*f.v) }
|
||||
|
||||
func (f *hexBytesValue) String() string { return fmt.Sprintf("%v", *f.v) }
|
||||
|
||||
// Bytes as a hex string.
|
||||
func (p *parserMixin) HexBytes() (target *[]byte) {
|
||||
target = new([]byte)
|
||||
p.HexBytesVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) HexBytesVar(target *[]byte) {
|
||||
p.SetValue(newHexBytesValue(target))
|
||||
}
|
||||
|
||||
// HexBytesList accumulates []byte values into a slice.
|
||||
func (p *parserMixin) HexBytesList() (target *[][]byte) {
|
||||
target = new([][]byte)
|
||||
p.HexBytesListVar(target)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *parserMixin) HexBytesListVar(target *[][]byte) {
|
||||
p.SetValue(newAccumulator(target, func(v interface{}) Value {
|
||||
return newHexBytesValue(v.(*[]byte))
|
||||
}))
|
||||
}
|
||||
29
vendor/modules.txt
vendored
29
vendor/modules.txt
vendored
@@ -3,6 +3,19 @@ github.com/BurntSushi/toml
|
||||
# github.com/DataDog/zstd v1.4.8
|
||||
## explicit
|
||||
github.com/DataDog/zstd
|
||||
# github.com/alecthomas/gocyclo v0.0.0-20150208221726-aa8f8b160214
|
||||
## explicit
|
||||
github.com/alecthomas/gocyclo
|
||||
# github.com/alecthomas/gometalinter v1.0.3
|
||||
## explicit
|
||||
github.com/alecthomas/gometalinter
|
||||
# github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
## explicit
|
||||
github.com/alecthomas/template
|
||||
github.com/alecthomas/template/parse
|
||||
# github.com/alecthomas/units v0.0.0-20210208195552-ff826a37aa15
|
||||
## explicit
|
||||
github.com/alecthomas/units
|
||||
# github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a
|
||||
## explicit
|
||||
github.com/beorn7/perks/quantile
|
||||
@@ -37,8 +50,13 @@ github.com/golang/protobuf/proto
|
||||
# github.com/golang/snappy v0.0.3
|
||||
## explicit
|
||||
github.com/golang/snappy
|
||||
# github.com/kr/pretty v0.2.1
|
||||
# github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
## explicit
|
||||
github.com/google/shlex
|
||||
# github.com/gordonklaus/ineffassign v0.0.0-20210225214923-2e10b2664254
|
||||
## explicit
|
||||
github.com/gordonklaus/ineffassign
|
||||
github.com/gordonklaus/ineffassign/pkg/ineffassign
|
||||
# github.com/matttproud/golang_protobuf_extensions v1.0.1
|
||||
## explicit
|
||||
github.com/matttproud/golang_protobuf_extensions/pbutil
|
||||
@@ -79,7 +97,12 @@ golang.org/x/sys/unix
|
||||
# golang.org/x/tools v0.1.0
|
||||
## explicit
|
||||
golang.org/x/tools/go/analysis
|
||||
golang.org/x/tools/go/analysis/internal/analysisflags
|
||||
golang.org/x/tools/go/analysis/internal/checker
|
||||
golang.org/x/tools/go/analysis/internal/facts
|
||||
golang.org/x/tools/go/analysis/passes/inspect
|
||||
golang.org/x/tools/go/analysis/singlechecker
|
||||
golang.org/x/tools/go/analysis/unitchecker
|
||||
golang.org/x/tools/go/ast/astutil
|
||||
golang.org/x/tools/go/ast/inspector
|
||||
golang.org/x/tools/go/buildutil
|
||||
@@ -100,6 +123,7 @@ golang.org/x/tools/internal/event/label
|
||||
golang.org/x/tools/internal/gocommand
|
||||
golang.org/x/tools/internal/lsp/fuzzy
|
||||
golang.org/x/tools/internal/packagesinternal
|
||||
golang.org/x/tools/internal/span
|
||||
golang.org/x/tools/internal/typesinternal
|
||||
# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||
golang.org/x/xerrors
|
||||
@@ -132,6 +156,9 @@ google.golang.org/protobuf/reflect/protoregistry
|
||||
google.golang.org/protobuf/runtime/protoiface
|
||||
google.golang.org/protobuf/runtime/protoimpl
|
||||
google.golang.org/protobuf/types/descriptorpb
|
||||
# gopkg.in/alecthomas/kingpin.v2 v2.2.6
|
||||
## explicit
|
||||
gopkg.in/alecthomas/kingpin.v2
|
||||
# honnef.co/go/tools v0.1.3
|
||||
## explicit
|
||||
honnef.co/go/tools/analysis/code
|
||||
|
||||
Reference in New Issue
Block a user