mirror of
https://github.com/octoleo/restic.git
synced 2024-11-30 16:53:59 +00:00
Merge pull request #181 from restic/compile-from-checkout
Vendor using godep, allow compiling from checkout
This commit is contained in:
commit
0bb43fa0af
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
|||||||
|
/.gopath
|
||||||
|
/restic
|
||||||
|
/restic.debug
|
||||||
|
/dirdiff
|
||||||
cmd/dirdiff/dirdiff
|
cmd/dirdiff/dirdiff
|
||||||
cmd/gentestdata/gentestdata
|
cmd/gentestdata/gentestdata
|
||||||
cmd/restic/restic
|
cmd/restic/restic
|
||||||
|
23
.travis.yml
23
.travis.yml
@ -8,6 +8,8 @@ os:
|
|||||||
- linux
|
- linux
|
||||||
- osx
|
- osx
|
||||||
|
|
||||||
|
env: SFTP_PATH="/usr/lib/openssh/sftp-server" GOX_OS="linux darwin openbsd freebsd"
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
irc:
|
irc:
|
||||||
channels:
|
channels:
|
||||||
@ -15,23 +17,22 @@ notifications:
|
|||||||
on_success: change
|
on_success: change
|
||||||
on_failure: change
|
on_failure: change
|
||||||
|
|
||||||
env: GOX_OS="linux darwin openbsd freebsd"
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- go get github.com/mattn/goveralls
|
- go get github.com/mattn/goveralls
|
||||||
- go get github.com/mitchellh/gox
|
- go get github.com/mitchellh/gox
|
||||||
- gox -build-toolchain -os "$GOX_OS"
|
- gox -build-toolchain -os "$GOX_OS"
|
||||||
- go get -v -t ./...
|
- go version
|
||||||
|
- go env
|
||||||
|
- make env
|
||||||
|
- make goenv
|
||||||
|
- make list
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- go build -ldflags "-s" ./...
|
- make restic
|
||||||
- go build -ldflags "-s" -o restic ./cmd/restic
|
- make gox
|
||||||
- sh -c 'cd cmd/restic && gox -verbose -os "$GOX_OS" && ls -al'
|
- make test
|
||||||
- go test -v ./...
|
- make test-integration
|
||||||
- ./testsuite.sh
|
- make all.cov
|
||||||
- sh -c "cd backend && go test -v -test.sftppath /usr/lib/openssh/sftp-server ./..."
|
|
||||||
- go list ./... | while read pkg; do go test -covermode=count -coverprofile=$(base64 <<< $pkg).cov $pkg; done
|
|
||||||
- 'echo "mode: count" > all.cov; tail -q -n +2 *.cov >> all.cov'
|
|
||||||
- goveralls -coverprofile=all.cov -service=travis-ci -repotoken "$COVERALLS_TOKEN"
|
- goveralls -coverprofile=all.cov -service=travis-ci -repotoken "$COVERALLS_TOKEN"
|
||||||
- gofmt -l *.go */*.go */*/*.go
|
- gofmt -l *.go */*.go */*/*.go
|
||||||
- test -z "$(gofmt -l *.go */*.go */*/*.go)"
|
- test -z "$(gofmt -l *.go */*.go */*/*.go)"
|
||||||
|
42
Godeps/Godeps.json
generated
Normal file
42
Godeps/Godeps.json
generated
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/restic/restic",
|
||||||
|
"GoVersion": "go1.4.2",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/jessevdk/go-flags",
|
||||||
|
"Comment": "v1-293-g5e11878",
|
||||||
|
"Rev": "5e118789801496c93ba210d34ef1f2ce5a9173bd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/juju/errors",
|
||||||
|
"Rev": "4567a5e69fd3130ca0d89f69478e7ac025b67452"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/kr/fs",
|
||||||
|
"Rev": "2788f0dbd16903de03cb8186e5c7d97b69ad387b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/pkg/sftp",
|
||||||
|
"Rev": "506297c9013d2893d5c5daaa9155e7333a1c58de"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/pbkdf2",
|
||||||
|
"Rev": "24ffb5feb3312a39054178a4b0a4554fc2201248"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/poly1305",
|
||||||
|
"Rev": "24ffb5feb3312a39054178a4b0a4554fc2201248"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/scrypt",
|
||||||
|
"Rev": "24ffb5feb3312a39054178a4b0a4554fc2201248"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "golang.org/x/crypto/ssh",
|
||||||
|
"Rev": "24ffb5feb3312a39054178a4b0a4554fc2201248"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/pkg
|
||||||
|
/bin
|
35
Godeps/_workspace/src/github.com/jessevdk/go-flags/.travis.yml
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/jessevdk/go-flags/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
install:
|
||||||
|
# go-flags
|
||||||
|
- go get -d -v ./...
|
||||||
|
- go build -v ./...
|
||||||
|
|
||||||
|
# linting
|
||||||
|
- go get golang.org/x/tools/cmd/vet
|
||||||
|
- go get github.com/golang/lint
|
||||||
|
- go install github.com/golang/lint/golint
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/onsi/ginkgo/ginkgo
|
||||||
|
- go get github.com/modocache/gover
|
||||||
|
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then go get github.com/mattn/goveralls; fi
|
||||||
|
|
||||||
|
script:
|
||||||
|
# go-flags
|
||||||
|
- $(exit $(gofmt -l . | wc -l))
|
||||||
|
- go test -v ./...
|
||||||
|
|
||||||
|
# linting
|
||||||
|
- go tool vet -all=true -v=true . || true
|
||||||
|
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/golint ./...
|
||||||
|
|
||||||
|
# code coverage
|
||||||
|
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/ginkgo -r -cover
|
||||||
|
- $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/gover
|
||||||
|
- if [ "$TRAVIS_SECURE_ENV_VARS" = "true" ]; then $(go env GOPATH | awk 'BEGIN{FS=":"} {print $1}')/bin/goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN; fi
|
||||||
|
|
||||||
|
env:
|
||||||
|
# coveralls.io
|
||||||
|
secure: "RCYbiB4P0RjQRIoUx/vG/AjP3mmYCbzOmr86DCww1Z88yNcy3hYr3Cq8rpPtYU5v0g7wTpu4adaKIcqRE9xknYGbqj3YWZiCoBP1/n4Z+9sHW3Dsd9D/GRGeHUus0laJUGARjWoCTvoEtOgTdGQDoX7mH+pUUY0FBltNYUdOiiU="
|
26
Godeps/_workspace/src/github.com/jessevdk/go-flags/LICENSE
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/jessevdk/go-flags/LICENSE
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
Copyright (c) 2012 Jesse van den Kieboom. 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.
|
131
Godeps/_workspace/src/github.com/jessevdk/go-flags/README.md
generated
vendored
Normal file
131
Godeps/_workspace/src/github.com/jessevdk/go-flags/README.md
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
go-flags: a go library for parsing command line arguments
|
||||||
|
=========================================================
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/jessevdk/go-flags?status.png)](https://godoc.org/github.com/jessevdk/go-flags) [![Build Status](https://travis-ci.org/jessevdk/go-flags.svg?branch=master)](https://travis-ci.org/jessevdk/go-flags) [![Coverage Status](https://img.shields.io/coveralls/jessevdk/go-flags.svg)](https://coveralls.io/r/jessevdk/go-flags?branch=master)
|
||||||
|
|
||||||
|
This library provides similar functionality to the builtin flag library of
|
||||||
|
go, but provides much more functionality and nicer formatting. From the
|
||||||
|
documentation:
|
||||||
|
|
||||||
|
Package flags provides an extensive command line option parser.
|
||||||
|
The flags package is similar in functionality to the go builtin flag package
|
||||||
|
but provides more options and uses reflection to provide a convenient and
|
||||||
|
succinct way of specifying command line options.
|
||||||
|
|
||||||
|
Supported features:
|
||||||
|
* Options with short names (-v)
|
||||||
|
* Options with long names (--verbose)
|
||||||
|
* Options with and without arguments (bool v.s. other type)
|
||||||
|
* Options with optional arguments and default values
|
||||||
|
* Multiple option groups each containing a set of options
|
||||||
|
* Generate and print well-formatted help message
|
||||||
|
* Passing remaining command line arguments after -- (optional)
|
||||||
|
* Ignoring unknown command line options (optional)
|
||||||
|
* Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
|
||||||
|
* Supports multiple short options -aux
|
||||||
|
* Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
|
||||||
|
* Supports same option multiple times (can store in slice or last option counts)
|
||||||
|
* Supports maps
|
||||||
|
* Supports function callbacks
|
||||||
|
* Supports namespaces for (nested) option groups
|
||||||
|
|
||||||
|
The flags package uses structs, reflection and struct field tags
|
||||||
|
to allow users to specify command line options. This results in very simple
|
||||||
|
and concise specification of your application options. For example:
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||||
|
}
|
||||||
|
|
||||||
|
This specifies one option with a short name -v and a long name --verbose.
|
||||||
|
When either -v or --verbose is found on the command line, a 'true' value
|
||||||
|
will be appended to the Verbose field. e.g. when specifying -vvv, the
|
||||||
|
resulting value of Verbose will be {[true, true, true]}.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
--------
|
||||||
|
var opts struct {
|
||||||
|
// Slice of bool will append 'true' each time the option
|
||||||
|
// is encountered (can be set multiple times, like -vvv)
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||||
|
|
||||||
|
// Example of automatic marshalling to desired type (uint)
|
||||||
|
Offset uint `long:"offset" description:"Offset"`
|
||||||
|
|
||||||
|
// Example of a callback, called each time the option is found.
|
||||||
|
Call func(string) `short:"c" description:"Call phone number"`
|
||||||
|
|
||||||
|
// Example of a required flag
|
||||||
|
Name string `short:"n" long:"name" description:"A name" required:"true"`
|
||||||
|
|
||||||
|
// Example of a value name
|
||||||
|
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
|
||||||
|
|
||||||
|
// Example of a pointer
|
||||||
|
Ptr *int `short:"p" description:"A pointer to an integer"`
|
||||||
|
|
||||||
|
// Example of a slice of strings
|
||||||
|
StringSlice []string `short:"s" description:"A slice of strings"`
|
||||||
|
|
||||||
|
// Example of a slice of pointers
|
||||||
|
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||||
|
|
||||||
|
// Example of a map
|
||||||
|
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback which will invoke callto:<argument> to call a number.
|
||||||
|
// Note that this works just on OS X (and probably only with
|
||||||
|
// Skype) but it shows the idea.
|
||||||
|
opts.Call = func(num string) {
|
||||||
|
cmd := exec.Command("open", "callto:"+num)
|
||||||
|
cmd.Start()
|
||||||
|
cmd.Process.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make some fake arguments to parse.
|
||||||
|
args := []string{
|
||||||
|
"-vv",
|
||||||
|
"--offset=5",
|
||||||
|
"-n", "Me",
|
||||||
|
"-p", "3",
|
||||||
|
"-s", "hello",
|
||||||
|
"-s", "world",
|
||||||
|
"--ptrslice", "hello",
|
||||||
|
"--ptrslice", "world",
|
||||||
|
"--intmap", "a:1",
|
||||||
|
"--intmap", "b:5",
|
||||||
|
"arg1",
|
||||||
|
"arg2",
|
||||||
|
"arg3",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse flags from `args'. Note that here we use flags.ParseArgs for
|
||||||
|
// the sake of making a working example. Normally, you would simply use
|
||||||
|
// flags.Parse(&opts) which uses os.Args
|
||||||
|
args, err := flags.ParseArgs(&opts, args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Verbosity: %v\n", opts.Verbose)
|
||||||
|
fmt.Printf("Offset: %d\n", opts.Offset)
|
||||||
|
fmt.Printf("Name: %s\n", opts.Name)
|
||||||
|
fmt.Printf("Ptr: %d\n", *opts.Ptr)
|
||||||
|
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
|
||||||
|
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
|
||||||
|
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
|
||||||
|
fmt.Printf("Remaining args: %s\n", strings.Join(args, " "))
|
||||||
|
|
||||||
|
// Output: Verbosity: [true true]
|
||||||
|
// Offset: 5
|
||||||
|
// Name: Me
|
||||||
|
// Ptr: 3
|
||||||
|
// StringSlice: [hello world]
|
||||||
|
// PtrSlice: [hello world]
|
||||||
|
// IntMap: [a:1 b:5]
|
||||||
|
// Remaining args: arg1 arg2 arg3
|
||||||
|
|
||||||
|
More information can be found in the godocs: <http://godoc.org/github.com/jessevdk/go-flags>
|
21
Godeps/_workspace/src/github.com/jessevdk/go-flags/arg.go
generated
vendored
Normal file
21
Godeps/_workspace/src/github.com/jessevdk/go-flags/arg.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Arg represents a positional argument on the command line.
|
||||||
|
type Arg struct {
|
||||||
|
// The name of the positional argument (used in the help)
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// A description of the positional argument (used in the help)
|
||||||
|
Description string
|
||||||
|
|
||||||
|
value reflect.Value
|
||||||
|
tag multiTag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Arg) isRemaining() bool {
|
||||||
|
return a.value.Type().Kind() == reflect.Slice
|
||||||
|
}
|
53
Godeps/_workspace/src/github.com/jessevdk/go-flags/arg_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/github.com/jessevdk/go-flags/arg_test.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPositional(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Positional struct {
|
||||||
|
Command int
|
||||||
|
Filename string
|
||||||
|
Rest []string
|
||||||
|
} `positional-args:"yes" required:"yes"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, Default)
|
||||||
|
ret, err := p.ParseArgs([]string{"10", "arg_test.go", "a", "b"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Positional.Command != 10 {
|
||||||
|
t.Fatalf("Expected opts.Positional.Command to be 10, but got %v", opts.Positional.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Positional.Filename != "arg_test.go" {
|
||||||
|
t.Fatalf("Expected opts.Positional.Filename to be \"arg_test.go\", but got %v", opts.Positional.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStringArray(t, opts.Positional.Rest, []string{"a", "b"})
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPositionalRequired(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Positional struct {
|
||||||
|
Command int
|
||||||
|
Filename string
|
||||||
|
Rest []string
|
||||||
|
} `positional-args:"yes" required:"yes"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, None)
|
||||||
|
_, err := p.ParseArgs([]string{"10"})
|
||||||
|
|
||||||
|
assertError(t, err, ErrRequired, "the required argument `Filename` was not provided")
|
||||||
|
}
|
177
Godeps/_workspace/src/github.com/jessevdk/go-flags/assert_test.go
generated
vendored
Normal file
177
Godeps/_workspace/src/github.com/jessevdk/go-flags/assert_test.go
generated
vendored
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func assertCallerInfo() (string, int) {
|
||||||
|
ptr := make([]uintptr, 15)
|
||||||
|
n := runtime.Callers(1, ptr)
|
||||||
|
|
||||||
|
if n == 0 {
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mef := runtime.FuncForPC(ptr[0])
|
||||||
|
mefile, meline := mef.FileLine(ptr[0])
|
||||||
|
|
||||||
|
for i := 2; i < n; i++ {
|
||||||
|
f := runtime.FuncForPC(ptr[i])
|
||||||
|
file, line := f.FileLine(ptr[i])
|
||||||
|
|
||||||
|
if file != mefile {
|
||||||
|
return file, line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mefile, meline
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertErrorf(t *testing.T, format string, args ...interface{}) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
|
||||||
|
file, line := assertCallerInfo()
|
||||||
|
|
||||||
|
t.Errorf("%s:%d: %s", path.Base(file), line, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertFatalf(t *testing.T, format string, args ...interface{}) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
|
||||||
|
file, line := assertCallerInfo()
|
||||||
|
|
||||||
|
t.Fatalf("%s:%d: %s", path.Base(file), line, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertString(t *testing.T, a string, b string) {
|
||||||
|
if a != b {
|
||||||
|
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertStringArray(t *testing.T, a []string, b []string) {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range a {
|
||||||
|
if b[i] != v {
|
||||||
|
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertBoolArray(t *testing.T, a []bool, b []bool) {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range a {
|
||||||
|
if b[i] != v {
|
||||||
|
assertErrorf(t, "Expected %#v, but got %#v", b, a)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertParserSuccess(t *testing.T, data interface{}, args ...string) (*Parser, []string) {
|
||||||
|
parser := NewParser(data, Default&^PrintErrors)
|
||||||
|
ret, err := parser.ParseArgs(args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected parse error: %s", err)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return parser, ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertParseSuccess(t *testing.T, data interface{}, args ...string) []string {
|
||||||
|
_, ret := assertParserSuccess(t, data, args...)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertError(t *testing.T, err error, typ ErrorType, msg string) {
|
||||||
|
if err == nil {
|
||||||
|
assertFatalf(t, "Expected error: %s", msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := err.(*Error); !ok {
|
||||||
|
assertFatalf(t, "Expected Error type, but got %#v", err)
|
||||||
|
} else {
|
||||||
|
if e.Type != typ {
|
||||||
|
assertErrorf(t, "Expected error type {%s}, but got {%s}", typ, e.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.Message != msg {
|
||||||
|
assertErrorf(t, "Expected error message %#v, but got %#v", msg, e.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertParseFail(t *testing.T, typ ErrorType, msg string, data interface{}, args ...string) []string {
|
||||||
|
parser := NewParser(data, Default&^PrintErrors)
|
||||||
|
ret, err := parser.ParseArgs(args)
|
||||||
|
|
||||||
|
assertError(t, err, typ, msg)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func diff(a, b string) (string, error) {
|
||||||
|
atmp, err := ioutil.TempFile("", "help-diff")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
btmp, err := ioutil.TempFile("", "help-diff")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.WriteString(atmp, a); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.WriteString(btmp, b); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := exec.Command("diff", "-u", "-d", "--label", "got", atmp.Name(), "--label", "expected", btmp.Name()).Output()
|
||||||
|
|
||||||
|
os.Remove(atmp.Name())
|
||||||
|
os.Remove(btmp.Name())
|
||||||
|
|
||||||
|
if err.Error() == "exit status 1" {
|
||||||
|
return string(ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(ret), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertDiff(t *testing.T, actual, expected, msg string) {
|
||||||
|
if actual == expected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := diff(actual, expected)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
assertErrorf(t, "Unexpected diff error: %s", err)
|
||||||
|
assertErrorf(t, "Unexpected %s, expected:\n\n%s\n\nbut got\n\n%s", msg, expected, actual)
|
||||||
|
} else {
|
||||||
|
assertErrorf(t, "Unexpected %s:\n\n%s", msg, ret)
|
||||||
|
}
|
||||||
|
}
|
16
Godeps/_workspace/src/github.com/jessevdk/go-flags/check_crosscompile.sh
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/jessevdk/go-flags/check_crosscompile.sh
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo '# linux arm7'
|
||||||
|
GOARM=7 GOARCH=arm GOOS=linux go build
|
||||||
|
echo '# linux arm5'
|
||||||
|
GOARM=5 GOARCH=arm GOOS=linux go build
|
||||||
|
echo '# windows 386'
|
||||||
|
GOARCH=386 GOOS=windows go build
|
||||||
|
echo '# windows amd64'
|
||||||
|
GOARCH=amd64 GOOS=windows go build
|
||||||
|
echo '# darwin'
|
||||||
|
GOARCH=amd64 GOOS=darwin go build
|
||||||
|
echo '# freebsd'
|
||||||
|
GOARCH=amd64 GOOS=freebsd go build
|
59
Godeps/_workspace/src/github.com/jessevdk/go-flags/closest.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/jessevdk/go-flags/closest.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
func levenshtein(s string, t string) int {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return len(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t) == 0 {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
dists := make([][]int, len(s)+1)
|
||||||
|
for i := range dists {
|
||||||
|
dists[i] = make([]int, len(t)+1)
|
||||||
|
dists[i][0] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := range t {
|
||||||
|
dists[0][j] = j
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, sc := range s {
|
||||||
|
for j, tc := range t {
|
||||||
|
if sc == tc {
|
||||||
|
dists[i+1][j+1] = dists[i][j]
|
||||||
|
} else {
|
||||||
|
dists[i+1][j+1] = dists[i][j] + 1
|
||||||
|
if dists[i+1][j] < dists[i+1][j+1] {
|
||||||
|
dists[i+1][j+1] = dists[i+1][j] + 1
|
||||||
|
}
|
||||||
|
if dists[i][j+1] < dists[i+1][j+1] {
|
||||||
|
dists[i+1][j+1] = dists[i][j+1] + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dists[len(s)][len(t)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func closestChoice(cmd string, choices []string) (string, int) {
|
||||||
|
if len(choices) == 0 {
|
||||||
|
return "", 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mincmd := -1
|
||||||
|
mindist := -1
|
||||||
|
|
||||||
|
for i, c := range choices {
|
||||||
|
l := levenshtein(cmd, c)
|
||||||
|
|
||||||
|
if mincmd < 0 || l < mindist {
|
||||||
|
mindist = l
|
||||||
|
mincmd = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return choices[mincmd], mindist
|
||||||
|
}
|
106
Godeps/_workspace/src/github.com/jessevdk/go-flags/command.go
generated
vendored
Normal file
106
Godeps/_workspace/src/github.com/jessevdk/go-flags/command.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
// Command represents an application command. Commands can be added to the
|
||||||
|
// parser (which itself is a command) and are selected/executed when its name
|
||||||
|
// is specified on the command line. The Command type embeds a Group and
|
||||||
|
// therefore also carries a set of command specific options.
|
||||||
|
type Command struct {
|
||||||
|
// Embedded, see Group for more information
|
||||||
|
*Group
|
||||||
|
|
||||||
|
// The name by which the command can be invoked
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// The active sub command (set by parsing) or nil
|
||||||
|
Active *Command
|
||||||
|
|
||||||
|
// Whether subcommands are optional
|
||||||
|
SubcommandsOptional bool
|
||||||
|
|
||||||
|
// Aliases for the command
|
||||||
|
Aliases []string
|
||||||
|
|
||||||
|
// Whether positional arguments are required
|
||||||
|
ArgsRequired bool
|
||||||
|
|
||||||
|
commands []*Command
|
||||||
|
hasBuiltinHelpGroup bool
|
||||||
|
args []*Arg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commander is an interface which can be implemented by any command added in
|
||||||
|
// the options. When implemented, the Execute method will be called for the last
|
||||||
|
// specified (sub)command providing the remaining command line arguments.
|
||||||
|
type Commander interface {
|
||||||
|
// Execute will be called for the last active (sub)command. The
|
||||||
|
// args argument contains the remaining command line arguments. The
|
||||||
|
// error that Execute returns will be eventually passed out of the
|
||||||
|
// Parse method of the Parser.
|
||||||
|
Execute(args []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage is an interface which can be implemented to show a custom usage string
|
||||||
|
// in the help message shown for a command.
|
||||||
|
type Usage interface {
|
||||||
|
// Usage is called for commands to allow customized printing of command
|
||||||
|
// usage in the generated help message.
|
||||||
|
Usage() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCommand adds a new command to the parser with the given name and data. The
|
||||||
|
// data needs to be a pointer to a struct from which the fields indicate which
|
||||||
|
// options are in the command. The provided data can implement the Command and
|
||||||
|
// Usage interfaces.
|
||||||
|
func (c *Command) AddCommand(command string, shortDescription string, longDescription string, data interface{}) (*Command, error) {
|
||||||
|
cmd := newCommand(command, shortDescription, longDescription, data)
|
||||||
|
|
||||||
|
cmd.parent = c
|
||||||
|
|
||||||
|
if err := cmd.scan(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.commands = append(c.commands, cmd)
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddGroup adds a new group to the command with the given name and data. The
|
||||||
|
// data needs to be a pointer to a struct from which the fields indicate which
|
||||||
|
// options are in the group.
|
||||||
|
func (c *Command) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
|
||||||
|
group := newGroup(shortDescription, longDescription, data)
|
||||||
|
|
||||||
|
group.parent = c
|
||||||
|
|
||||||
|
if err := group.scanType(c.scanSubcommandHandler(group)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.groups = append(c.groups, group)
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands returns a list of subcommands of this command.
|
||||||
|
func (c *Command) Commands() []*Command {
|
||||||
|
return c.commands
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find locates the subcommand with the given name and returns it. If no such
|
||||||
|
// command can be found Find will return nil.
|
||||||
|
func (c *Command) Find(name string) *Command {
|
||||||
|
for _, cc := range c.commands {
|
||||||
|
if cc.match(name) {
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Args returns a list of positional arguments associated with this command.
|
||||||
|
func (c *Command) Args() []*Arg {
|
||||||
|
ret := make([]*Arg, len(c.args))
|
||||||
|
copy(ret, c.args)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
250
Godeps/_workspace/src/github.com/jessevdk/go-flags/command_private.go
generated
vendored
Normal file
250
Godeps/_workspace/src/github.com/jessevdk/go-flags/command_private.go
generated
vendored
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lookup struct {
|
||||||
|
shortNames map[string]*Option
|
||||||
|
longNames map[string]*Option
|
||||||
|
|
||||||
|
commands map[string]*Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommand(name string, shortDescription string, longDescription string, data interface{}) *Command {
|
||||||
|
return &Command{
|
||||||
|
Group: newGroup(shortDescription, longDescription, data),
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) scanSubcommandHandler(parentg *Group) scanHandler {
|
||||||
|
f := func(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
|
||||||
|
mtag := newMultiTag(string(sfield.Tag))
|
||||||
|
|
||||||
|
if err := mtag.Parse(); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
positional := mtag.Get("positional-args")
|
||||||
|
|
||||||
|
if len(positional) != 0 {
|
||||||
|
stype := realval.Type()
|
||||||
|
|
||||||
|
for i := 0; i < stype.NumField(); i++ {
|
||||||
|
field := stype.Field(i)
|
||||||
|
|
||||||
|
m := newMultiTag((string(field.Tag)))
|
||||||
|
|
||||||
|
if err := m.Parse(); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := m.Get("positional-arg-name")
|
||||||
|
|
||||||
|
if len(name) == 0 {
|
||||||
|
name = field.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
arg := &Arg{
|
||||||
|
Name: name,
|
||||||
|
Description: m.Get("description"),
|
||||||
|
|
||||||
|
value: realval.Field(i),
|
||||||
|
tag: m,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.args = append(c.args, arg)
|
||||||
|
|
||||||
|
if len(mtag.Get("required")) != 0 {
|
||||||
|
c.ArgsRequired = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
subcommand := mtag.Get("command")
|
||||||
|
|
||||||
|
if len(subcommand) != 0 {
|
||||||
|
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
|
||||||
|
|
||||||
|
shortDescription := mtag.Get("description")
|
||||||
|
longDescription := mtag.Get("long-description")
|
||||||
|
subcommandsOptional := mtag.Get("subcommands-optional")
|
||||||
|
aliases := mtag.GetMany("alias")
|
||||||
|
|
||||||
|
subc, err := c.AddCommand(subcommand, shortDescription, longDescription, ptrval.Interface())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(subcommandsOptional) > 0 {
|
||||||
|
subc.SubcommandsOptional = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(aliases) > 0 {
|
||||||
|
subc.Aliases = aliases
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentg.scanSubGroupHandler(realval, sfield)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) scan() error {
|
||||||
|
return c.scanType(c.scanSubcommandHandler(c.Group))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) eachCommand(f func(*Command), recurse bool) {
|
||||||
|
f(c)
|
||||||
|
|
||||||
|
for _, cc := range c.commands {
|
||||||
|
if recurse {
|
||||||
|
cc.eachCommand(f, true)
|
||||||
|
} else {
|
||||||
|
f(cc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) eachActiveGroup(f func(cc *Command, g *Group)) {
|
||||||
|
c.eachGroup(func(g *Group) {
|
||||||
|
f(c, g)
|
||||||
|
})
|
||||||
|
|
||||||
|
if c.Active != nil {
|
||||||
|
c.Active.eachActiveGroup(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) addHelpGroups(showHelp func() error) {
|
||||||
|
if !c.hasBuiltinHelpGroup {
|
||||||
|
c.addHelpGroup(showHelp)
|
||||||
|
c.hasBuiltinHelpGroup = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cc := range c.commands {
|
||||||
|
cc.addHelpGroups(showHelp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) makeLookup() lookup {
|
||||||
|
ret := lookup{
|
||||||
|
shortNames: make(map[string]*Option),
|
||||||
|
longNames: make(map[string]*Option),
|
||||||
|
commands: make(map[string]*Command),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.eachGroup(func(g *Group) {
|
||||||
|
for _, option := range g.options {
|
||||||
|
if option.ShortName != 0 {
|
||||||
|
ret.shortNames[string(option.ShortName)] = option
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(option.LongName) > 0 {
|
||||||
|
ret.longNames[option.LongNameWithNamespace()] = option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, subcommand := range c.commands {
|
||||||
|
ret.commands[subcommand.Name] = subcommand
|
||||||
|
|
||||||
|
for _, a := range subcommand.Aliases {
|
||||||
|
ret.commands[a] = subcommand
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) groupByName(name string) *Group {
|
||||||
|
if grp := c.Group.groupByName(name); grp != nil {
|
||||||
|
return grp
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, subc := range c.commands {
|
||||||
|
prefix := subc.Name + "."
|
||||||
|
|
||||||
|
if strings.HasPrefix(name, prefix) {
|
||||||
|
if grp := subc.groupByName(name[len(prefix):]); grp != nil {
|
||||||
|
return grp
|
||||||
|
}
|
||||||
|
} else if name == subc.Name {
|
||||||
|
return subc.Group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type commandList []*Command
|
||||||
|
|
||||||
|
func (c commandList) Less(i, j int) bool {
|
||||||
|
return c[i].Name < c[j].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c commandList) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c commandList) Swap(i, j int) {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) sortedCommands() []*Command {
|
||||||
|
ret := make(commandList, len(c.commands))
|
||||||
|
copy(ret, c.commands)
|
||||||
|
|
||||||
|
sort.Sort(ret)
|
||||||
|
return []*Command(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) match(name string) bool {
|
||||||
|
if c.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range c.Aliases {
|
||||||
|
if v == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) hasCliOptions() bool {
|
||||||
|
ret := false
|
||||||
|
|
||||||
|
c.eachGroup(func(g *Group) {
|
||||||
|
if g.isBuiltinHelp {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range g.options {
|
||||||
|
if opt.canCli() {
|
||||||
|
ret = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) fillParseState(s *parseState) {
|
||||||
|
s.positional = make([]*Arg, len(c.args))
|
||||||
|
copy(s.positional, c.args)
|
||||||
|
|
||||||
|
s.lookup = c.makeLookup()
|
||||||
|
s.command = c
|
||||||
|
}
|
354
Godeps/_workspace/src/github.com/jessevdk/go-flags/command_test.go
generated
vendored
Normal file
354
Godeps/_workspace/src/github.com/jessevdk/go-flags/command_test.go
generated
vendored
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandInline(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
} `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if p.Active == nil {
|
||||||
|
t.Errorf("Expected active command")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Command.G {
|
||||||
|
t.Errorf("Expected Command.G to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Command.Find("cmd") != p.Active {
|
||||||
|
t.Errorf("Expected to find command `cmd' to be active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandInlineMulti(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
C1 struct {
|
||||||
|
} `command:"c1"`
|
||||||
|
|
||||||
|
C2 struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
} `command:"c2"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p, ret := assertParserSuccess(t, &opts, "-v", "c2", "-g")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if p.Active == nil {
|
||||||
|
t.Errorf("Expected active command")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.C2.G {
|
||||||
|
t.Errorf("Expected C2.G to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Command.Find("c1") == nil {
|
||||||
|
t.Errorf("Expected to find command `c1'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c2 := p.Command.Find("c2"); c2 == nil {
|
||||||
|
t.Errorf("Expected to find command `c2'")
|
||||||
|
} else if c2 != p.Active {
|
||||||
|
t.Errorf("Expected to find command `c2' to be active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandFlagOrder1(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
} `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrUnknownFlag, "unknown flag `g'", &opts, "-v", "-g", "cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandFlagOrder2(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
} `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrUnknownFlag, "unknown flag `v'", &opts, "cmd", "-v", "-g")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandEstimate(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Cmd1 struct {
|
||||||
|
} `command:"remove"`
|
||||||
|
|
||||||
|
Cmd2 struct {
|
||||||
|
} `command:"add"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, None)
|
||||||
|
_, err := p.ParseArgs([]string{})
|
||||||
|
|
||||||
|
assertError(t, err, ErrCommandRequired, "Please specify one command of: add or remove")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandEstimate2(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Cmd1 struct {
|
||||||
|
} `command:"remove"`
|
||||||
|
|
||||||
|
Cmd2 struct {
|
||||||
|
} `command:"add"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, None)
|
||||||
|
_, err := p.ParseArgs([]string{"rmive"})
|
||||||
|
|
||||||
|
assertError(t, err, ErrUnknownCommand, "Unknown command `rmive', did you mean `remove'?")
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCommand struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
Executed bool
|
||||||
|
EArgs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *testCommand) Execute(args []string) error {
|
||||||
|
c.Executed = true
|
||||||
|
c.EArgs = args
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandExecute(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Command testCommand `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseSuccess(t, &opts, "-v", "cmd", "-g", "a", "b")
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Command.Executed {
|
||||||
|
t.Errorf("Did not execute command")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Command.G {
|
||||||
|
t.Errorf("Expected Command.C to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStringArray(t, opts.Command.EArgs, []string{"a", "b"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandClosest(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Cmd1 struct {
|
||||||
|
} `command:"remove"`
|
||||||
|
|
||||||
|
Cmd2 struct {
|
||||||
|
} `command:"add"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
args := assertParseFail(t, ErrUnknownCommand, "Unknown command `addd', did you mean `add'?", &opts, "-v", "addd")
|
||||||
|
|
||||||
|
assertStringArray(t, args, []string{"addd"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandAdd(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
var cmd = struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, Default)
|
||||||
|
c, err := p.AddCommand("cmd", "", "", &cmd)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := p.ParseArgs([]string{"-v", "cmd", "-g", "rest"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"rest"})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cmd.G {
|
||||||
|
t.Errorf("Expected Command.G to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Command.Find("cmd") != c {
|
||||||
|
t.Errorf("Expected to find command `cmd'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Commands()[0] != c {
|
||||||
|
t.Errorf("Expected command %#v, but got %#v", c, p.Commands()[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Options()[0].ShortName != 'g' {
|
||||||
|
t.Errorf("Expected short name `g' but got %v", c.Options()[0].ShortName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandNestedInline(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
|
||||||
|
Nested struct {
|
||||||
|
N string `long:"n"`
|
||||||
|
} `command:"nested"`
|
||||||
|
} `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p, ret := assertParserSuccess(t, &opts, "-v", "cmd", "-g", "nested", "--n", "n", "rest")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"rest"})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Command.G {
|
||||||
|
t.Errorf("Expected Command.G to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertString(t, opts.Command.Nested.N, "n")
|
||||||
|
|
||||||
|
if c := p.Command.Find("cmd"); c == nil {
|
||||||
|
t.Errorf("Expected to find command `cmd'")
|
||||||
|
} else {
|
||||||
|
if c != p.Active {
|
||||||
|
t.Errorf("Expected `cmd' to be the active parser command")
|
||||||
|
}
|
||||||
|
|
||||||
|
if nested := c.Find("nested"); nested == nil {
|
||||||
|
t.Errorf("Expected to find command `nested'")
|
||||||
|
} else if nested != c.Active {
|
||||||
|
t.Errorf("Expected to find command `nested' to be the active `cmd' command")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiredOnCommand(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v" required:"true"`
|
||||||
|
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
} `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flag `%cv' was not specified", defaultShortOptDelimiter), &opts, "cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequiredAllOnCommand(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v" required:"true"`
|
||||||
|
Missing bool `long:"missing" required:"true"`
|
||||||
|
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
} `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flags `%smissing' and `%cv' were not specified", defaultLongOptDelimiter, defaultShortOptDelimiter), &opts, "cmd")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultOnCommand(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g" default:"true"`
|
||||||
|
} `command:"cmd"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseSuccess(t, &opts, "cmd")
|
||||||
|
|
||||||
|
if !opts.Command.G {
|
||||||
|
t.Errorf("Expected G to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubcommandsOptional(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Cmd1 struct {
|
||||||
|
} `command:"remove"`
|
||||||
|
|
||||||
|
Cmd2 struct {
|
||||||
|
} `command:"add"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, None)
|
||||||
|
p.SubcommandsOptional = true
|
||||||
|
|
||||||
|
_, err := p.ParseArgs([]string{"-v"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommandAlias(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Command struct {
|
||||||
|
G bool `short:"g" default:"true"`
|
||||||
|
} `command:"cmd" alias:"cm"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseSuccess(t, &opts, "cm")
|
||||||
|
|
||||||
|
if !opts.Command.G {
|
||||||
|
t.Errorf("Expected G to be true")
|
||||||
|
}
|
||||||
|
}
|
304
Godeps/_workspace/src/github.com/jessevdk/go-flags/completion.go
generated
vendored
Normal file
304
Godeps/_workspace/src/github.com/jessevdk/go-flags/completion.go
generated
vendored
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Completion is a type containing information of a completion.
|
||||||
|
type Completion struct {
|
||||||
|
// The completed item
|
||||||
|
Item string
|
||||||
|
|
||||||
|
// A description of the completed item (optional)
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
type completions []Completion
|
||||||
|
|
||||||
|
func (c completions) Len() int {
|
||||||
|
return len(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c completions) Less(i, j int) bool {
|
||||||
|
return c[i].Item < c[j].Item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c completions) Swap(i, j int) {
|
||||||
|
c[i], c[j] = c[j], c[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completer is an interface which can be implemented by types
|
||||||
|
// to provide custom command line argument completion.
|
||||||
|
type Completer interface {
|
||||||
|
// Complete receives a prefix representing a (partial) value
|
||||||
|
// for its type and should provide a list of possible valid
|
||||||
|
// completions.
|
||||||
|
Complete(match string) []Completion
|
||||||
|
}
|
||||||
|
|
||||||
|
type completion struct {
|
||||||
|
parser *Parser
|
||||||
|
|
||||||
|
ShowDescriptions bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filename is a string alias which provides filename completion.
|
||||||
|
type Filename string
|
||||||
|
|
||||||
|
func completionsWithoutDescriptions(items []string) []Completion {
|
||||||
|
ret := make([]Completion, len(items))
|
||||||
|
|
||||||
|
for i, v := range items {
|
||||||
|
ret[i].Item = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete returns a list of existing files with the given
|
||||||
|
// prefix.
|
||||||
|
func (f *Filename) Complete(match string) []Completion {
|
||||||
|
ret, _ := filepath.Glob(match + "*")
|
||||||
|
return completionsWithoutDescriptions(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) skipPositional(s *parseState, n int) {
|
||||||
|
if n >= len(s.positional) {
|
||||||
|
s.positional = nil
|
||||||
|
} else {
|
||||||
|
s.positional = s.positional[n:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) completeOptionNames(names map[string]*Option, prefix string, match string) []Completion {
|
||||||
|
n := make([]Completion, 0, len(names))
|
||||||
|
|
||||||
|
for k, opt := range names {
|
||||||
|
if strings.HasPrefix(k, match) {
|
||||||
|
n = append(n, Completion{
|
||||||
|
Item: prefix + k,
|
||||||
|
Description: opt.Description,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) completeLongNames(s *parseState, prefix string, match string) []Completion {
|
||||||
|
return c.completeOptionNames(s.lookup.longNames, prefix, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) completeShortNames(s *parseState, prefix string, match string) []Completion {
|
||||||
|
if len(match) != 0 {
|
||||||
|
return []Completion{
|
||||||
|
Completion{
|
||||||
|
Item: prefix + match,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.completeOptionNames(s.lookup.shortNames, prefix, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) completeCommands(s *parseState, match string) []Completion {
|
||||||
|
n := make([]Completion, 0, len(s.command.commands))
|
||||||
|
|
||||||
|
for _, cmd := range s.command.commands {
|
||||||
|
if cmd.data != c && strings.HasPrefix(cmd.Name, match) {
|
||||||
|
n = append(n, Completion{
|
||||||
|
Item: cmd.Name,
|
||||||
|
Description: cmd.ShortDescription,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) completeValue(value reflect.Value, prefix string, match string) []Completion {
|
||||||
|
i := value.Interface()
|
||||||
|
|
||||||
|
var ret []Completion
|
||||||
|
|
||||||
|
if cmp, ok := i.(Completer); ok {
|
||||||
|
ret = cmp.Complete(match)
|
||||||
|
} else if value.CanAddr() {
|
||||||
|
if cmp, ok = value.Addr().Interface().(Completer); ok {
|
||||||
|
ret = cmp.Complete(match)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range ret {
|
||||||
|
ret[i].Item = prefix + v.Item
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) completeArg(arg *Arg, prefix string, match string) []Completion {
|
||||||
|
if arg.isRemaining() {
|
||||||
|
// For remaining positional args (that are parsed into a slice), complete
|
||||||
|
// based on the element type.
|
||||||
|
return c.completeValue(reflect.New(arg.value.Type().Elem()), prefix, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.completeValue(arg.value, prefix, match)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) complete(args []string) []Completion {
|
||||||
|
if len(args) == 0 {
|
||||||
|
args = []string{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &parseState{
|
||||||
|
args: args,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.parser.fillParseState(s)
|
||||||
|
|
||||||
|
var opt *Option
|
||||||
|
|
||||||
|
for len(s.args) > 1 {
|
||||||
|
arg := s.pop()
|
||||||
|
|
||||||
|
if (c.parser.Options&PassDoubleDash) != None && arg == "--" {
|
||||||
|
opt = nil
|
||||||
|
c.skipPositional(s, len(s.args)-1)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if argumentIsOption(arg) {
|
||||||
|
prefix, optname, islong := stripOptionPrefix(arg)
|
||||||
|
optname, _, argument := splitOption(prefix, optname, islong)
|
||||||
|
|
||||||
|
if argument == nil {
|
||||||
|
var o *Option
|
||||||
|
canarg := true
|
||||||
|
|
||||||
|
if islong {
|
||||||
|
o = s.lookup.longNames[optname]
|
||||||
|
} else {
|
||||||
|
for i, r := range optname {
|
||||||
|
sname := string(r)
|
||||||
|
o = s.lookup.shortNames[sname]
|
||||||
|
|
||||||
|
if o == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 && o.canArgument() && len(optname) != len(sname) {
|
||||||
|
canarg = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if o == nil && (c.parser.Options&PassAfterNonOption) != None {
|
||||||
|
opt = nil
|
||||||
|
c.skipPositional(s, len(s.args)-1)
|
||||||
|
|
||||||
|
break
|
||||||
|
} else if o != nil && o.canArgument() && !o.OptionalArgument && canarg {
|
||||||
|
if len(s.args) > 1 {
|
||||||
|
s.pop()
|
||||||
|
} else {
|
||||||
|
opt = o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(s.positional) > 0 {
|
||||||
|
if !s.positional[0].isRemaining() {
|
||||||
|
// Don't advance beyond a remaining positional arg (because
|
||||||
|
// it consumes all subsequent args).
|
||||||
|
s.positional = s.positional[1:]
|
||||||
|
}
|
||||||
|
} else if cmd, ok := s.lookup.commands[arg]; ok {
|
||||||
|
cmd.fillParseState(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
opt = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastarg := s.args[len(s.args)-1]
|
||||||
|
var ret []Completion
|
||||||
|
|
||||||
|
if opt != nil {
|
||||||
|
// Completion for the argument of 'opt'
|
||||||
|
ret = c.completeValue(opt.value, "", lastarg)
|
||||||
|
} else if argumentStartsOption(lastarg) {
|
||||||
|
// Complete the option
|
||||||
|
prefix, optname, islong := stripOptionPrefix(lastarg)
|
||||||
|
optname, split, argument := splitOption(prefix, optname, islong)
|
||||||
|
|
||||||
|
if argument == nil && !islong {
|
||||||
|
rname, n := utf8.DecodeRuneInString(optname)
|
||||||
|
sname := string(rname)
|
||||||
|
|
||||||
|
if opt := s.lookup.shortNames[sname]; opt != nil && opt.canArgument() {
|
||||||
|
ret = c.completeValue(opt.value, prefix+sname, optname[n:])
|
||||||
|
} else {
|
||||||
|
ret = c.completeShortNames(s, prefix, optname)
|
||||||
|
}
|
||||||
|
} else if argument != nil {
|
||||||
|
if islong {
|
||||||
|
opt = s.lookup.longNames[optname]
|
||||||
|
} else {
|
||||||
|
opt = s.lookup.shortNames[optname]
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt != nil {
|
||||||
|
ret = c.completeValue(opt.value, prefix+optname+split, *argument)
|
||||||
|
}
|
||||||
|
} else if islong {
|
||||||
|
ret = c.completeLongNames(s, prefix, optname)
|
||||||
|
} else {
|
||||||
|
ret = c.completeShortNames(s, prefix, optname)
|
||||||
|
}
|
||||||
|
} else if len(s.positional) > 0 {
|
||||||
|
// Complete for positional argument
|
||||||
|
ret = c.completeArg(s.positional[0], "", lastarg)
|
||||||
|
} else if len(s.command.commands) > 0 {
|
||||||
|
// Complete for command
|
||||||
|
ret = c.completeCommands(s, lastarg)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(completions(ret))
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *completion) execute(args []string) {
|
||||||
|
ret := c.complete(args)
|
||||||
|
|
||||||
|
if c.ShowDescriptions && len(ret) > 1 {
|
||||||
|
maxl := 0
|
||||||
|
|
||||||
|
for _, v := range ret {
|
||||||
|
if len(v.Item) > maxl {
|
||||||
|
maxl = len(v.Item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range ret {
|
||||||
|
fmt.Printf("%s", v.Item)
|
||||||
|
|
||||||
|
if len(v.Description) > 0 {
|
||||||
|
fmt.Printf("%s # %s", strings.Repeat(" ", maxl-len(v.Item)), v.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, v := range ret {
|
||||||
|
fmt.Println(v.Item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
289
Godeps/_workspace/src/github.com/jessevdk/go-flags/completion_test.go
generated
vendored
Normal file
289
Godeps/_workspace/src/github.com/jessevdk/go-flags/completion_test.go
generated
vendored
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestComplete struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestComplete) Complete(match string) []Completion {
|
||||||
|
options := []string{
|
||||||
|
"hello world",
|
||||||
|
"hello universe",
|
||||||
|
"hello multiverse",
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := make([]Completion, 0, len(options))
|
||||||
|
|
||||||
|
for _, o := range options {
|
||||||
|
if strings.HasPrefix(o, match) {
|
||||||
|
ret = append(ret, Completion{
|
||||||
|
Item: o,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
var completionTestOptions struct {
|
||||||
|
Verbose bool `short:"v" long:"verbose" description:"Verbose messages"`
|
||||||
|
Debug bool `short:"d" long:"debug" description:"Enable debug"`
|
||||||
|
Version bool `long:"version" description:"Show version"`
|
||||||
|
Required bool `long:"required" required:"true" description:"This is required"`
|
||||||
|
|
||||||
|
AddCommand struct {
|
||||||
|
Positional struct {
|
||||||
|
Filename Filename
|
||||||
|
} `positional-args:"yes"`
|
||||||
|
} `command:"add" description:"add an item"`
|
||||||
|
|
||||||
|
AddMultiCommand struct {
|
||||||
|
Positional struct {
|
||||||
|
Filename []Filename
|
||||||
|
} `positional-args:"yes"`
|
||||||
|
} `command:"add-multi" description:"add multiple items"`
|
||||||
|
|
||||||
|
RemoveCommand struct {
|
||||||
|
Other bool `short:"o"`
|
||||||
|
File Filename `short:"f" long:"filename"`
|
||||||
|
} `command:"rm" description:"remove an item"`
|
||||||
|
|
||||||
|
RenameCommand struct {
|
||||||
|
Completed TestComplete `short:"c" long:"completed"`
|
||||||
|
} `command:"rename" description:"rename an item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type completionTest struct {
|
||||||
|
Args []string
|
||||||
|
Completed []string
|
||||||
|
ShowDescriptions bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var completionTests []completionTest
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_, sourcefile, _, _ := runtime.Caller(0)
|
||||||
|
completionTestSourcedir := filepath.Join(filepath.SplitList(path.Dir(sourcefile))...)
|
||||||
|
|
||||||
|
completionTestFilename := []string{filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion_test.go")}
|
||||||
|
|
||||||
|
completionTests = []completionTest{
|
||||||
|
{
|
||||||
|
// Short names
|
||||||
|
[]string{"-"},
|
||||||
|
[]string{"-d", "-v"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Short names concatenated
|
||||||
|
[]string{"-dv"},
|
||||||
|
[]string{"-dv"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Long names
|
||||||
|
[]string{"--"},
|
||||||
|
[]string{"--debug", "--required", "--verbose", "--version"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Long names with descriptions
|
||||||
|
[]string{"--"},
|
||||||
|
[]string{
|
||||||
|
"--debug # Enable debug",
|
||||||
|
"--required # This is required",
|
||||||
|
"--verbose # Verbose messages",
|
||||||
|
"--version # Show version",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Long names partial
|
||||||
|
[]string{"--ver"},
|
||||||
|
[]string{"--verbose", "--version"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Commands
|
||||||
|
[]string{""},
|
||||||
|
[]string{"add", "add-multi", "rename", "rm"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Commands with descriptions
|
||||||
|
[]string{""},
|
||||||
|
[]string{
|
||||||
|
"add # add an item",
|
||||||
|
"add-multi # add multiple items",
|
||||||
|
"rename # rename an item",
|
||||||
|
"rm # remove an item",
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Commands partial
|
||||||
|
[]string{"r"},
|
||||||
|
[]string{"rename", "rm"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Positional filename
|
||||||
|
[]string{"add", filepath.Join(completionTestSourcedir, "completion")},
|
||||||
|
completionTestFilename,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Multiple positional filename (1 arg)
|
||||||
|
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion")},
|
||||||
|
completionTestFilename,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multiple positional filename (2 args)
|
||||||
|
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
|
||||||
|
completionTestFilename,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Multiple positional filename (3 args)
|
||||||
|
[]string{"add-multi", filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion.go"), filepath.Join(completionTestSourcedir, "completion")},
|
||||||
|
completionTestFilename,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Flag filename
|
||||||
|
[]string{"rm", "-f", path.Join(completionTestSourcedir, "completion")},
|
||||||
|
completionTestFilename,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Flag short concat last filename
|
||||||
|
[]string{"rm", "-of", path.Join(completionTestSourcedir, "completion")},
|
||||||
|
completionTestFilename,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Flag concat filename
|
||||||
|
[]string{"rm", "-f" + path.Join(completionTestSourcedir, "completion")},
|
||||||
|
[]string{"-f" + completionTestFilename[0], "-f" + completionTestFilename[1]},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Flag equal concat filename
|
||||||
|
[]string{"rm", "-f=" + path.Join(completionTestSourcedir, "completion")},
|
||||||
|
[]string{"-f=" + completionTestFilename[0], "-f=" + completionTestFilename[1]},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Flag concat long filename
|
||||||
|
[]string{"rm", "--filename=" + path.Join(completionTestSourcedir, "completion")},
|
||||||
|
[]string{"--filename=" + completionTestFilename[0], "--filename=" + completionTestFilename[1]},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Flag long filename
|
||||||
|
[]string{"rm", "--filename", path.Join(completionTestSourcedir, "completion")},
|
||||||
|
completionTestFilename,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
// Custom completed
|
||||||
|
[]string{"rename", "-c", "hello un"},
|
||||||
|
[]string{"hello universe"},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCompletion(t *testing.T) {
|
||||||
|
p := NewParser(&completionTestOptions, Default)
|
||||||
|
c := &completion{parser: p}
|
||||||
|
|
||||||
|
for _, test := range completionTests {
|
||||||
|
if test.ShowDescriptions {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := c.complete(test.Args)
|
||||||
|
items := make([]string, len(ret))
|
||||||
|
|
||||||
|
for i, v := range ret {
|
||||||
|
items[i] = v.Item
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(items, test.Completed) {
|
||||||
|
t.Errorf("Args: %#v, %#v\n Expected: %#v\n Got: %#v", test.Args, test.ShowDescriptions, test.Completed, items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserCompletion(t *testing.T) {
|
||||||
|
for _, test := range completionTests {
|
||||||
|
if test.ShowDescriptions {
|
||||||
|
os.Setenv("GO_FLAGS_COMPLETION", "verbose")
|
||||||
|
} else {
|
||||||
|
os.Setenv("GO_FLAGS_COMPLETION", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp := os.Stdout
|
||||||
|
|
||||||
|
r, w, _ := os.Pipe()
|
||||||
|
os.Stdout = w
|
||||||
|
|
||||||
|
out := make(chan string)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
io.Copy(&buf, r)
|
||||||
|
|
||||||
|
out <- buf.String()
|
||||||
|
}()
|
||||||
|
|
||||||
|
p := NewParser(&completionTestOptions, None)
|
||||||
|
|
||||||
|
_, err := p.ParseArgs(test.Args)
|
||||||
|
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
os.Stdout = tmp
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := strings.Split(strings.Trim(<-out, "\n"), "\n")
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(got, test.Completed) {
|
||||||
|
t.Errorf("Expected: %#v\nGot: %#v", test.Completed, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Setenv("GO_FLAGS_COMPLETION", "")
|
||||||
|
}
|
357
Godeps/_workspace/src/github.com/jessevdk/go-flags/convert.go
generated
vendored
Normal file
357
Godeps/_workspace/src/github.com/jessevdk/go-flags/convert.go
generated
vendored
Normal file
@ -0,0 +1,357 @@
|
|||||||
|
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Marshaler is the interface implemented by types that can marshal themselves
|
||||||
|
// to a string representation of the flag.
|
||||||
|
type Marshaler interface {
|
||||||
|
// MarshalFlag marshals a flag value to its string representation.
|
||||||
|
MarshalFlag() (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is the interface implemented by types that can unmarshal a flag
|
||||||
|
// argument to themselves. The provided value is directly passed from the
|
||||||
|
// command line.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
// UnmarshalFlag unmarshals a string value representation to the flag
|
||||||
|
// value (which therefore needs to be a pointer receiver).
|
||||||
|
UnmarshalFlag(value string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBase(options multiTag, base int) (int, error) {
|
||||||
|
sbase := options.Get("base")
|
||||||
|
|
||||||
|
var err error
|
||||||
|
var ivbase int64
|
||||||
|
|
||||||
|
if sbase != "" {
|
||||||
|
ivbase, err = strconv.ParseInt(sbase, 10, 32)
|
||||||
|
base = int(ivbase)
|
||||||
|
}
|
||||||
|
|
||||||
|
return base, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertMarshal(val reflect.Value) (bool, string, error) {
|
||||||
|
// Check first for the Marshaler interface
|
||||||
|
if val.Type().NumMethod() > 0 && val.CanInterface() {
|
||||||
|
if marshaler, ok := val.Interface().(Marshaler); ok {
|
||||||
|
ret, err := marshaler.MarshalFlag()
|
||||||
|
return true, ret, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertToString(val reflect.Value, options multiTag) (string, error) {
|
||||||
|
if ok, ret, err := convertMarshal(val); ok {
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tp := val.Type()
|
||||||
|
|
||||||
|
// Support for time.Duration
|
||||||
|
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
|
||||||
|
stringer := val.Interface().(fmt.Stringer)
|
||||||
|
return stringer.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tp.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return val.String(), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
if val.Bool() {
|
||||||
|
return "true", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "false", nil
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
base, err := getBase(options, 10)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.FormatInt(val.Int(), base), nil
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
base, err := getBase(options, 10)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.FormatUint(val.Uint(), base), nil
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return strconv.FormatFloat(val.Float(), 'g', -1, tp.Bits()), nil
|
||||||
|
case reflect.Slice:
|
||||||
|
if val.Len() == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := "["
|
||||||
|
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
if i != 0 {
|
||||||
|
ret += ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := convertToString(val.Index(i), options)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += item
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret + "]", nil
|
||||||
|
case reflect.Map:
|
||||||
|
ret := "{"
|
||||||
|
|
||||||
|
for i, key := range val.MapKeys() {
|
||||||
|
if i != 0 {
|
||||||
|
ret += ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
keyitem, err := convertToString(key, options)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := convertToString(val.MapIndex(key), options)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += keyitem + ":" + item
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret + "}", nil
|
||||||
|
case reflect.Ptr:
|
||||||
|
return convertToString(reflect.Indirect(val), options)
|
||||||
|
case reflect.Interface:
|
||||||
|
if !val.IsNil() {
|
||||||
|
return convertToString(val.Elem(), options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertUnmarshal(val string, retval reflect.Value) (bool, error) {
|
||||||
|
if retval.Type().NumMethod() > 0 && retval.CanInterface() {
|
||||||
|
if unmarshaler, ok := retval.Interface().(Unmarshaler); ok {
|
||||||
|
return true, unmarshaler.UnmarshalFlag(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if retval.Type().Kind() != reflect.Ptr && retval.CanAddr() {
|
||||||
|
return convertUnmarshal(val, retval.Addr())
|
||||||
|
}
|
||||||
|
|
||||||
|
if retval.Type().Kind() == reflect.Interface && !retval.IsNil() {
|
||||||
|
return convertUnmarshal(val, retval.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convert(val string, retval reflect.Value, options multiTag) error {
|
||||||
|
if ok, err := convertUnmarshal(val, retval); ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tp := retval.Type()
|
||||||
|
|
||||||
|
// Support for time.Duration
|
||||||
|
if tp == reflect.TypeOf((*time.Duration)(nil)).Elem() {
|
||||||
|
parsed, err := time.ParseDuration(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.SetInt(int64(parsed))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tp.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
retval.SetString(val)
|
||||||
|
case reflect.Bool:
|
||||||
|
if val == "" {
|
||||||
|
retval.SetBool(true)
|
||||||
|
} else {
|
||||||
|
b, err := strconv.ParseBool(val)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.SetBool(b)
|
||||||
|
}
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
base, err := getBase(options, 10)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := strconv.ParseInt(val, base, tp.Bits())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.SetInt(parsed)
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
base, err := getBase(options, 10)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := strconv.ParseUint(val, base, tp.Bits())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.SetUint(parsed)
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
parsed, err := strconv.ParseFloat(val, tp.Bits())
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.SetFloat(parsed)
|
||||||
|
case reflect.Slice:
|
||||||
|
elemtp := tp.Elem()
|
||||||
|
|
||||||
|
elemvalptr := reflect.New(elemtp)
|
||||||
|
elemval := reflect.Indirect(elemvalptr)
|
||||||
|
|
||||||
|
if err := convert(val, elemval, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.Set(reflect.Append(retval, elemval))
|
||||||
|
case reflect.Map:
|
||||||
|
parts := strings.SplitN(val, ":", 2)
|
||||||
|
|
||||||
|
key := parts[0]
|
||||||
|
var value string
|
||||||
|
|
||||||
|
if len(parts) == 2 {
|
||||||
|
value = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
keytp := tp.Key()
|
||||||
|
keyval := reflect.New(keytp)
|
||||||
|
|
||||||
|
if err := convert(key, keyval, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
valuetp := tp.Elem()
|
||||||
|
valueval := reflect.New(valuetp)
|
||||||
|
|
||||||
|
if err := convert(value, valueval, options); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if retval.IsNil() {
|
||||||
|
retval.Set(reflect.MakeMap(tp))
|
||||||
|
}
|
||||||
|
|
||||||
|
retval.SetMapIndex(reflect.Indirect(keyval), reflect.Indirect(valueval))
|
||||||
|
case reflect.Ptr:
|
||||||
|
if retval.IsNil() {
|
||||||
|
retval.Set(reflect.New(retval.Type().Elem()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return convert(val, reflect.Indirect(retval), options)
|
||||||
|
case reflect.Interface:
|
||||||
|
if !retval.IsNil() {
|
||||||
|
return convert(val, retval.Elem(), options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPrint(s string) bool {
|
||||||
|
for _, c := range s {
|
||||||
|
if !strconv.IsPrint(c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func quoteIfNeeded(s string) string {
|
||||||
|
if !isPrint(s) {
|
||||||
|
return strconv.Quote(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func unquoteIfPossible(s string) (string, error) {
|
||||||
|
if len(s) == 0 || s[0] != '"' {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.Unquote(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapText(s string, l int, prefix string) string {
|
||||||
|
// Basic text wrapping of s at spaces to fit in l
|
||||||
|
var ret string
|
||||||
|
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
|
||||||
|
for len(s) > l {
|
||||||
|
// Try to split on space
|
||||||
|
suffix := ""
|
||||||
|
|
||||||
|
pos := strings.LastIndex(s[:l], " ")
|
||||||
|
|
||||||
|
if pos < 0 {
|
||||||
|
pos = l - 1
|
||||||
|
suffix = "-\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ret) != 0 {
|
||||||
|
ret += "\n" + prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += strings.TrimSpace(s[:pos]) + suffix
|
||||||
|
s = strings.TrimSpace(s[pos:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s) > 0 {
|
||||||
|
if len(ret) != 0 {
|
||||||
|
ret += "\n" + prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret + s
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
175
Godeps/_workspace/src/github.com/jessevdk/go-flags/convert_test.go
generated
vendored
Normal file
175
Godeps/_workspace/src/github.com/jessevdk/go-flags/convert_test.go
generated
vendored
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func expectConvert(t *testing.T, o *Option, expected string) {
|
||||||
|
s, err := convertToString(o.value, o.tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assertString(t, s, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertToString(t *testing.T) {
|
||||||
|
d, _ := time.ParseDuration("1h2m4s")
|
||||||
|
|
||||||
|
var opts = struct {
|
||||||
|
String string `long:"string"`
|
||||||
|
|
||||||
|
Int int `long:"int"`
|
||||||
|
Int8 int8 `long:"int8"`
|
||||||
|
Int16 int16 `long:"int16"`
|
||||||
|
Int32 int32 `long:"int32"`
|
||||||
|
Int64 int64 `long:"int64"`
|
||||||
|
|
||||||
|
Uint uint `long:"uint"`
|
||||||
|
Uint8 uint8 `long:"uint8"`
|
||||||
|
Uint16 uint16 `long:"uint16"`
|
||||||
|
Uint32 uint32 `long:"uint32"`
|
||||||
|
Uint64 uint64 `long:"uint64"`
|
||||||
|
|
||||||
|
Float32 float32 `long:"float32"`
|
||||||
|
Float64 float64 `long:"float64"`
|
||||||
|
|
||||||
|
Duration time.Duration `long:"duration"`
|
||||||
|
|
||||||
|
Bool bool `long:"bool"`
|
||||||
|
|
||||||
|
IntSlice []int `long:"int-slice"`
|
||||||
|
IntFloatMap map[int]float64 `long:"int-float-map"`
|
||||||
|
|
||||||
|
PtrBool *bool `long:"ptr-bool"`
|
||||||
|
Interface interface{} `long:"interface"`
|
||||||
|
|
||||||
|
Int32Base int32 `long:"int32-base" base:"16"`
|
||||||
|
Uint32Base uint32 `long:"uint32-base" base:"16"`
|
||||||
|
}{
|
||||||
|
"string",
|
||||||
|
|
||||||
|
-2,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
3,
|
||||||
|
4,
|
||||||
|
5,
|
||||||
|
|
||||||
|
1.2,
|
||||||
|
-3.4,
|
||||||
|
|
||||||
|
d,
|
||||||
|
true,
|
||||||
|
|
||||||
|
[]int{-3, 4, -2},
|
||||||
|
map[int]float64{-2: 4.5},
|
||||||
|
|
||||||
|
new(bool),
|
||||||
|
float32(5.2),
|
||||||
|
|
||||||
|
-5823,
|
||||||
|
4232,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewNamedParser("test", Default)
|
||||||
|
grp, _ := p.AddGroup("test group", "", &opts)
|
||||||
|
|
||||||
|
expects := []string{
|
||||||
|
"string",
|
||||||
|
"-2",
|
||||||
|
"-1",
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
|
||||||
|
"1.2",
|
||||||
|
"-3.4",
|
||||||
|
|
||||||
|
"1h2m4s",
|
||||||
|
"true",
|
||||||
|
|
||||||
|
"[-3, 4, -2]",
|
||||||
|
"{-2:4.5}",
|
||||||
|
|
||||||
|
"false",
|
||||||
|
"5.2",
|
||||||
|
|
||||||
|
"-16bf",
|
||||||
|
"1088",
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range grp.Options() {
|
||||||
|
expectConvert(t, v, expects[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertToStringInvalidIntBase(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Int int `long:"int" base:"no"`
|
||||||
|
}{
|
||||||
|
2,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewNamedParser("test", Default)
|
||||||
|
grp, _ := p.AddGroup("test group", "", &opts)
|
||||||
|
o := grp.Options()[0]
|
||||||
|
|
||||||
|
_, err := convertToString(o.value, o.tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = newErrorf(ErrMarshal, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertToStringInvalidUintBase(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Uint uint `long:"uint" base:"no"`
|
||||||
|
}{
|
||||||
|
2,
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewNamedParser("test", Default)
|
||||||
|
grp, _ := p.AddGroup("test group", "", &opts)
|
||||||
|
o := grp.Options()[0]
|
||||||
|
|
||||||
|
_, err := convertToString(o.value, o.tag)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = newErrorf(ErrMarshal, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertError(t, err, ErrMarshal, "strconv.ParseInt: parsing \"no\": invalid syntax")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrapText(t *testing.T) {
|
||||||
|
s := "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||||
|
|
||||||
|
got := wrapText(s, 60, " ")
|
||||||
|
expected := `Lorem ipsum dolor sit amet, consectetur adipisicing elit,
|
||||||
|
sed do eiusmod tempor incididunt ut labore et dolore magna
|
||||||
|
aliqua. Ut enim ad minim veniam, quis nostrud exercitation
|
||||||
|
ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit
|
||||||
|
esse cillum dolore eu fugiat nulla pariatur. Excepteur sint
|
||||||
|
occaecat cupidatat non proident, sunt in culpa qui officia
|
||||||
|
deserunt mollit anim id est laborum.`
|
||||||
|
|
||||||
|
assertDiff(t, got, expected, "wrapped text")
|
||||||
|
}
|
123
Godeps/_workspace/src/github.com/jessevdk/go-flags/error.go
generated
vendored
Normal file
123
Godeps/_workspace/src/github.com/jessevdk/go-flags/error.go
generated
vendored
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrorType represents the type of error.
|
||||||
|
type ErrorType uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ErrUnknown indicates a generic error.
|
||||||
|
ErrUnknown ErrorType = iota
|
||||||
|
|
||||||
|
// ErrExpectedArgument indicates that an argument was expected.
|
||||||
|
ErrExpectedArgument
|
||||||
|
|
||||||
|
// ErrUnknownFlag indicates an unknown flag.
|
||||||
|
ErrUnknownFlag
|
||||||
|
|
||||||
|
// ErrUnknownGroup indicates an unknown group.
|
||||||
|
ErrUnknownGroup
|
||||||
|
|
||||||
|
// ErrMarshal indicates a marshalling error while converting values.
|
||||||
|
ErrMarshal
|
||||||
|
|
||||||
|
// ErrHelp indicates that the built-in help was shown (the error
|
||||||
|
// contains the help message).
|
||||||
|
ErrHelp
|
||||||
|
|
||||||
|
// ErrNoArgumentForBool indicates that an argument was given for a
|
||||||
|
// boolean flag (which don't not take any arguments).
|
||||||
|
ErrNoArgumentForBool
|
||||||
|
|
||||||
|
// ErrRequired indicates that a required flag was not provided.
|
||||||
|
ErrRequired
|
||||||
|
|
||||||
|
// ErrShortNameTooLong indicates that a short flag name was specified,
|
||||||
|
// longer than one character.
|
||||||
|
ErrShortNameTooLong
|
||||||
|
|
||||||
|
// ErrDuplicatedFlag indicates that a short or long flag has been
|
||||||
|
// defined more than once
|
||||||
|
ErrDuplicatedFlag
|
||||||
|
|
||||||
|
// ErrTag indicates an error while parsing flag tags.
|
||||||
|
ErrTag
|
||||||
|
|
||||||
|
// ErrCommandRequired indicates that a command was required but not
|
||||||
|
// specified
|
||||||
|
ErrCommandRequired
|
||||||
|
|
||||||
|
// ErrUnknownCommand indicates that an unknown command was specified.
|
||||||
|
ErrUnknownCommand
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e ErrorType) String() string {
|
||||||
|
switch e {
|
||||||
|
case ErrUnknown:
|
||||||
|
return "unknown"
|
||||||
|
case ErrExpectedArgument:
|
||||||
|
return "expected argument"
|
||||||
|
case ErrUnknownFlag:
|
||||||
|
return "unknown flag"
|
||||||
|
case ErrUnknownGroup:
|
||||||
|
return "unknown group"
|
||||||
|
case ErrMarshal:
|
||||||
|
return "marshal"
|
||||||
|
case ErrHelp:
|
||||||
|
return "help"
|
||||||
|
case ErrNoArgumentForBool:
|
||||||
|
return "no argument for bool"
|
||||||
|
case ErrRequired:
|
||||||
|
return "required"
|
||||||
|
case ErrShortNameTooLong:
|
||||||
|
return "short name too long"
|
||||||
|
case ErrDuplicatedFlag:
|
||||||
|
return "duplicated flag"
|
||||||
|
case ErrTag:
|
||||||
|
return "tag"
|
||||||
|
case ErrCommandRequired:
|
||||||
|
return "command required"
|
||||||
|
case ErrUnknownCommand:
|
||||||
|
return "unknown command"
|
||||||
|
}
|
||||||
|
|
||||||
|
return "unrecognized error type"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error represents a parser error. The error returned from Parse is of this
|
||||||
|
// type. The error contains both a Type and Message.
|
||||||
|
type Error struct {
|
||||||
|
// The type of error
|
||||||
|
Type ErrorType
|
||||||
|
|
||||||
|
// The error message
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns the error's message
|
||||||
|
func (e *Error) Error() string {
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
|
|
||||||
|
func newError(tp ErrorType, message string) *Error {
|
||||||
|
return &Error{
|
||||||
|
Type: tp,
|
||||||
|
Message: message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newErrorf(tp ErrorType, format string, args ...interface{}) *Error {
|
||||||
|
return newError(tp, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapError(err error) *Error {
|
||||||
|
ret, ok := err.(*Error)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return newError(ErrUnknown, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
110
Godeps/_workspace/src/github.com/jessevdk/go-flags/example_test.go
generated
vendored
Normal file
110
Godeps/_workspace/src/github.com/jessevdk/go-flags/example_test.go
generated
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
// Example of use of the flags package.
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example() {
|
||||||
|
var opts struct {
|
||||||
|
// Slice of bool will append 'true' each time the option
|
||||||
|
// is encountered (can be set multiple times, like -vvv)
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||||
|
|
||||||
|
// Example of automatic marshalling to desired type (uint)
|
||||||
|
Offset uint `long:"offset" description:"Offset"`
|
||||||
|
|
||||||
|
// Example of a callback, called each time the option is found.
|
||||||
|
Call func(string) `short:"c" description:"Call phone number"`
|
||||||
|
|
||||||
|
// Example of a required flag
|
||||||
|
Name string `short:"n" long:"name" description:"A name" required:"true"`
|
||||||
|
|
||||||
|
// Example of a value name
|
||||||
|
File string `short:"f" long:"file" description:"A file" value-name:"FILE"`
|
||||||
|
|
||||||
|
// Example of a pointer
|
||||||
|
Ptr *int `short:"p" description:"A pointer to an integer"`
|
||||||
|
|
||||||
|
// Example of a slice of strings
|
||||||
|
StringSlice []string `short:"s" description:"A slice of strings"`
|
||||||
|
|
||||||
|
// Example of a slice of pointers
|
||||||
|
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||||
|
|
||||||
|
// Example of a map
|
||||||
|
IntMap map[string]int `long:"intmap" description:"A map from string to int"`
|
||||||
|
|
||||||
|
// Example of a filename (useful for completion)
|
||||||
|
Filename Filename `long:"filename" description:"A filename"`
|
||||||
|
|
||||||
|
// Example of positional arguments
|
||||||
|
Args struct {
|
||||||
|
Id string
|
||||||
|
Num int
|
||||||
|
Rest []string
|
||||||
|
} `positional-args:"yes" required:"yes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback which will invoke callto:<argument> to call a number.
|
||||||
|
// Note that this works just on OS X (and probably only with
|
||||||
|
// Skype) but it shows the idea.
|
||||||
|
opts.Call = func(num string) {
|
||||||
|
cmd := exec.Command("open", "callto:"+num)
|
||||||
|
cmd.Start()
|
||||||
|
cmd.Process.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make some fake arguments to parse.
|
||||||
|
args := []string{
|
||||||
|
"-vv",
|
||||||
|
"--offset=5",
|
||||||
|
"-n", "Me",
|
||||||
|
"-p", "3",
|
||||||
|
"-s", "hello",
|
||||||
|
"-s", "world",
|
||||||
|
"--ptrslice", "hello",
|
||||||
|
"--ptrslice", "world",
|
||||||
|
"--intmap", "a:1",
|
||||||
|
"--intmap", "b:5",
|
||||||
|
"--filename", "hello.go",
|
||||||
|
"id",
|
||||||
|
"10",
|
||||||
|
"remaining1",
|
||||||
|
"remaining2",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse flags from `args'. Note that here we use flags.ParseArgs for
|
||||||
|
// the sake of making a working example. Normally, you would simply use
|
||||||
|
// flags.Parse(&opts) which uses os.Args
|
||||||
|
_, err := ParseArgs(&opts, args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Verbosity: %v\n", opts.Verbose)
|
||||||
|
fmt.Printf("Offset: %d\n", opts.Offset)
|
||||||
|
fmt.Printf("Name: %s\n", opts.Name)
|
||||||
|
fmt.Printf("Ptr: %d\n", *opts.Ptr)
|
||||||
|
fmt.Printf("StringSlice: %v\n", opts.StringSlice)
|
||||||
|
fmt.Printf("PtrSlice: [%v %v]\n", *opts.PtrSlice[0], *opts.PtrSlice[1])
|
||||||
|
fmt.Printf("IntMap: [a:%v b:%v]\n", opts.IntMap["a"], opts.IntMap["b"])
|
||||||
|
fmt.Printf("Filename: %v\n", opts.Filename)
|
||||||
|
fmt.Printf("Args.Id: %s\n", opts.Args.Id)
|
||||||
|
fmt.Printf("Args.Num: %d\n", opts.Args.Num)
|
||||||
|
fmt.Printf("Args.Rest: %v\n", opts.Args.Rest)
|
||||||
|
|
||||||
|
// Output: Verbosity: [true true]
|
||||||
|
// Offset: 5
|
||||||
|
// Name: Me
|
||||||
|
// Ptr: 3
|
||||||
|
// StringSlice: [hello world]
|
||||||
|
// PtrSlice: [hello world]
|
||||||
|
// IntMap: [a:1 b:5]
|
||||||
|
// Filename: hello.go
|
||||||
|
// Args.Id: id
|
||||||
|
// Args.Num: 10
|
||||||
|
// Args.Rest: [remaining1 remaining2]
|
||||||
|
}
|
23
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/add.go
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/add.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddCommand struct {
|
||||||
|
All bool `short:"a" long:"all" description:"Add all files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var addCommand AddCommand
|
||||||
|
|
||||||
|
func (x *AddCommand) Execute(args []string) error {
|
||||||
|
fmt.Printf("Adding (all=%v): %#v\n", x.All, args)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
parser.AddCommand("add",
|
||||||
|
"Add a file",
|
||||||
|
"The add command adds a file to the repository. Use -a to add all files.",
|
||||||
|
&addCommand)
|
||||||
|
}
|
9
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/bash-completion
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/bash-completion
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
_examples() {
|
||||||
|
args=("${COMP_WORDS[@]:1:$COMP_CWORD}")
|
||||||
|
|
||||||
|
local IFS=$'\n'
|
||||||
|
COMPREPLY=($(GO_FLAGS_COMPLETION=1 ${COMP_WORDS[0]} "${args[@]}"))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _examples examples
|
75
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/main.go
generated
vendored
Normal file
75
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/main.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/jessevdk/go-flags"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EditorOptions struct {
|
||||||
|
Input flags.Filename `short:"i" long:"input" description:"Input file" default:"-"`
|
||||||
|
Output flags.Filename `short:"o" long:"output" description:"Output file" default:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Point struct {
|
||||||
|
X, Y int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Point) UnmarshalFlag(value string) error {
|
||||||
|
parts := strings.Split(value, ",")
|
||||||
|
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return errors.New("expected two numbers separated by a ,")
|
||||||
|
}
|
||||||
|
|
||||||
|
x, err := strconv.ParseInt(parts[0], 10, 32)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
y, err := strconv.ParseInt(parts[1], 10, 32)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.X = int(x)
|
||||||
|
p.Y = int(y)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Point) MarshalFlag() (string, error) {
|
||||||
|
return fmt.Sprintf("%d,%d", p.X, p.Y), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
// Example of verbosity with level
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
|
||||||
|
|
||||||
|
// Example of optional value
|
||||||
|
User string `short:"u" long:"user" description:"User name" optional:"yes" optional-value:"pancake"`
|
||||||
|
|
||||||
|
// Example of map with multiple default values
|
||||||
|
Users map[string]string `long:"users" description:"User e-mail map" default:"system:system@example.org" default:"admin:admin@example.org"`
|
||||||
|
|
||||||
|
// Example of option group
|
||||||
|
Editor EditorOptions `group:"Editor Options"`
|
||||||
|
|
||||||
|
// Example of custom type Marshal/Unmarshal
|
||||||
|
Point Point `long:"point" description:"A x,y point" default:"1,2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var options Options
|
||||||
|
|
||||||
|
var parser = flags.NewParser(&options, flags.Default)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if _, err := parser.Parse(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
23
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/rm.go
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/jessevdk/go-flags/examples/rm.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RmCommand struct {
|
||||||
|
Force bool `short:"f" long:"force" description:"Force removal of files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var rmCommand RmCommand
|
||||||
|
|
||||||
|
func (x *RmCommand) Execute(args []string) error {
|
||||||
|
fmt.Printf("Removing (force=%v): %#v\n", x.Force, args)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
parser.AddCommand("rm",
|
||||||
|
"Remove a file",
|
||||||
|
"The rm command removes a file to the repository. Use -f to force removal of files.",
|
||||||
|
&rmCommand)
|
||||||
|
}
|
242
Godeps/_workspace/src/github.com/jessevdk/go-flags/flags.go
generated
vendored
Normal file
242
Godeps/_workspace/src/github.com/jessevdk/go-flags/flags.go
generated
vendored
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package flags provides an extensive command line option parser.
|
||||||
|
The flags package is similar in functionality to the go built-in flag package
|
||||||
|
but provides more options and uses reflection to provide a convenient and
|
||||||
|
succinct way of specifying command line options.
|
||||||
|
|
||||||
|
|
||||||
|
Supported features
|
||||||
|
|
||||||
|
The following features are supported in go-flags:
|
||||||
|
|
||||||
|
Options with short names (-v)
|
||||||
|
Options with long names (--verbose)
|
||||||
|
Options with and without arguments (bool v.s. other type)
|
||||||
|
Options with optional arguments and default values
|
||||||
|
Option default values from ENVIRONMENT_VARIABLES, including slice and map values
|
||||||
|
Multiple option groups each containing a set of options
|
||||||
|
Generate and print well-formatted help message
|
||||||
|
Passing remaining command line arguments after -- (optional)
|
||||||
|
Ignoring unknown command line options (optional)
|
||||||
|
Supports -I/usr/include -I=/usr/include -I /usr/include option argument specification
|
||||||
|
Supports multiple short options -aux
|
||||||
|
Supports all primitive go types (string, int{8..64}, uint{8..64}, float)
|
||||||
|
Supports same option multiple times (can store in slice or last option counts)
|
||||||
|
Supports maps
|
||||||
|
Supports function callbacks
|
||||||
|
Supports namespaces for (nested) option groups
|
||||||
|
|
||||||
|
Additional features specific to Windows:
|
||||||
|
Options with short names (/v)
|
||||||
|
Options with long names (/verbose)
|
||||||
|
Windows-style options with arguments use a colon as the delimiter
|
||||||
|
Modify generated help message with Windows-style / options
|
||||||
|
|
||||||
|
|
||||||
|
Basic usage
|
||||||
|
|
||||||
|
The flags package uses structs, reflection and struct field tags
|
||||||
|
to allow users to specify command line options. This results in very simple
|
||||||
|
and concise specification of your application options. For example:
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||||
|
}
|
||||||
|
|
||||||
|
This specifies one option with a short name -v and a long name --verbose.
|
||||||
|
When either -v or --verbose is found on the command line, a 'true' value
|
||||||
|
will be appended to the Verbose field. e.g. when specifying -vvv, the
|
||||||
|
resulting value of Verbose will be {[true, true, true]}.
|
||||||
|
|
||||||
|
Slice options work exactly the same as primitive type options, except that
|
||||||
|
whenever the option is encountered, a value is appended to the slice.
|
||||||
|
|
||||||
|
Map options from string to primitive type are also supported. On the command
|
||||||
|
line, you specify the value for such an option as key:value. For example
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
AuthorInfo string[string] `short:"a"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Then, the AuthorInfo map can be filled with something like
|
||||||
|
-a name:Jesse -a "surname:van den Kieboom".
|
||||||
|
|
||||||
|
Finally, for full control over the conversion between command line argument
|
||||||
|
values and options, user defined types can choose to implement the Marshaler
|
||||||
|
and Unmarshaler interfaces.
|
||||||
|
|
||||||
|
|
||||||
|
Available field tags
|
||||||
|
|
||||||
|
The following is a list of tags for struct fields supported by go-flags:
|
||||||
|
|
||||||
|
short: the short name of the option (single character)
|
||||||
|
long: the long name of the option
|
||||||
|
required: whether an option is required to appear on the command
|
||||||
|
line. If a required option is not present, the parser will
|
||||||
|
return ErrRequired (optional)
|
||||||
|
description: the description of the option (optional)
|
||||||
|
long-description: the long description of the option. Currently only
|
||||||
|
displayed in generated man pages (optional)
|
||||||
|
no-flag: if non-empty this field is ignored as an option (optional)
|
||||||
|
|
||||||
|
optional: whether an argument of the option is optional (optional)
|
||||||
|
optional-value: the value of an optional option when the option occurs
|
||||||
|
without an argument. This tag can be specified multiple
|
||||||
|
times in the case of maps or slices (optional)
|
||||||
|
default: the default value of an option. This tag can be specified
|
||||||
|
multiple times in the case of slices or maps (optional)
|
||||||
|
default-mask: when specified, this value will be displayed in the help
|
||||||
|
instead of the actual default value. This is useful
|
||||||
|
mostly for hiding otherwise sensitive information from
|
||||||
|
showing up in the help. If default-mask takes the special
|
||||||
|
value "-", then no default value will be shown at all
|
||||||
|
(optional)
|
||||||
|
env: the default value of the option is overridden from the
|
||||||
|
specified environment variable, if one has been defined.
|
||||||
|
(optional)
|
||||||
|
env-delim: the 'env' default value from environment is split into
|
||||||
|
multiple values with the given delimiter string, use with
|
||||||
|
slices and maps (optional)
|
||||||
|
value-name: the name of the argument value (to be shown in the help)
|
||||||
|
(optional)
|
||||||
|
|
||||||
|
base: a base (radix) used to convert strings to integer values, the
|
||||||
|
default base is 10 (i.e. decimal) (optional)
|
||||||
|
|
||||||
|
ini-name: the explicit ini option name (optional)
|
||||||
|
no-ini: if non-empty this field is ignored as an ini option
|
||||||
|
(optional)
|
||||||
|
|
||||||
|
group: when specified on a struct field, makes the struct
|
||||||
|
field a separate group with the given name (optional)
|
||||||
|
namespace: when specified on a group struct field, the namespace
|
||||||
|
gets prepended to every option's long name and
|
||||||
|
subgroup's namespace of this group, separated by
|
||||||
|
the parser's namespace delimiter (optional)
|
||||||
|
command: when specified on a struct field, makes the struct
|
||||||
|
field a (sub)command with the given name (optional)
|
||||||
|
subcommands-optional: when specified on a command struct field, makes
|
||||||
|
any subcommands of that command optional (optional)
|
||||||
|
alias: when specified on a command struct field, adds the
|
||||||
|
specified name as an alias for the command. Can be
|
||||||
|
be specified multiple times to add more than one
|
||||||
|
alias (optional)
|
||||||
|
positional-args: when specified on a field with a struct type,
|
||||||
|
uses the fields of that struct to parse remaining
|
||||||
|
positional command line arguments into (in order
|
||||||
|
of the fields). If a field has a slice type,
|
||||||
|
then all remaining arguments will be added to it.
|
||||||
|
Positional arguments are optional by default,
|
||||||
|
unless the "required" tag is specified together
|
||||||
|
with the "positional-args" tag (optional)
|
||||||
|
positional-arg-name: used on a field in a positional argument struct; name
|
||||||
|
of the positional argument placeholder to be shown in
|
||||||
|
the help (optional)
|
||||||
|
|
||||||
|
|
||||||
|
Either the `short:` tag or the `long:` must be specified to make the field eligible as an
|
||||||
|
option.
|
||||||
|
|
||||||
|
|
||||||
|
Option groups
|
||||||
|
|
||||||
|
Option groups are a simple way to semantically separate your options. All
|
||||||
|
options in a particular group are shown together in the help under the name
|
||||||
|
of the group. Namespaces can be used to specify option long names more
|
||||||
|
precisely and emphasize the options affiliation to their group.
|
||||||
|
|
||||||
|
There are currently three ways to specify option groups.
|
||||||
|
|
||||||
|
1. Use NewNamedParser specifying the various option groups.
|
||||||
|
2. Use AddGroup to add a group to an existing parser.
|
||||||
|
3. Add a struct field to the top-level options annotated with the
|
||||||
|
group:"group-name" tag.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Commands
|
||||||
|
|
||||||
|
The flags package also has basic support for commands. Commands are often
|
||||||
|
used in monolithic applications that support various commands or actions.
|
||||||
|
Take git for example, all of the add, commit, checkout, etc. are called
|
||||||
|
commands. Using commands you can easily separate multiple functions of your
|
||||||
|
application.
|
||||||
|
|
||||||
|
There are currently two ways to specify a command.
|
||||||
|
|
||||||
|
1. Use AddCommand on an existing parser.
|
||||||
|
2. Add a struct field to your options struct annotated with the
|
||||||
|
command:"command-name" tag.
|
||||||
|
|
||||||
|
The most common, idiomatic way to implement commands is to define a global
|
||||||
|
parser instance and implement each command in a separate file. These
|
||||||
|
command files should define a go init function which calls AddCommand on
|
||||||
|
the global parser.
|
||||||
|
|
||||||
|
When parsing ends and there is an active command and that command implements
|
||||||
|
the Commander interface, then its Execute method will be run with the
|
||||||
|
remaining command line arguments.
|
||||||
|
|
||||||
|
Command structs can have options which become valid to parse after the
|
||||||
|
command has been specified on the command line. It is currently not valid
|
||||||
|
to specify options from the parent level of the command after the command
|
||||||
|
name has occurred. Thus, given a top-level option "-v" and a command "add":
|
||||||
|
|
||||||
|
Valid: ./app -v add
|
||||||
|
Invalid: ./app add -v
|
||||||
|
|
||||||
|
|
||||||
|
Completion
|
||||||
|
|
||||||
|
go-flags has builtin support to provide bash completion of flags, commands
|
||||||
|
and argument values. To use completion, the binary which uses go-flags
|
||||||
|
can be invoked in a special environment to list completion of the current
|
||||||
|
command line argument. It should be noted that this `executes` your application,
|
||||||
|
and it is up to the user to make sure there are no negative side effects (for
|
||||||
|
example from init functions).
|
||||||
|
|
||||||
|
Setting the environment variable `GO_FLAGS_COMPLETION=1` enables completion
|
||||||
|
by replacing the argument parsing routine with the completion routine which
|
||||||
|
outputs completions for the passed arguments. The basic invocation to
|
||||||
|
complete a set of arguments is therefore:
|
||||||
|
|
||||||
|
GO_FLAGS_COMPLETION=1 ./completion-example arg1 arg2 arg3
|
||||||
|
|
||||||
|
where `completion-example` is the binary, `arg1` and `arg2` are
|
||||||
|
the current arguments, and `arg3` (the last argument) is the argument
|
||||||
|
to be completed. If the GO_FLAGS_COMPLETION is set to "verbose", then
|
||||||
|
descriptions of possible completion items will also be shown, if there
|
||||||
|
are more than 1 completion items.
|
||||||
|
|
||||||
|
To use this with bash completion, a simple file can be written which
|
||||||
|
calls the binary which supports go-flags completion:
|
||||||
|
|
||||||
|
_completion_example() {
|
||||||
|
# All arguments except the first one
|
||||||
|
args=("${COMP_WORDS[@]:1:$COMP_CWORD}")
|
||||||
|
|
||||||
|
# Only split on newlines
|
||||||
|
local IFS=$'\n'
|
||||||
|
|
||||||
|
# Call completion (note that the first element of COMP_WORDS is
|
||||||
|
# the executable itself)
|
||||||
|
COMPREPLY=($(GO_FLAGS_COMPLETION=1 ${COMP_WORDS[0]} "${args[@]}"))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _completion_example completion-example
|
||||||
|
|
||||||
|
Completion requires the parser option PassDoubleDash and is therefore enforced if the environment variable GO_FLAGS_COMPLETION is set.
|
||||||
|
|
||||||
|
Customized completion for argument values is supported by implementing
|
||||||
|
the flags.Completer interface for the argument value type. An example
|
||||||
|
of a type which does so is the flags.Filename type, an alias of string
|
||||||
|
allowing simple filename completion. A slice or array argument value
|
||||||
|
whose element type implements flags.Completer will also be completed.
|
||||||
|
*/
|
||||||
|
package flags
|
91
Godeps/_workspace/src/github.com/jessevdk/go-flags/group.go
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/jessevdk/go-flags/group.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNotPointerToStruct indicates that a provided data container is not
|
||||||
|
// a pointer to a struct. Only pointers to structs are valid data containers
|
||||||
|
// for options.
|
||||||
|
var ErrNotPointerToStruct = errors.New("provided data is not a pointer to struct")
|
||||||
|
|
||||||
|
// Group represents an option group. Option groups can be used to logically
|
||||||
|
// group options together under a description. Groups are only used to provide
|
||||||
|
// more structure to options both for the user (as displayed in the help message)
|
||||||
|
// and for you, since groups can be nested.
|
||||||
|
type Group struct {
|
||||||
|
// A short description of the group. The
|
||||||
|
// short description is primarily used in the built-in generated help
|
||||||
|
// message
|
||||||
|
ShortDescription string
|
||||||
|
|
||||||
|
// A long description of the group. The long
|
||||||
|
// description is primarily used to present information on commands
|
||||||
|
// (Command embeds Group) in the built-in generated help and man pages.
|
||||||
|
LongDescription string
|
||||||
|
|
||||||
|
// The namespace of the group
|
||||||
|
Namespace string
|
||||||
|
|
||||||
|
// The parent of the group or nil if it has no parent
|
||||||
|
parent interface{}
|
||||||
|
|
||||||
|
// All the options in the group
|
||||||
|
options []*Option
|
||||||
|
|
||||||
|
// All the subgroups
|
||||||
|
groups []*Group
|
||||||
|
|
||||||
|
// Whether the group represents the built-in help group
|
||||||
|
isBuiltinHelp bool
|
||||||
|
|
||||||
|
data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddGroup adds a new group to the command with the given name and data. The
|
||||||
|
// data needs to be a pointer to a struct from which the fields indicate which
|
||||||
|
// options are in the group.
|
||||||
|
func (g *Group) AddGroup(shortDescription string, longDescription string, data interface{}) (*Group, error) {
|
||||||
|
group := newGroup(shortDescription, longDescription, data)
|
||||||
|
|
||||||
|
group.parent = g
|
||||||
|
|
||||||
|
if err := group.scan(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.groups = append(g.groups, group)
|
||||||
|
return group, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Groups returns the list of groups embedded in this group.
|
||||||
|
func (g *Group) Groups() []*Group {
|
||||||
|
return g.groups
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options returns the list of options in this group.
|
||||||
|
func (g *Group) Options() []*Option {
|
||||||
|
return g.options
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find locates the subgroup with the given short description and returns it.
|
||||||
|
// If no such group can be found Find will return nil. Note that the description
|
||||||
|
// is matched case insensitively.
|
||||||
|
func (g *Group) Find(shortDescription string) *Group {
|
||||||
|
lshortDescription := strings.ToLower(shortDescription)
|
||||||
|
|
||||||
|
var ret *Group
|
||||||
|
|
||||||
|
g.eachGroup(func(gg *Group) {
|
||||||
|
if gg != g && strings.ToLower(gg.ShortDescription) == lshortDescription {
|
||||||
|
ret = gg
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
254
Godeps/_workspace/src/github.com/jessevdk/go-flags/group_private.go
generated
vendored
Normal file
254
Godeps/_workspace/src/github.com/jessevdk/go-flags/group_private.go
generated
vendored
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unicode/utf8"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type scanHandler func(reflect.Value, *reflect.StructField) (bool, error)
|
||||||
|
|
||||||
|
func newGroup(shortDescription string, longDescription string, data interface{}) *Group {
|
||||||
|
return &Group{
|
||||||
|
ShortDescription: shortDescription,
|
||||||
|
LongDescription: longDescription,
|
||||||
|
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) optionByName(name string, namematch func(*Option, string) bool) *Option {
|
||||||
|
prio := 0
|
||||||
|
var retopt *Option
|
||||||
|
|
||||||
|
for _, opt := range g.options {
|
||||||
|
if namematch != nil && namematch(opt, name) && prio < 4 {
|
||||||
|
retopt = opt
|
||||||
|
prio = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == opt.field.Name && prio < 3 {
|
||||||
|
retopt = opt
|
||||||
|
prio = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == opt.LongNameWithNamespace() && prio < 2 {
|
||||||
|
retopt = opt
|
||||||
|
prio = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.ShortName != 0 && name == string(opt.ShortName) && prio < 1 {
|
||||||
|
retopt = opt
|
||||||
|
prio = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return retopt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) eachGroup(f func(*Group)) {
|
||||||
|
f(g)
|
||||||
|
|
||||||
|
for _, gg := range g.groups {
|
||||||
|
gg.eachGroup(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) scanStruct(realval reflect.Value, sfield *reflect.StructField, handler scanHandler) error {
|
||||||
|
stype := realval.Type()
|
||||||
|
|
||||||
|
if sfield != nil {
|
||||||
|
if ok, err := handler(realval, sfield); err != nil {
|
||||||
|
return err
|
||||||
|
} else if ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < stype.NumField(); i++ {
|
||||||
|
field := stype.Field(i)
|
||||||
|
|
||||||
|
// PkgName is set only for non-exported fields, which we ignore
|
||||||
|
if field.PkgPath != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mtag := newMultiTag(string(field.Tag))
|
||||||
|
|
||||||
|
if err := mtag.Parse(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip fields with the no-flag tag
|
||||||
|
if mtag.Get("no-flag") != "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dive deep into structs or pointers to structs
|
||||||
|
kind := field.Type.Kind()
|
||||||
|
fld := realval.Field(i)
|
||||||
|
|
||||||
|
if kind == reflect.Struct {
|
||||||
|
if err := g.scanStruct(fld, &field, handler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if kind == reflect.Ptr && field.Type.Elem().Kind() == reflect.Struct {
|
||||||
|
if fld.IsNil() {
|
||||||
|
fld.Set(reflect.New(fld.Type().Elem()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.scanStruct(reflect.Indirect(fld), &field, handler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
longname := mtag.Get("long")
|
||||||
|
shortname := mtag.Get("short")
|
||||||
|
|
||||||
|
// Need at least either a short or long name
|
||||||
|
if longname == "" && shortname == "" && mtag.Get("ini-name") == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
short := rune(0)
|
||||||
|
rc := utf8.RuneCountInString(shortname)
|
||||||
|
|
||||||
|
if rc > 1 {
|
||||||
|
return newErrorf(ErrShortNameTooLong,
|
||||||
|
"short names can only be 1 character long, not `%s'",
|
||||||
|
shortname)
|
||||||
|
|
||||||
|
} else if rc == 1 {
|
||||||
|
short, _ = utf8.DecodeRuneInString(shortname)
|
||||||
|
}
|
||||||
|
|
||||||
|
description := mtag.Get("description")
|
||||||
|
def := mtag.GetMany("default")
|
||||||
|
|
||||||
|
optionalValue := mtag.GetMany("optional-value")
|
||||||
|
valueName := mtag.Get("value-name")
|
||||||
|
defaultMask := mtag.Get("default-mask")
|
||||||
|
|
||||||
|
optional := (mtag.Get("optional") != "")
|
||||||
|
required := (mtag.Get("required") != "")
|
||||||
|
|
||||||
|
option := &Option{
|
||||||
|
Description: description,
|
||||||
|
ShortName: short,
|
||||||
|
LongName: longname,
|
||||||
|
Default: def,
|
||||||
|
EnvDefaultKey: mtag.Get("env"),
|
||||||
|
EnvDefaultDelim: mtag.Get("env-delim"),
|
||||||
|
OptionalArgument: optional,
|
||||||
|
OptionalValue: optionalValue,
|
||||||
|
Required: required,
|
||||||
|
ValueName: valueName,
|
||||||
|
DefaultMask: defaultMask,
|
||||||
|
|
||||||
|
group: g,
|
||||||
|
|
||||||
|
field: field,
|
||||||
|
value: realval.Field(i),
|
||||||
|
tag: mtag,
|
||||||
|
}
|
||||||
|
|
||||||
|
g.options = append(g.options, option)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) checkForDuplicateFlags() *Error {
|
||||||
|
shortNames := make(map[rune]*Option)
|
||||||
|
longNames := make(map[string]*Option)
|
||||||
|
|
||||||
|
var duplicateError *Error
|
||||||
|
|
||||||
|
g.eachGroup(func(g *Group) {
|
||||||
|
for _, option := range g.options {
|
||||||
|
if option.LongName != "" {
|
||||||
|
longName := option.LongNameWithNamespace()
|
||||||
|
|
||||||
|
if otherOption, ok := longNames[longName]; ok {
|
||||||
|
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same long name as option `%s'", option, otherOption)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
longNames[longName] = option
|
||||||
|
}
|
||||||
|
if option.ShortName != 0 {
|
||||||
|
if otherOption, ok := shortNames[option.ShortName]; ok {
|
||||||
|
duplicateError = newErrorf(ErrDuplicatedFlag, "option `%s' uses the same short name as option `%s'", option, otherOption)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shortNames[option.ShortName] = option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return duplicateError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) scanSubGroupHandler(realval reflect.Value, sfield *reflect.StructField) (bool, error) {
|
||||||
|
mtag := newMultiTag(string(sfield.Tag))
|
||||||
|
|
||||||
|
if err := mtag.Parse(); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
subgroup := mtag.Get("group")
|
||||||
|
|
||||||
|
if len(subgroup) != 0 {
|
||||||
|
ptrval := reflect.NewAt(realval.Type(), unsafe.Pointer(realval.UnsafeAddr()))
|
||||||
|
description := mtag.Get("description")
|
||||||
|
|
||||||
|
group, err := g.AddGroup(subgroup, description, ptrval.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Namespace = mtag.Get("namespace")
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) scanType(handler scanHandler) error {
|
||||||
|
// Get all the public fields in the data struct
|
||||||
|
ptrval := reflect.ValueOf(g.data)
|
||||||
|
|
||||||
|
if ptrval.Type().Kind() != reflect.Ptr {
|
||||||
|
panic(ErrNotPointerToStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
stype := ptrval.Type().Elem()
|
||||||
|
|
||||||
|
if stype.Kind() != reflect.Struct {
|
||||||
|
panic(ErrNotPointerToStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
realval := reflect.Indirect(ptrval)
|
||||||
|
|
||||||
|
if err := g.scanStruct(realval, nil, handler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := g.checkForDuplicateFlags(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) scan() error {
|
||||||
|
return g.scanType(g.scanSubGroupHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Group) groupByName(name string) *Group {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
return g.Find(name)
|
||||||
|
}
|
187
Godeps/_workspace/src/github.com/jessevdk/go-flags/group_test.go
generated
vendored
Normal file
187
Godeps/_workspace/src/github.com/jessevdk/go-flags/group_test.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGroupInline(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Group struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
} `group:"Grouped Options"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p, ret := assertParserSuccess(t, &opts, "-v", "-g")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Group.G {
|
||||||
|
t.Errorf("Expected Group.G to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Command.Group.Find("Grouped Options") == nil {
|
||||||
|
t.Errorf("Expected to find group `Grouped Options'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupAdd(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
var grp = struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, Default)
|
||||||
|
g, err := p.AddGroup("Grouped Options", "", &grp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ret, err := p.ParseArgs([]string{"-v", "-g", "rest"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"rest"})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !grp.G {
|
||||||
|
t.Errorf("Expected Group.G to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Command.Group.Find("Grouped Options") != g {
|
||||||
|
t.Errorf("Expected to find group `Grouped Options'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Groups()[1] != g {
|
||||||
|
t.Errorf("Expected group %#v, but got %#v", g, p.Groups()[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.Options()[0].ShortName != 'g' {
|
||||||
|
t.Errorf("Expected short name `g' but got %v", g.Options()[0].ShortName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupNestedInline(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
|
||||||
|
Group struct {
|
||||||
|
G bool `short:"g"`
|
||||||
|
|
||||||
|
Nested struct {
|
||||||
|
N string `long:"n"`
|
||||||
|
} `group:"Nested Options"`
|
||||||
|
} `group:"Grouped Options"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p, ret := assertParserSuccess(t, &opts, "-v", "-g", "--n", "n", "rest")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"rest"})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Group.G {
|
||||||
|
t.Errorf("Expected Group.G to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertString(t, opts.Group.Nested.N, "n")
|
||||||
|
|
||||||
|
if p.Command.Group.Find("Grouped Options") == nil {
|
||||||
|
t.Errorf("Expected to find group `Grouped Options'")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Command.Group.Find("Nested Options") == nil {
|
||||||
|
t.Errorf("Expected to find group `Nested Options'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroupNestedInlineNamespace(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Opt string `long:"opt"`
|
||||||
|
|
||||||
|
Group struct {
|
||||||
|
Opt string `long:"opt"`
|
||||||
|
Group struct {
|
||||||
|
Opt string `long:"opt"`
|
||||||
|
} `group:"Subsubgroup" namespace:"sap"`
|
||||||
|
} `group:"Subgroup" namespace:"sip"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p, ret := assertParserSuccess(t, &opts, "--opt", "a", "--sip.opt", "b", "--sip.sap.opt", "c", "rest")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"rest"})
|
||||||
|
|
||||||
|
assertString(t, opts.Opt, "a")
|
||||||
|
assertString(t, opts.Group.Opt, "b")
|
||||||
|
assertString(t, opts.Group.Group.Opt, "c")
|
||||||
|
|
||||||
|
for _, name := range []string{"Subgroup", "Subsubgroup"} {
|
||||||
|
if p.Command.Group.Find(name) == nil {
|
||||||
|
t.Errorf("Expected to find group '%s'", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateShortFlags(t *testing.T) {
|
||||||
|
var opts struct {
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information"`
|
||||||
|
Variables []string `short:"v" long:"variable" description:"Set a variable value."`
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"--verbose",
|
||||||
|
"-v", "123",
|
||||||
|
"-v", "456",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseArgs(&opts, args)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||||
|
} else {
|
||||||
|
err2 := err.(*Error)
|
||||||
|
if err2.Type != ErrDuplicatedFlag {
|
||||||
|
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDuplicateLongFlags(t *testing.T) {
|
||||||
|
var opts struct {
|
||||||
|
Test1 []bool `short:"a" long:"testing" description:"Test 1"`
|
||||||
|
Test2 []string `short:"b" long:"testing" description:"Test 2."`
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"--testing",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseArgs(&opts, args)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||||
|
} else {
|
||||||
|
err2 := err.(*Error)
|
||||||
|
if err2.Type != ErrDuplicatedFlag {
|
||||||
|
t.Errorf("Expected an error with type ErrDuplicatedFlag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
426
Godeps/_workspace/src/github.com/jessevdk/go-flags/help.go
generated
vendored
Normal file
426
Godeps/_workspace/src/github.com/jessevdk/go-flags/help.go
generated
vendored
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type alignmentInfo struct {
|
||||||
|
maxLongLen int
|
||||||
|
hasShort bool
|
||||||
|
hasValueName bool
|
||||||
|
terminalColumns int
|
||||||
|
indent bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
paddingBeforeOption = 2
|
||||||
|
distanceBetweenOptionAndDescription = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *alignmentInfo) descriptionStart() int {
|
||||||
|
ret := a.maxLongLen + distanceBetweenOptionAndDescription
|
||||||
|
|
||||||
|
if a.hasShort {
|
||||||
|
ret += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.maxLongLen > 0 {
|
||||||
|
ret += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.hasValueName {
|
||||||
|
ret += 3
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *alignmentInfo) updateLen(name string, indent bool) {
|
||||||
|
l := utf8.RuneCountInString(name)
|
||||||
|
|
||||||
|
if indent {
|
||||||
|
l = l + 4
|
||||||
|
}
|
||||||
|
|
||||||
|
if l > a.maxLongLen {
|
||||||
|
a.maxLongLen = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) getAlignmentInfo() alignmentInfo {
|
||||||
|
ret := alignmentInfo{
|
||||||
|
maxLongLen: 0,
|
||||||
|
hasShort: false,
|
||||||
|
hasValueName: false,
|
||||||
|
terminalColumns: getTerminalColumns(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if ret.terminalColumns <= 0 {
|
||||||
|
ret.terminalColumns = 80
|
||||||
|
}
|
||||||
|
|
||||||
|
var prevcmd *Command
|
||||||
|
|
||||||
|
p.eachActiveGroup(func(c *Command, grp *Group) {
|
||||||
|
if c != prevcmd {
|
||||||
|
for _, arg := range c.args {
|
||||||
|
ret.updateLen(arg.Name, c != p.Command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, info := range grp.options {
|
||||||
|
if !info.canCli() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.ShortName != 0 {
|
||||||
|
ret.hasShort = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(info.ValueName) > 0 {
|
||||||
|
ret.hasValueName = true
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.updateLen(info.LongNameWithNamespace()+info.ValueName, c != p.Command)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) writeHelpOption(writer *bufio.Writer, option *Option, info alignmentInfo) {
|
||||||
|
line := &bytes.Buffer{}
|
||||||
|
|
||||||
|
prefix := paddingBeforeOption
|
||||||
|
|
||||||
|
if info.indent {
|
||||||
|
prefix += 4
|
||||||
|
}
|
||||||
|
|
||||||
|
line.WriteString(strings.Repeat(" ", prefix))
|
||||||
|
|
||||||
|
if option.ShortName != 0 {
|
||||||
|
line.WriteRune(defaultShortOptDelimiter)
|
||||||
|
line.WriteRune(option.ShortName)
|
||||||
|
} else if info.hasShort {
|
||||||
|
line.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
descstart := info.descriptionStart() + paddingBeforeOption
|
||||||
|
|
||||||
|
if len(option.LongName) > 0 {
|
||||||
|
if option.ShortName != 0 {
|
||||||
|
line.WriteString(", ")
|
||||||
|
} else if info.hasShort {
|
||||||
|
line.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
line.WriteString(defaultLongOptDelimiter)
|
||||||
|
line.WriteString(option.LongNameWithNamespace())
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.canArgument() {
|
||||||
|
line.WriteRune(defaultNameArgDelimiter)
|
||||||
|
|
||||||
|
if len(option.ValueName) > 0 {
|
||||||
|
line.WriteString(option.ValueName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
written := line.Len()
|
||||||
|
line.WriteTo(writer)
|
||||||
|
|
||||||
|
if option.Description != "" {
|
||||||
|
dw := descstart - written
|
||||||
|
writer.WriteString(strings.Repeat(" ", dw))
|
||||||
|
|
||||||
|
def := ""
|
||||||
|
defs := option.Default
|
||||||
|
|
||||||
|
if len(option.DefaultMask) != 0 {
|
||||||
|
if option.DefaultMask != "-" {
|
||||||
|
def = option.DefaultMask
|
||||||
|
}
|
||||||
|
} else if len(defs) == 0 && option.canArgument() {
|
||||||
|
var showdef bool
|
||||||
|
|
||||||
|
switch option.field.Type.Kind() {
|
||||||
|
case reflect.Func, reflect.Ptr:
|
||||||
|
showdef = !option.value.IsNil()
|
||||||
|
case reflect.Slice, reflect.String, reflect.Array:
|
||||||
|
showdef = option.value.Len() > 0
|
||||||
|
case reflect.Map:
|
||||||
|
showdef = !option.value.IsNil() && option.value.Len() > 0
|
||||||
|
default:
|
||||||
|
zeroval := reflect.Zero(option.field.Type)
|
||||||
|
showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
if showdef {
|
||||||
|
def, _ = convertToString(option.value, option.tag)
|
||||||
|
}
|
||||||
|
} else if len(defs) != 0 {
|
||||||
|
l := len(defs) - 1
|
||||||
|
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
def += quoteIfNeeded(defs[i]) + ", "
|
||||||
|
}
|
||||||
|
|
||||||
|
def += quoteIfNeeded(defs[l])
|
||||||
|
}
|
||||||
|
|
||||||
|
var envDef string
|
||||||
|
if option.EnvDefaultKey != "" {
|
||||||
|
var envPrintable string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
envPrintable = "%" + option.EnvDefaultKey + "%"
|
||||||
|
} else {
|
||||||
|
envPrintable = "$" + option.EnvDefaultKey
|
||||||
|
}
|
||||||
|
envDef = fmt.Sprintf(" [%s]", envPrintable)
|
||||||
|
}
|
||||||
|
|
||||||
|
var desc string
|
||||||
|
|
||||||
|
if def != "" {
|
||||||
|
desc = fmt.Sprintf("%s (%v)%s", option.Description, def, envDef)
|
||||||
|
} else {
|
||||||
|
desc = option.Description + envDef
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString(wrapText(desc,
|
||||||
|
info.terminalColumns-descstart,
|
||||||
|
strings.Repeat(" ", descstart)))
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxCommandLength(s []*Command) int {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ret := len(s[0].Name)
|
||||||
|
|
||||||
|
for _, v := range s[1:] {
|
||||||
|
l := len(v.Name)
|
||||||
|
|
||||||
|
if l > ret {
|
||||||
|
ret = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHelp writes a help message containing all the possible options and
|
||||||
|
// their descriptions to the provided writer. Note that the HelpFlag parser
|
||||||
|
// option provides a convenient way to add a -h/--help option group to the
|
||||||
|
// command line parser which will automatically show the help messages using
|
||||||
|
// this method.
|
||||||
|
func (p *Parser) WriteHelp(writer io.Writer) {
|
||||||
|
if writer == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wr := bufio.NewWriter(writer)
|
||||||
|
aligninfo := p.getAlignmentInfo()
|
||||||
|
|
||||||
|
cmd := p.Command
|
||||||
|
|
||||||
|
for cmd.Active != nil {
|
||||||
|
cmd = cmd.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Name != "" {
|
||||||
|
wr.WriteString("Usage:\n")
|
||||||
|
wr.WriteString(" ")
|
||||||
|
|
||||||
|
allcmd := p.Command
|
||||||
|
|
||||||
|
for allcmd != nil {
|
||||||
|
var usage string
|
||||||
|
|
||||||
|
if allcmd == p.Command {
|
||||||
|
if len(p.Usage) != 0 {
|
||||||
|
usage = p.Usage
|
||||||
|
} else if p.Options&HelpFlag != 0 {
|
||||||
|
usage = "[OPTIONS]"
|
||||||
|
}
|
||||||
|
} else if us, ok := allcmd.data.(Usage); ok {
|
||||||
|
usage = us.Usage()
|
||||||
|
} else if allcmd.hasCliOptions() {
|
||||||
|
usage = fmt.Sprintf("[%s-OPTIONS]", allcmd.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(usage) != 0 {
|
||||||
|
fmt.Fprintf(wr, " %s %s", allcmd.Name, usage)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(wr, " %s", allcmd.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allcmd.args) > 0 {
|
||||||
|
fmt.Fprintf(wr, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, arg := range allcmd.args {
|
||||||
|
if i != 0 {
|
||||||
|
fmt.Fprintf(wr, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
name := arg.Name
|
||||||
|
|
||||||
|
if arg.isRemaining() {
|
||||||
|
name = name + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
if !allcmd.ArgsRequired {
|
||||||
|
fmt.Fprintf(wr, "[%s]", name)
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(wr, "%s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if allcmd.Active == nil && len(allcmd.commands) > 0 {
|
||||||
|
var co, cc string
|
||||||
|
|
||||||
|
if allcmd.SubcommandsOptional {
|
||||||
|
co, cc = "[", "]"
|
||||||
|
} else {
|
||||||
|
co, cc = "<", ">"
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allcmd.commands) > 3 {
|
||||||
|
fmt.Fprintf(wr, " %scommand%s", co, cc)
|
||||||
|
} else {
|
||||||
|
subcommands := allcmd.sortedCommands()
|
||||||
|
names := make([]string, len(subcommands))
|
||||||
|
|
||||||
|
for i, subc := range subcommands {
|
||||||
|
names[i] = subc.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(wr, " %s%s%s", co, strings.Join(names, " | "), cc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allcmd = allcmd.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(wr)
|
||||||
|
|
||||||
|
if len(cmd.LongDescription) != 0 {
|
||||||
|
fmt.Fprintln(wr)
|
||||||
|
|
||||||
|
t := wrapText(cmd.LongDescription,
|
||||||
|
aligninfo.terminalColumns,
|
||||||
|
"")
|
||||||
|
|
||||||
|
fmt.Fprintln(wr, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c := p.Command
|
||||||
|
|
||||||
|
for c != nil {
|
||||||
|
printcmd := c != p.Command
|
||||||
|
|
||||||
|
c.eachGroup(func(grp *Group) {
|
||||||
|
first := true
|
||||||
|
|
||||||
|
// Skip built-in help group for all commands except the top-level
|
||||||
|
// parser
|
||||||
|
if grp.isBuiltinHelp && c != p.Command {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, info := range grp.options {
|
||||||
|
if !info.canCli() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if printcmd {
|
||||||
|
fmt.Fprintf(wr, "\n[%s command options]\n", c.Name)
|
||||||
|
aligninfo.indent = true
|
||||||
|
printcmd = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if first && cmd.Group != grp {
|
||||||
|
fmt.Fprintln(wr)
|
||||||
|
|
||||||
|
if aligninfo.indent {
|
||||||
|
wr.WriteString(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(wr, "%s:\n", grp.ShortDescription)
|
||||||
|
first = false
|
||||||
|
}
|
||||||
|
|
||||||
|
p.writeHelpOption(wr, info, aligninfo)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(c.args) > 0 {
|
||||||
|
if c == p.Command {
|
||||||
|
fmt.Fprintf(wr, "\nArguments:\n")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(wr, "\n[%s command arguments]\n", c.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxlen := aligninfo.descriptionStart()
|
||||||
|
|
||||||
|
for _, arg := range c.args {
|
||||||
|
prefix := strings.Repeat(" ", paddingBeforeOption)
|
||||||
|
fmt.Fprintf(wr, "%s%s", prefix, arg.Name)
|
||||||
|
|
||||||
|
if len(arg.Description) > 0 {
|
||||||
|
align := strings.Repeat(" ", maxlen-len(arg.Name)-1)
|
||||||
|
fmt.Fprintf(wr, ":%s%s", align, arg.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(wr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c = c.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
scommands := cmd.sortedCommands()
|
||||||
|
|
||||||
|
if len(scommands) > 0 {
|
||||||
|
maxnamelen := maxCommandLength(scommands)
|
||||||
|
|
||||||
|
fmt.Fprintln(wr)
|
||||||
|
fmt.Fprintln(wr, "Available commands:")
|
||||||
|
|
||||||
|
for _, c := range scommands {
|
||||||
|
fmt.Fprintf(wr, " %s", c.Name)
|
||||||
|
|
||||||
|
if len(c.ShortDescription) > 0 {
|
||||||
|
pad := strings.Repeat(" ", maxnamelen-len(c.Name))
|
||||||
|
fmt.Fprintf(wr, "%s %s", pad, c.ShortDescription)
|
||||||
|
|
||||||
|
if len(c.Aliases) > 0 {
|
||||||
|
fmt.Fprintf(wr, " (aliases: %s)", strings.Join(c.Aliases, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(wr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wr.Flush()
|
||||||
|
}
|
292
Godeps/_workspace/src/github.com/jessevdk/go-flags/help_test.go
generated
vendored
Normal file
292
Godeps/_workspace/src/github.com/jessevdk/go-flags/help_test.go
generated
vendored
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type helpOptions struct {
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Show verbose debug information" ini-name:"verbose"`
|
||||||
|
Call func(string) `short:"c" description:"Call phone number" ini-name:"call"`
|
||||||
|
PtrSlice []*string `long:"ptrslice" description:"A slice of pointers to string"`
|
||||||
|
EmptyDescription bool `long:"empty-description"`
|
||||||
|
|
||||||
|
Default string `long:"default" default:"Some\nvalue" description:"Test default value"`
|
||||||
|
DefaultArray []string `long:"default-array" default:"Some value" default:"Other\tvalue" description:"Test default array value"`
|
||||||
|
DefaultMap map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"`
|
||||||
|
EnvDefault1 string `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"`
|
||||||
|
EnvDefault2 string `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"`
|
||||||
|
OptionWithArgName string `long:"opt-with-arg-name" value-name:"something" description:"Option with named argument"`
|
||||||
|
|
||||||
|
OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
|
||||||
|
|
||||||
|
Other struct {
|
||||||
|
StringSlice []string `short:"s" default:"some" default:"value" description:"A slice of strings"`
|
||||||
|
IntMap map[string]int `long:"intmap" default:"a:1" description:"A map from string to int" ini-name:"int-map"`
|
||||||
|
} `group:"Other Options"`
|
||||||
|
|
||||||
|
Group struct {
|
||||||
|
Opt string `long:"opt" description:"This is a subgroup option"`
|
||||||
|
|
||||||
|
Group struct {
|
||||||
|
Opt string `long:"opt" description:"This is a subsubgroup option"`
|
||||||
|
} `group:"Subsubgroup" namespace:"sap"`
|
||||||
|
} `group:"Subgroup" namespace:"sip"`
|
||||||
|
|
||||||
|
Command struct {
|
||||||
|
ExtraVerbose []bool `long:"extra-verbose" description:"Use for extra verbosity"`
|
||||||
|
} `command:"command" alias:"cm" alias:"cmd" description:"A command"`
|
||||||
|
|
||||||
|
Args struct {
|
||||||
|
Filename string `positional-arg-name:"filename" description:"A filename"`
|
||||||
|
Number int `positional-arg-name:"num" description:"A number"`
|
||||||
|
} `positional-args:"yes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelp(t *testing.T) {
|
||||||
|
oldEnv := EnvSnapshot()
|
||||||
|
defer oldEnv.Restore()
|
||||||
|
os.Setenv("ENV_DEFAULT", "env-def")
|
||||||
|
|
||||||
|
var opts helpOptions
|
||||||
|
p := NewNamedParser("TestHelp", HelpFlag)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
_, err := p.ParseArgs([]string{"--help"})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected help error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := err.(*Error); !ok {
|
||||||
|
t.Fatalf("Expected flags.Error, but got %T", err)
|
||||||
|
} else {
|
||||||
|
if e.Type != ErrHelp {
|
||||||
|
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected string
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
expected = `Usage:
|
||||||
|
TestHelp [OPTIONS] [filename] [num] <command>
|
||||||
|
|
||||||
|
Application Options:
|
||||||
|
/v, /verbose Show verbose debug information
|
||||||
|
/c: Call phone number
|
||||||
|
/ptrslice: A slice of pointers to string
|
||||||
|
/empty-description
|
||||||
|
/default: Test default value ("Some\nvalue")
|
||||||
|
/default-array: Test default array value (Some value, "Other\tvalue")
|
||||||
|
/default-map: Testdefault map value (some:value, another:value)
|
||||||
|
/env-default1: Test env-default1 value (Some value) [%ENV_DEFAULT%]
|
||||||
|
/env-default2: Test env-default2 value [%ENV_DEFAULT%]
|
||||||
|
/opt-with-arg-name:something Option with named argument
|
||||||
|
|
||||||
|
Other Options:
|
||||||
|
/s: A slice of strings (some, value)
|
||||||
|
/intmap: A map from string to int (a:1)
|
||||||
|
|
||||||
|
Subgroup:
|
||||||
|
/sip.opt: This is a subgroup option
|
||||||
|
|
||||||
|
Subsubgroup:
|
||||||
|
/sip.sap.opt: This is a subsubgroup option
|
||||||
|
|
||||||
|
Help Options:
|
||||||
|
/? Show this help message
|
||||||
|
/h, /help Show this help message
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
filename: A filename
|
||||||
|
num: A number
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
command A command (aliases: cm, cmd)
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
expected = `Usage:
|
||||||
|
TestHelp [OPTIONS] [filename] [num] <command>
|
||||||
|
|
||||||
|
Application Options:
|
||||||
|
-v, --verbose Show verbose debug information
|
||||||
|
-c= Call phone number
|
||||||
|
--ptrslice= A slice of pointers to string
|
||||||
|
--empty-description
|
||||||
|
--default= Test default value ("Some\nvalue")
|
||||||
|
--default-array= Test default array value (Some value,
|
||||||
|
"Other\tvalue")
|
||||||
|
--default-map= Testdefault map value (some:value,
|
||||||
|
another:value)
|
||||||
|
--env-default1= Test env-default1 value (Some value)
|
||||||
|
[$ENV_DEFAULT]
|
||||||
|
--env-default2= Test env-default2 value [$ENV_DEFAULT]
|
||||||
|
--opt-with-arg-name=something Option with named argument
|
||||||
|
|
||||||
|
Other Options:
|
||||||
|
-s= A slice of strings (some, value)
|
||||||
|
--intmap= A map from string to int (a:1)
|
||||||
|
|
||||||
|
Subgroup:
|
||||||
|
--sip.opt= This is a subgroup option
|
||||||
|
|
||||||
|
Subsubgroup:
|
||||||
|
--sip.sap.opt= This is a subsubgroup option
|
||||||
|
|
||||||
|
Help Options:
|
||||||
|
-h, --help Show this help message
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
filename: A filename
|
||||||
|
num: A number
|
||||||
|
|
||||||
|
Available commands:
|
||||||
|
command A command (aliases: cm, cmd)
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDiff(t, e.Message, expected, "help message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMan(t *testing.T) {
|
||||||
|
oldEnv := EnvSnapshot()
|
||||||
|
defer oldEnv.Restore()
|
||||||
|
os.Setenv("ENV_DEFAULT", "env-def")
|
||||||
|
|
||||||
|
var opts helpOptions
|
||||||
|
p := NewNamedParser("TestMan", HelpFlag)
|
||||||
|
p.ShortDescription = "Test manpage generation"
|
||||||
|
p.LongDescription = "This is a somewhat `longer' description of what this does"
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
p.Commands()[0].LongDescription = "Longer `command' description"
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
p.WriteManPage(&buf)
|
||||||
|
|
||||||
|
got := buf.String()
|
||||||
|
|
||||||
|
tt := time.Now()
|
||||||
|
|
||||||
|
expected := fmt.Sprintf(`.TH TestMan 1 "%s"
|
||||||
|
.SH NAME
|
||||||
|
TestMan \- Test manpage generation
|
||||||
|
.SH SYNOPSIS
|
||||||
|
\fBTestMan\fP [OPTIONS]
|
||||||
|
.SH DESCRIPTION
|
||||||
|
This is a somewhat \fBlonger\fP description of what this does
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
\fB-v, --verbose\fP
|
||||||
|
Show verbose debug information
|
||||||
|
.TP
|
||||||
|
\fB-c\fP
|
||||||
|
Call phone number
|
||||||
|
.TP
|
||||||
|
\fB--ptrslice\fP
|
||||||
|
A slice of pointers to string
|
||||||
|
.TP
|
||||||
|
\fB--empty-description\fP
|
||||||
|
.TP
|
||||||
|
\fB--default\fP
|
||||||
|
Test default value
|
||||||
|
.TP
|
||||||
|
\fB--default-array\fP
|
||||||
|
Test default array value
|
||||||
|
.TP
|
||||||
|
\fB--default-map\fP
|
||||||
|
Testdefault map value
|
||||||
|
.TP
|
||||||
|
\fB--env-default1\fP
|
||||||
|
Test env-default1 value
|
||||||
|
.TP
|
||||||
|
\fB--env-default2\fP
|
||||||
|
Test env-default2 value
|
||||||
|
.TP
|
||||||
|
\fB--opt-with-arg-name\fP
|
||||||
|
Option with named argument
|
||||||
|
.TP
|
||||||
|
\fB-s\fP
|
||||||
|
A slice of strings
|
||||||
|
.TP
|
||||||
|
\fB--intmap\fP
|
||||||
|
A map from string to int
|
||||||
|
.TP
|
||||||
|
\fB--sip.opt\fP
|
||||||
|
This is a subgroup option
|
||||||
|
.TP
|
||||||
|
\fB--sip.sap.opt\fP
|
||||||
|
This is a subsubgroup option
|
||||||
|
.SH COMMANDS
|
||||||
|
.SS command
|
||||||
|
A command
|
||||||
|
|
||||||
|
Longer \fBcommand\fP description
|
||||||
|
|
||||||
|
\fBUsage\fP: TestMan [OPTIONS] command [command-OPTIONS]
|
||||||
|
|
||||||
|
|
||||||
|
\fBAliases\fP: cm, cmd
|
||||||
|
|
||||||
|
.TP
|
||||||
|
\fB--extra-verbose\fP
|
||||||
|
Use for extra verbosity
|
||||||
|
`, tt.Format("2 January 2006"))
|
||||||
|
|
||||||
|
assertDiff(t, got, expected, "man page")
|
||||||
|
}
|
||||||
|
|
||||||
|
type helpCommandNoOptions struct {
|
||||||
|
Command struct {
|
||||||
|
} `command:"command" description:"A command"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHelpCommand(t *testing.T) {
|
||||||
|
oldEnv := EnvSnapshot()
|
||||||
|
defer oldEnv.Restore()
|
||||||
|
os.Setenv("ENV_DEFAULT", "env-def")
|
||||||
|
|
||||||
|
var opts helpCommandNoOptions
|
||||||
|
p := NewNamedParser("TestHelpCommand", HelpFlag)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
_, err := p.ParseArgs([]string{"command", "--help"})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected help error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if e, ok := err.(*Error); !ok {
|
||||||
|
t.Fatalf("Expected flags.Error, but got %T", err)
|
||||||
|
} else {
|
||||||
|
if e.Type != ErrHelp {
|
||||||
|
t.Errorf("Expected flags.ErrHelp type, but got %s", e.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected string
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
expected = `Usage:
|
||||||
|
TestHelpCommand [OPTIONS] command
|
||||||
|
|
||||||
|
Help Options:
|
||||||
|
/? Show this help message
|
||||||
|
/h, /help Show this help message
|
||||||
|
`
|
||||||
|
} else {
|
||||||
|
expected = `Usage:
|
||||||
|
TestHelpCommand [OPTIONS] command
|
||||||
|
|
||||||
|
Help Options:
|
||||||
|
-h, --help Show this help message
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDiff(t, e.Message, expected, "help message")
|
||||||
|
}
|
||||||
|
}
|
140
Godeps/_workspace/src/github.com/jessevdk/go-flags/ini.go
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/jessevdk/go-flags/ini.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IniError contains location information on where an error occured.
|
||||||
|
type IniError struct {
|
||||||
|
// The error message.
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// The filename of the file in which the error occurred.
|
||||||
|
File string
|
||||||
|
|
||||||
|
// The line number at which the error occurred.
|
||||||
|
LineNumber uint
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error provides a "file:line: message" formatted message of the ini error.
|
||||||
|
func (x *IniError) Error() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"%s:%d: %s",
|
||||||
|
x.File,
|
||||||
|
x.LineNumber,
|
||||||
|
x.Message,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IniOptions for writing
|
||||||
|
type IniOptions uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// IniNone indicates no options.
|
||||||
|
IniNone IniOptions = 0
|
||||||
|
|
||||||
|
// IniIncludeDefaults indicates that default values should be written.
|
||||||
|
IniIncludeDefaults = 1 << iota
|
||||||
|
|
||||||
|
// IniCommentDefaults indicates that if IniIncludeDefaults is used
|
||||||
|
// options with default values are written but commented out.
|
||||||
|
IniCommentDefaults
|
||||||
|
|
||||||
|
// IniIncludeComments indicates that comments containing the description
|
||||||
|
// of an option should be written.
|
||||||
|
IniIncludeComments
|
||||||
|
|
||||||
|
// IniDefault provides a default set of options.
|
||||||
|
IniDefault = IniIncludeComments
|
||||||
|
)
|
||||||
|
|
||||||
|
// IniParser is a utility to read and write flags options from and to ini
|
||||||
|
// formatted strings.
|
||||||
|
type IniParser struct {
|
||||||
|
parser *Parser
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIniParser creates a new ini parser for a given Parser.
|
||||||
|
func NewIniParser(p *Parser) *IniParser {
|
||||||
|
return &IniParser{
|
||||||
|
parser: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IniParse is a convenience function to parse command line options with default
|
||||||
|
// settings from an ini formatted file. The provided data is a pointer to a struct
|
||||||
|
// representing the default option group (named "Application Options"). For
|
||||||
|
// more control, use flags.NewParser.
|
||||||
|
func IniParse(filename string, data interface{}) error {
|
||||||
|
p := NewParser(data, Default)
|
||||||
|
|
||||||
|
return NewIniParser(p).ParseFile(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFile parses flags from an ini formatted file. See Parse for more
|
||||||
|
// information on the ini file format. The returned errors can be of the type
|
||||||
|
// flags.Error or flags.IniError.
|
||||||
|
func (i *IniParser) ParseFile(filename string) error {
|
||||||
|
i.parser.clearIsSet()
|
||||||
|
|
||||||
|
ini, err := readIniFromFile(filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.parse(ini)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses flags from an ini format. You can use ParseFile as a
|
||||||
|
// convenience function to parse from a filename instead of a general
|
||||||
|
// io.Reader.
|
||||||
|
//
|
||||||
|
// The format of the ini file is as follows:
|
||||||
|
//
|
||||||
|
// [Option group name]
|
||||||
|
// option = value
|
||||||
|
//
|
||||||
|
// Each section in the ini file represents an option group or command in the
|
||||||
|
// flags parser. The default flags parser option group (i.e. when using
|
||||||
|
// flags.Parse) is named 'Application Options'. The ini option name is matched
|
||||||
|
// in the following order:
|
||||||
|
//
|
||||||
|
// 1. Compared to the ini-name tag on the option struct field (if present)
|
||||||
|
// 2. Compared to the struct field name
|
||||||
|
// 3. Compared to the option long name (if present)
|
||||||
|
// 4. Compared to the option short name (if present)
|
||||||
|
//
|
||||||
|
// Sections for nested groups and commands can be addressed using a dot `.'
|
||||||
|
// namespacing notation (i.e [subcommand.Options]). Group section names are
|
||||||
|
// matched case insensitive.
|
||||||
|
//
|
||||||
|
// The returned errors can be of the type flags.Error or flags.IniError.
|
||||||
|
func (i *IniParser) Parse(reader io.Reader) error {
|
||||||
|
i.parser.clearIsSet()
|
||||||
|
|
||||||
|
ini, err := readIni(reader, "")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return i.parse(ini)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile writes the flags as ini format into a file. See WriteIni
|
||||||
|
// for more information. The returned error occurs when the specified file
|
||||||
|
// could not be opened for writing.
|
||||||
|
func (i *IniParser) WriteFile(filename string, options IniOptions) error {
|
||||||
|
return writeIniToFile(i, filename, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the current values of all the flags to an ini format.
|
||||||
|
// See Parse for more information on the ini file format. You typically
|
||||||
|
// call this only after settings have been parsed since the default values of each
|
||||||
|
// option are stored just before parsing the flags (this is only relevant when
|
||||||
|
// IniIncludeDefaults is _not_ set in options).
|
||||||
|
func (i *IniParser) Write(writer io.Writer, options IniOptions) {
|
||||||
|
writeIni(i, writer, options)
|
||||||
|
}
|
452
Godeps/_workspace/src/github.com/jessevdk/go-flags/ini_private.go
generated
vendored
Normal file
452
Godeps/_workspace/src/github.com/jessevdk/go-flags/ini_private.go
generated
vendored
Normal file
@ -0,0 +1,452 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type iniValue struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
Quoted bool
|
||||||
|
LineNumber uint
|
||||||
|
}
|
||||||
|
|
||||||
|
type iniSection []iniValue
|
||||||
|
type ini struct {
|
||||||
|
File string
|
||||||
|
Sections map[string]iniSection
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFullLine(reader *bufio.Reader) (string, error) {
|
||||||
|
var line []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
l, more, err := reader.ReadLine()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if line == nil && !more {
|
||||||
|
return string(l), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
line = append(line, l...)
|
||||||
|
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(line), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func optionIniName(option *Option) string {
|
||||||
|
name := option.tag.Get("_read-ini-name")
|
||||||
|
|
||||||
|
if len(name) != 0 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
name = option.tag.Get("ini-name")
|
||||||
|
|
||||||
|
if len(name) != 0 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
return option.field.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeGroupIni(cmd *Command, group *Group, namespace string, writer io.Writer, options IniOptions) {
|
||||||
|
var sname string
|
||||||
|
|
||||||
|
if len(namespace) != 0 {
|
||||||
|
sname = namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.Group != group && len(group.ShortDescription) != 0 {
|
||||||
|
if len(sname) != 0 {
|
||||||
|
sname += "."
|
||||||
|
}
|
||||||
|
|
||||||
|
sname += group.ShortDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionwritten := false
|
||||||
|
comments := (options & IniIncludeComments) != IniNone
|
||||||
|
|
||||||
|
for _, option := range group.options {
|
||||||
|
if option.isFunc() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(option.tag.Get("no-ini")) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val := option.value
|
||||||
|
|
||||||
|
if (options&IniIncludeDefaults) == IniNone && option.valueIsDefault() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sectionwritten {
|
||||||
|
fmt.Fprintf(writer, "[%s]\n", sname)
|
||||||
|
sectionwritten = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if comments && len(option.Description) != 0 {
|
||||||
|
fmt.Fprintf(writer, "; %s\n", option.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
oname := optionIniName(option)
|
||||||
|
|
||||||
|
commentOption := (options&(IniIncludeDefaults|IniCommentDefaults)) == IniIncludeDefaults|IniCommentDefaults && option.valueIsDefault()
|
||||||
|
|
||||||
|
kind := val.Type().Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.Slice:
|
||||||
|
kind = val.Type().Elem().Kind()
|
||||||
|
|
||||||
|
if val.Len() == 0 {
|
||||||
|
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
|
||||||
|
} else {
|
||||||
|
for idx := 0; idx < val.Len(); idx++ {
|
||||||
|
v, _ := convertToString(val.Index(idx), option.tag)
|
||||||
|
|
||||||
|
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
kind = val.Type().Elem().Kind()
|
||||||
|
|
||||||
|
if val.Len() == 0 {
|
||||||
|
writeOption(writer, oname, kind, "", "", true, option.iniQuote)
|
||||||
|
} else {
|
||||||
|
mkeys := val.MapKeys()
|
||||||
|
keys := make([]string, len(val.MapKeys()))
|
||||||
|
kkmap := make(map[string]reflect.Value)
|
||||||
|
|
||||||
|
for i, k := range mkeys {
|
||||||
|
keys[i], _ = convertToString(k, option.tag)
|
||||||
|
kkmap[keys[i]] = k
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
for _, k := range keys {
|
||||||
|
v, _ := convertToString(val.MapIndex(kkmap[k]), option.tag)
|
||||||
|
|
||||||
|
writeOption(writer, oname, kind, k, v, commentOption, option.iniQuote)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
v, _ := convertToString(val, option.tag)
|
||||||
|
|
||||||
|
writeOption(writer, oname, kind, "", v, commentOption, option.iniQuote)
|
||||||
|
}
|
||||||
|
|
||||||
|
if comments {
|
||||||
|
fmt.Fprintln(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sectionwritten && !comments {
|
||||||
|
fmt.Fprintln(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeOption(writer io.Writer, optionName string, optionType reflect.Kind, optionKey string, optionValue string, commentOption bool, forceQuote bool) {
|
||||||
|
if forceQuote || (optionType == reflect.String && !isPrint(optionValue)) {
|
||||||
|
optionValue = strconv.Quote(optionValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
comment := ""
|
||||||
|
if commentOption {
|
||||||
|
comment = "; "
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(writer, "%s%s =", comment, optionName)
|
||||||
|
|
||||||
|
if optionKey != "" {
|
||||||
|
fmt.Fprintf(writer, " %s:%s", optionKey, optionValue)
|
||||||
|
} else if optionValue != "" {
|
||||||
|
fmt.Fprintf(writer, " %s", optionValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeCommandIni(command *Command, namespace string, writer io.Writer, options IniOptions) {
|
||||||
|
command.eachGroup(func(group *Group) {
|
||||||
|
writeGroupIni(command, group, namespace, writer, options)
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, c := range command.commands {
|
||||||
|
var nns string
|
||||||
|
|
||||||
|
if len(namespace) != 0 {
|
||||||
|
nns = c.Name + "." + nns
|
||||||
|
} else {
|
||||||
|
nns = c.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
writeCommandIni(c, nns, writer, options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIni(parser *IniParser, writer io.Writer, options IniOptions) {
|
||||||
|
writeCommandIni(parser.parser.Command, "", writer, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeIniToFile(parser *IniParser, filename string, options IniOptions) error {
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
writeIni(parser, file, options)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIniFromFile(filename string) (*ini, error) {
|
||||||
|
file, err := os.Open(filename)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
return readIni(file, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIni(contents io.Reader, filename string) (*ini, error) {
|
||||||
|
ret := &ini{
|
||||||
|
File: filename,
|
||||||
|
Sections: make(map[string]iniSection),
|
||||||
|
}
|
||||||
|
|
||||||
|
reader := bufio.NewReader(contents)
|
||||||
|
|
||||||
|
// Empty global section
|
||||||
|
section := make(iniSection, 0, 10)
|
||||||
|
sectionname := ""
|
||||||
|
|
||||||
|
ret.Sections[sectionname] = section
|
||||||
|
|
||||||
|
var lineno uint
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err := readFullLine(reader)
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lineno++
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
// Skip empty lines and lines starting with ; (comments)
|
||||||
|
if len(line) == 0 || line[0] == ';' || line[0] == '#' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if line[0] == '[' {
|
||||||
|
if line[0] != '[' || line[len(line)-1] != ']' {
|
||||||
|
return nil, &IniError{
|
||||||
|
Message: "malformed section header",
|
||||||
|
File: filename,
|
||||||
|
LineNumber: lineno,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.TrimSpace(line[1 : len(line)-1])
|
||||||
|
|
||||||
|
if len(name) == 0 {
|
||||||
|
return nil, &IniError{
|
||||||
|
Message: "empty section name",
|
||||||
|
File: filename,
|
||||||
|
LineNumber: lineno,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sectionname = name
|
||||||
|
section = ret.Sections[name]
|
||||||
|
|
||||||
|
if section == nil {
|
||||||
|
section = make(iniSection, 0, 10)
|
||||||
|
ret.Sections[name] = section
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse option here
|
||||||
|
keyval := strings.SplitN(line, "=", 2)
|
||||||
|
|
||||||
|
if len(keyval) != 2 {
|
||||||
|
return nil, &IniError{
|
||||||
|
Message: fmt.Sprintf("malformed key=value (%s)", line),
|
||||||
|
File: filename,
|
||||||
|
LineNumber: lineno,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
name := strings.TrimSpace(keyval[0])
|
||||||
|
value := strings.TrimSpace(keyval[1])
|
||||||
|
quoted := false
|
||||||
|
|
||||||
|
if len(value) != 0 && value[0] == '"' {
|
||||||
|
if v, err := strconv.Unquote(value); err == nil {
|
||||||
|
value = v
|
||||||
|
|
||||||
|
quoted = true
|
||||||
|
} else {
|
||||||
|
return nil, &IniError{
|
||||||
|
Message: err.Error(),
|
||||||
|
File: filename,
|
||||||
|
LineNumber: lineno,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section = append(section, iniValue{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
Quoted: quoted,
|
||||||
|
LineNumber: lineno,
|
||||||
|
})
|
||||||
|
|
||||||
|
ret.Sections[sectionname] = section
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IniParser) matchingGroups(name string) []*Group {
|
||||||
|
if len(name) == 0 {
|
||||||
|
var ret []*Group
|
||||||
|
|
||||||
|
i.parser.eachGroup(func(g *Group) {
|
||||||
|
ret = append(ret, g)
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
g := i.parser.groupByName(name)
|
||||||
|
|
||||||
|
if g != nil {
|
||||||
|
return []*Group{g}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IniParser) parse(ini *ini) error {
|
||||||
|
p := i.parser
|
||||||
|
|
||||||
|
var quotesLookup = make(map[*Option]bool)
|
||||||
|
|
||||||
|
for name, section := range ini.Sections {
|
||||||
|
groups := i.matchingGroups(name)
|
||||||
|
|
||||||
|
if len(groups) == 0 {
|
||||||
|
return newErrorf(ErrUnknownGroup, "could not find option group `%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, inival := range section {
|
||||||
|
var opt *Option
|
||||||
|
|
||||||
|
for _, group := range groups {
|
||||||
|
opt = group.optionByName(inival.Name, func(o *Option, n string) bool {
|
||||||
|
return strings.ToLower(o.tag.Get("ini-name")) == strings.ToLower(n)
|
||||||
|
})
|
||||||
|
|
||||||
|
if opt != nil && len(opt.tag.Get("no-ini")) != 0 {
|
||||||
|
opt = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt == nil {
|
||||||
|
if (p.Options & IgnoreUnknown) == None {
|
||||||
|
return &IniError{
|
||||||
|
Message: fmt.Sprintf("unknown option: %s", inival.Name),
|
||||||
|
File: ini.File,
|
||||||
|
LineNumber: inival.LineNumber,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pval := &inival.Value
|
||||||
|
|
||||||
|
if !opt.canArgument() && len(inival.Value) == 0 {
|
||||||
|
pval = nil
|
||||||
|
} else {
|
||||||
|
if opt.value.Type().Kind() == reflect.Map {
|
||||||
|
parts := strings.SplitN(inival.Value, ":", 2)
|
||||||
|
|
||||||
|
// only handle unquoting
|
||||||
|
if len(parts) == 2 && parts[1][0] == '"' {
|
||||||
|
if v, err := strconv.Unquote(parts[1]); err == nil {
|
||||||
|
parts[1] = v
|
||||||
|
|
||||||
|
inival.Quoted = true
|
||||||
|
} else {
|
||||||
|
return &IniError{
|
||||||
|
Message: err.Error(),
|
||||||
|
File: ini.File,
|
||||||
|
LineNumber: inival.LineNumber,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s := parts[0] + ":" + parts[1]
|
||||||
|
|
||||||
|
pval = &s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := opt.set(pval); err != nil {
|
||||||
|
return &IniError{
|
||||||
|
Message: err.Error(),
|
||||||
|
File: ini.File,
|
||||||
|
LineNumber: inival.LineNumber,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// either all INI values are quoted or only values who need quoting
|
||||||
|
if _, ok := quotesLookup[opt]; !inival.Quoted || !ok {
|
||||||
|
quotesLookup[opt] = inival.Quoted
|
||||||
|
}
|
||||||
|
|
||||||
|
opt.tag.Set("_read-ini-name", inival.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for opt, quoted := range quotesLookup {
|
||||||
|
opt.iniQuote = quoted
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
767
Godeps/_workspace/src/github.com/jessevdk/go-flags/ini_test.go
generated
vendored
Normal file
767
Godeps/_workspace/src/github.com/jessevdk/go-flags/ini_test.go
generated
vendored
Normal file
@ -0,0 +1,767 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWriteIni(t *testing.T) {
|
||||||
|
oldEnv := EnvSnapshot()
|
||||||
|
defer oldEnv.Restore()
|
||||||
|
os.Setenv("ENV_DEFAULT", "env-def")
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
args []string
|
||||||
|
options IniOptions
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
|
||||||
|
IniDefault,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
verbose = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A map from string to int
|
||||||
|
int-map = a:2
|
||||||
|
int-map = b:3
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"-vv", "--intmap=a:2", "--intmap", "b:3", "filename", "0", "command"},
|
||||||
|
IniDefault | IniIncludeDefaults,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
verbose = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
; A slice of pointers to string
|
||||||
|
; PtrSlice =
|
||||||
|
|
||||||
|
EmptyDescription = false
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
Default = "Some\nvalue"
|
||||||
|
|
||||||
|
; Test default array value
|
||||||
|
DefaultArray = Some value
|
||||||
|
DefaultArray = "Other\tvalue"
|
||||||
|
|
||||||
|
; Testdefault map value
|
||||||
|
DefaultMap = another:value
|
||||||
|
DefaultMap = some:value
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
; Option with named argument
|
||||||
|
OptionWithArgName =
|
||||||
|
|
||||||
|
; Option only available in ini
|
||||||
|
only-ini =
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A slice of strings
|
||||||
|
StringSlice = some
|
||||||
|
StringSlice = value
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
int-map = a:2
|
||||||
|
int-map = b:3
|
||||||
|
|
||||||
|
[Subgroup]
|
||||||
|
; This is a subgroup option
|
||||||
|
Opt =
|
||||||
|
|
||||||
|
[Subsubgroup]
|
||||||
|
; This is a subsubgroup option
|
||||||
|
Opt =
|
||||||
|
|
||||||
|
[command]
|
||||||
|
; Use for extra verbosity
|
||||||
|
; ExtraVerbose =
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"filename", "0", "command"},
|
||||||
|
IniDefault | IniIncludeDefaults | IniCommentDefaults,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
; verbose =
|
||||||
|
|
||||||
|
; A slice of pointers to string
|
||||||
|
; PtrSlice =
|
||||||
|
|
||||||
|
; EmptyDescription = false
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
; Default = "Some\nvalue"
|
||||||
|
|
||||||
|
; Test default array value
|
||||||
|
; DefaultArray = Some value
|
||||||
|
; DefaultArray = "Other\tvalue"
|
||||||
|
|
||||||
|
; Testdefault map value
|
||||||
|
; DefaultMap = another:value
|
||||||
|
; DefaultMap = some:value
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
; Option with named argument
|
||||||
|
; OptionWithArgName =
|
||||||
|
|
||||||
|
; Option only available in ini
|
||||||
|
; only-ini =
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A slice of strings
|
||||||
|
; StringSlice = some
|
||||||
|
; StringSlice = value
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
; int-map = a:1
|
||||||
|
|
||||||
|
[Subgroup]
|
||||||
|
; This is a subgroup option
|
||||||
|
; Opt =
|
||||||
|
|
||||||
|
[Subsubgroup]
|
||||||
|
; This is a subsubgroup option
|
||||||
|
; Opt =
|
||||||
|
|
||||||
|
[command]
|
||||||
|
; Use for extra verbosity
|
||||||
|
; ExtraVerbose =
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[]string{"--default=New value", "--default-array=New value", "--default-map=new:value", "filename", "0", "command"},
|
||||||
|
IniDefault | IniIncludeDefaults | IniCommentDefaults,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
; verbose =
|
||||||
|
|
||||||
|
; A slice of pointers to string
|
||||||
|
; PtrSlice =
|
||||||
|
|
||||||
|
; EmptyDescription = false
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
Default = New value
|
||||||
|
|
||||||
|
; Test default array value
|
||||||
|
DefaultArray = New value
|
||||||
|
|
||||||
|
; Testdefault map value
|
||||||
|
DefaultMap = new:value
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
; Option with named argument
|
||||||
|
; OptionWithArgName =
|
||||||
|
|
||||||
|
; Option only available in ini
|
||||||
|
; only-ini =
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A slice of strings
|
||||||
|
; StringSlice = some
|
||||||
|
; StringSlice = value
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
; int-map = a:1
|
||||||
|
|
||||||
|
[Subgroup]
|
||||||
|
; This is a subgroup option
|
||||||
|
; Opt =
|
||||||
|
|
||||||
|
[Subsubgroup]
|
||||||
|
; This is a subsubgroup option
|
||||||
|
; Opt =
|
||||||
|
|
||||||
|
[command]
|
||||||
|
; Use for extra verbosity
|
||||||
|
; ExtraVerbose =
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var opts helpOptions
|
||||||
|
|
||||||
|
p := NewNamedParser("TestIni", Default)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
_, err := p.ParseArgs(test.args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inip := NewIniParser(p)
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
inip.Write(&b, test.options)
|
||||||
|
|
||||||
|
got := b.String()
|
||||||
|
expected := test.expected
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("with arguments %+v and ini options %b", test.args, test.options)
|
||||||
|
assertDiff(t, got, expected, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadIni(t *testing.T) {
|
||||||
|
var opts helpOptions
|
||||||
|
|
||||||
|
p := NewNamedParser("TestIni", Default)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
inip := NewIniParser(p)
|
||||||
|
|
||||||
|
inic := `
|
||||||
|
; Show verbose debug information
|
||||||
|
verbose = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
DefaultMap = another:"value\n1"
|
||||||
|
DefaultMap = some:value 2
|
||||||
|
|
||||||
|
[Application Options]
|
||||||
|
; A slice of pointers to string
|
||||||
|
; PtrSlice =
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
Default = "New\nvalue"
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = New value
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
# A slice of strings
|
||||||
|
StringSlice = "some\nvalue"
|
||||||
|
StringSlice = another value
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
int-map = a:2
|
||||||
|
int-map = b:3
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
b := strings.NewReader(inic)
|
||||||
|
err := inip.Parse(b)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertBoolArray(t, opts.Verbose, []bool{true, true})
|
||||||
|
|
||||||
|
if v := map[string]string{"another": "value\n1", "some": "value 2"}; !reflect.DeepEqual(opts.DefaultMap, v) {
|
||||||
|
t.Fatalf("Expected %#v for DefaultMap but got %#v", v, opts.DefaultMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertString(t, opts.Default, "New\nvalue")
|
||||||
|
|
||||||
|
assertString(t, opts.EnvDefault1, "New value")
|
||||||
|
|
||||||
|
assertStringArray(t, opts.Other.StringSlice, []string{"some\nvalue", "another value"})
|
||||||
|
|
||||||
|
if v, ok := opts.Other.IntMap["a"]; !ok {
|
||||||
|
t.Errorf("Expected \"a\" in Other.IntMap")
|
||||||
|
} else if v != 2 {
|
||||||
|
t.Errorf("Expected Other.IntMap[\"a\"] = 2, but got %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := opts.Other.IntMap["b"]; !ok {
|
||||||
|
t.Errorf("Expected \"b\" in Other.IntMap")
|
||||||
|
} else if v != 3 {
|
||||||
|
t.Errorf("Expected Other.IntMap[\"b\"] = 3, but got %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadAndWriteIni(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
options IniOptions
|
||||||
|
read string
|
||||||
|
write string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
IniIncludeComments,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
verbose = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
Default = "quote me"
|
||||||
|
|
||||||
|
; Test default array value
|
||||||
|
DefaultArray = 1
|
||||||
|
DefaultArray = "2"
|
||||||
|
DefaultArray = 3
|
||||||
|
|
||||||
|
; Testdefault map value
|
||||||
|
; DefaultMap =
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A slice of strings
|
||||||
|
; StringSlice =
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
int-map = a:2
|
||||||
|
int-map = b:"3"
|
||||||
|
|
||||||
|
`,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
verbose = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
Default = "quote me"
|
||||||
|
|
||||||
|
; Test default array value
|
||||||
|
DefaultArray = 1
|
||||||
|
DefaultArray = 2
|
||||||
|
DefaultArray = 3
|
||||||
|
|
||||||
|
; Testdefault map value
|
||||||
|
; DefaultMap =
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A slice of strings
|
||||||
|
; StringSlice =
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
int-map = a:2
|
||||||
|
int-map = b:3
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
IniIncludeComments,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
verbose = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
Default = "quote me"
|
||||||
|
|
||||||
|
; Test default array value
|
||||||
|
DefaultArray = "1"
|
||||||
|
DefaultArray = "2"
|
||||||
|
DefaultArray = "3"
|
||||||
|
|
||||||
|
; Testdefault map value
|
||||||
|
; DefaultMap =
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A slice of strings
|
||||||
|
; StringSlice =
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
int-map = a:"2"
|
||||||
|
int-map = b:"3"
|
||||||
|
|
||||||
|
`,
|
||||||
|
`[Application Options]
|
||||||
|
; Show verbose debug information
|
||||||
|
verbose = true
|
||||||
|
verbose = true
|
||||||
|
|
||||||
|
; Test default value
|
||||||
|
Default = "quote me"
|
||||||
|
|
||||||
|
; Test default array value
|
||||||
|
DefaultArray = "1"
|
||||||
|
DefaultArray = "2"
|
||||||
|
DefaultArray = "3"
|
||||||
|
|
||||||
|
; Testdefault map value
|
||||||
|
; DefaultMap =
|
||||||
|
|
||||||
|
; Test env-default1 value
|
||||||
|
EnvDefault1 = env-def
|
||||||
|
|
||||||
|
; Test env-default2 value
|
||||||
|
EnvDefault2 = env-def
|
||||||
|
|
||||||
|
[Other Options]
|
||||||
|
; A slice of strings
|
||||||
|
; StringSlice =
|
||||||
|
|
||||||
|
; A map from string to int
|
||||||
|
int-map = a:"2"
|
||||||
|
int-map = b:"3"
|
||||||
|
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var opts helpOptions
|
||||||
|
|
||||||
|
p := NewNamedParser("TestIni", Default)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
inip := NewIniParser(p)
|
||||||
|
|
||||||
|
read := strings.NewReader(test.read)
|
||||||
|
err := inip.Parse(read)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var write bytes.Buffer
|
||||||
|
inip.Write(&write, test.options)
|
||||||
|
|
||||||
|
got := write.String()
|
||||||
|
|
||||||
|
msg := fmt.Sprintf("with ini options %b", test.options)
|
||||||
|
assertDiff(t, got, test.write, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReadIniWrongQuoting(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
iniFile string
|
||||||
|
lineNumber uint
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
iniFile: `Default = "New\nvalue`,
|
||||||
|
lineNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iniFile: `StringSlice = "New\nvalue`,
|
||||||
|
lineNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iniFile: `StringSlice = "New\nvalue"
|
||||||
|
StringSlice = "Second\nvalue`,
|
||||||
|
lineNumber: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iniFile: `DefaultMap = some:"value`,
|
||||||
|
lineNumber: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iniFile: `DefaultMap = some:value
|
||||||
|
DefaultMap = another:"value`,
|
||||||
|
lineNumber: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var opts helpOptions
|
||||||
|
|
||||||
|
p := NewNamedParser("TestIni", Default)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
inip := NewIniParser(p)
|
||||||
|
|
||||||
|
inic := test.iniFile
|
||||||
|
|
||||||
|
b := strings.NewReader(inic)
|
||||||
|
err := inip.Parse(b)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expect error")
|
||||||
|
}
|
||||||
|
|
||||||
|
iniError := err.(*IniError)
|
||||||
|
|
||||||
|
if iniError.LineNumber != test.lineNumber {
|
||||||
|
t.Fatalf("Expect error on line %d", test.lineNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIniCommands(t *testing.T) {
|
||||||
|
var opts struct {
|
||||||
|
Value string `short:"v" long:"value"`
|
||||||
|
|
||||||
|
Add struct {
|
||||||
|
Name int `short:"n" long:"name" ini-name:"AliasName"`
|
||||||
|
|
||||||
|
Other struct {
|
||||||
|
O string `short:"o" long:"other"`
|
||||||
|
} `group:"Other Options"`
|
||||||
|
} `command:"add"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewNamedParser("TestIni", Default)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
inip := NewIniParser(p)
|
||||||
|
|
||||||
|
inic := `[Application Options]
|
||||||
|
value = some value
|
||||||
|
|
||||||
|
[add]
|
||||||
|
AliasName = 5
|
||||||
|
|
||||||
|
[add.Other Options]
|
||||||
|
other = subgroup
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
b := strings.NewReader(inic)
|
||||||
|
err := inip.Parse(b)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertString(t, opts.Value, "some value")
|
||||||
|
|
||||||
|
if opts.Add.Name != 5 {
|
||||||
|
t.Errorf("Expected opts.Add.Name to be 5, but got %v", opts.Add.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertString(t, opts.Add.Other.O, "subgroup")
|
||||||
|
|
||||||
|
// Test writing it back
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
inip.Write(buf, IniDefault)
|
||||||
|
|
||||||
|
assertDiff(t, buf.String(), inic, "ini contents")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIniNoIni(t *testing.T) {
|
||||||
|
var opts struct {
|
||||||
|
NoValue string `short:"n" long:"novalue" no-ini:"yes"`
|
||||||
|
Value string `short:"v" long:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewNamedParser("TestIni", Default)
|
||||||
|
p.AddGroup("Application Options", "The application options", &opts)
|
||||||
|
|
||||||
|
inip := NewIniParser(p)
|
||||||
|
|
||||||
|
// read INI
|
||||||
|
inic := `[Application Options]
|
||||||
|
novalue = some value
|
||||||
|
value = some other value
|
||||||
|
`
|
||||||
|
|
||||||
|
b := strings.NewReader(inic)
|
||||||
|
err := inip.Parse(b)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
iniError := err.(*IniError)
|
||||||
|
|
||||||
|
if v := uint(2); iniError.LineNumber != v {
|
||||||
|
t.Errorf("Expected opts.Add.Name to be %d, but got %d", v, iniError.LineNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := "unknown option: novalue"; iniError.Message != v {
|
||||||
|
t.Errorf("Expected opts.Add.Name to be %s, but got %s", v, iniError.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write INI
|
||||||
|
opts.NoValue = "some value"
|
||||||
|
opts.Value = "some other value"
|
||||||
|
|
||||||
|
file, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot create temporary file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
err = inip.WriteFile(file.Name(), IniIncludeDefaults)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not write ini file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := ioutil.ReadFile(file.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not read written ini file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "[Application Options]\nValue = some other value\n\n"
|
||||||
|
|
||||||
|
assertDiff(t, string(found), expected, "ini content")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIniParse(t *testing.T) {
|
||||||
|
file, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot create temporary file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
_, err = file.WriteString("value = 123")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot write to temporary file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
var opts struct {
|
||||||
|
Value int `long:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err = IniParse(file.Name(), &opts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not parse ini: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Value != 123 {
|
||||||
|
t.Fatalf("Expected Value to be \"123\" but was \"%d\"", opts.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWriteFile(t *testing.T) {
|
||||||
|
file, err := ioutil.TempFile("", "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Cannot create temporary file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(file.Name())
|
||||||
|
|
||||||
|
var opts struct {
|
||||||
|
Value int `long:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Value = 123
|
||||||
|
|
||||||
|
p := NewParser(&opts, Default)
|
||||||
|
ini := NewIniParser(p)
|
||||||
|
|
||||||
|
err = ini.WriteFile(file.Name(), IniIncludeDefaults)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not write ini file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found, err := ioutil.ReadFile(file.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not read written ini file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := "[Application Options]\nValue = 123\n\n"
|
||||||
|
|
||||||
|
assertDiff(t, string(found), expected, "ini content")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOverwriteRequiredOptions(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
args []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
args: []string{"--value", "from CLI"},
|
||||||
|
expected: []string{
|
||||||
|
"from CLI",
|
||||||
|
"from default",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--value", "from CLI", "--default", "from CLI"},
|
||||||
|
expected: []string{
|
||||||
|
"from CLI",
|
||||||
|
"from CLI",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--config", "no file name"},
|
||||||
|
expected: []string{
|
||||||
|
"from INI",
|
||||||
|
"from INI",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name"},
|
||||||
|
expected: []string{
|
||||||
|
"from INI",
|
||||||
|
"from INI",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"--value", "from CLI before", "--default", "from CLI before", "--config", "no file name", "--value", "from CLI after", "--default", "from CLI after"},
|
||||||
|
expected: []string{
|
||||||
|
"from CLI after",
|
||||||
|
"from CLI after",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var opts struct {
|
||||||
|
Config func(s string) error `long:"config" no-ini:"true"`
|
||||||
|
Value string `long:"value" required:"true"`
|
||||||
|
Default string `long:"default" required:"true" default:"from default"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewParser(&opts, Default)
|
||||||
|
|
||||||
|
opts.Config = func(s string) error {
|
||||||
|
ini := NewIniParser(p)
|
||||||
|
|
||||||
|
return ini.Parse(bytes.NewBufferString("value = from INI\ndefault = from INI"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := p.ParseArgs(test.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error %s with args %+v", err, test.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Value != test.expected[0] {
|
||||||
|
t.Fatalf("Expected Value to be \"%s\" but was \"%s\" with args %+v", test.expected[0], opts.Value, test.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Default != test.expected[1] {
|
||||||
|
t.Fatalf("Expected Default to be \"%s\" but was \"%s\" with args %+v", test.expected[1], opts.Default, test.args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
Godeps/_workspace/src/github.com/jessevdk/go-flags/long_test.go
generated
vendored
Normal file
85
Godeps/_workspace/src/github.com/jessevdk/go-flags/long_test.go
generated
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLong(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `long:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "--value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLongArg(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `long:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "--value", "value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLongArgEqual(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `long:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "--value=value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLongDefault(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `long:"value" default:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts)
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLongOptional(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `long:"value" optional:"yes" optional-value:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "--value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLongOptionalArg(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `long:"value" optional:"yes" optional-value:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "--value", "no")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"no"})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLongOptionalArgEqual(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `long:"value" optional:"yes" optional-value:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "--value=value", "no")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"no"})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
158
Godeps/_workspace/src/github.com/jessevdk/go-flags/man.go
generated
vendored
Normal file
158
Godeps/_workspace/src/github.com/jessevdk/go-flags/man.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func formatForMan(wr io.Writer, s string) {
|
||||||
|
for {
|
||||||
|
idx := strings.IndexRune(s, '`')
|
||||||
|
|
||||||
|
if idx < 0 {
|
||||||
|
fmt.Fprintf(wr, "%s", s)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(wr, "%s", s[:idx])
|
||||||
|
|
||||||
|
s = s[idx+1:]
|
||||||
|
idx = strings.IndexRune(s, '\'')
|
||||||
|
|
||||||
|
if idx < 0 {
|
||||||
|
fmt.Fprintf(wr, "%s", s)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(wr, "\\fB%s\\fP", s[:idx])
|
||||||
|
s = s[idx+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeManPageOptions(wr io.Writer, grp *Group) {
|
||||||
|
grp.eachGroup(func(group *Group) {
|
||||||
|
for _, opt := range group.options {
|
||||||
|
if !opt.canCli() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(wr, ".TP")
|
||||||
|
fmt.Fprintf(wr, "\\fB")
|
||||||
|
|
||||||
|
if opt.ShortName != 0 {
|
||||||
|
fmt.Fprintf(wr, "-%c", opt.ShortName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opt.LongName) != 0 {
|
||||||
|
if opt.ShortName != 0 {
|
||||||
|
fmt.Fprintf(wr, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(wr, "--%s", opt.LongNameWithNamespace())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(wr, "\\fP")
|
||||||
|
if len(opt.Description) != 0 {
|
||||||
|
formatForMan(wr, opt.Description)
|
||||||
|
fmt.Fprintln(wr, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeManPageSubcommands(wr io.Writer, name string, root *Command) {
|
||||||
|
commands := root.sortedCommands()
|
||||||
|
|
||||||
|
for _, c := range commands {
|
||||||
|
var nn string
|
||||||
|
|
||||||
|
if len(name) != 0 {
|
||||||
|
nn = name + " " + c.Name
|
||||||
|
} else {
|
||||||
|
nn = c.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
writeManPageCommand(wr, nn, root, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeManPageCommand(wr io.Writer, name string, root *Command, command *Command) {
|
||||||
|
fmt.Fprintf(wr, ".SS %s\n", name)
|
||||||
|
fmt.Fprintln(wr, command.ShortDescription)
|
||||||
|
|
||||||
|
if len(command.LongDescription) > 0 {
|
||||||
|
fmt.Fprintln(wr, "")
|
||||||
|
|
||||||
|
cmdstart := fmt.Sprintf("The %s command", command.Name)
|
||||||
|
|
||||||
|
if strings.HasPrefix(command.LongDescription, cmdstart) {
|
||||||
|
fmt.Fprintf(wr, "The \\fI%s\\fP command", command.Name)
|
||||||
|
|
||||||
|
formatForMan(wr, command.LongDescription[len(cmdstart):])
|
||||||
|
fmt.Fprintln(wr, "")
|
||||||
|
} else {
|
||||||
|
formatForMan(wr, command.LongDescription)
|
||||||
|
fmt.Fprintln(wr, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var usage string
|
||||||
|
if us, ok := command.data.(Usage); ok {
|
||||||
|
usage = us.Usage()
|
||||||
|
} else if command.hasCliOptions() {
|
||||||
|
usage = fmt.Sprintf("[%s-OPTIONS]", command.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pre string
|
||||||
|
if root.hasCliOptions() {
|
||||||
|
pre = fmt.Sprintf("%s [OPTIONS] %s", root.Name, command.Name)
|
||||||
|
} else {
|
||||||
|
pre = fmt.Sprintf("%s %s", root.Name, command.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(usage) > 0 {
|
||||||
|
fmt.Fprintf(wr, "\n\\fBUsage\\fP: %s %s\n\n", pre, usage)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(command.Aliases) > 0 {
|
||||||
|
fmt.Fprintf(wr, "\n\\fBAliases\\fP: %s\n\n", strings.Join(command.Aliases, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
writeManPageOptions(wr, command.Group)
|
||||||
|
writeManPageSubcommands(wr, name, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteManPage writes a basic man page in groff format to the specified
|
||||||
|
// writer.
|
||||||
|
func (p *Parser) WriteManPage(wr io.Writer) {
|
||||||
|
t := time.Now()
|
||||||
|
|
||||||
|
fmt.Fprintf(wr, ".TH %s 1 \"%s\"\n", p.Name, t.Format("2 January 2006"))
|
||||||
|
fmt.Fprintln(wr, ".SH NAME")
|
||||||
|
fmt.Fprintf(wr, "%s \\- %s\n", p.Name, p.ShortDescription)
|
||||||
|
fmt.Fprintln(wr, ".SH SYNOPSIS")
|
||||||
|
|
||||||
|
usage := p.Usage
|
||||||
|
|
||||||
|
if len(usage) == 0 {
|
||||||
|
usage = "[OPTIONS]"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(wr, "\\fB%s\\fP %s\n", p.Name, usage)
|
||||||
|
fmt.Fprintln(wr, ".SH DESCRIPTION")
|
||||||
|
|
||||||
|
formatForMan(wr, p.LongDescription)
|
||||||
|
fmt.Fprintln(wr, "")
|
||||||
|
|
||||||
|
fmt.Fprintln(wr, ".SH OPTIONS")
|
||||||
|
|
||||||
|
writeManPageOptions(wr, p.Command.Group)
|
||||||
|
|
||||||
|
if len(p.commands) > 0 {
|
||||||
|
fmt.Fprintln(wr, ".SH COMMANDS")
|
||||||
|
|
||||||
|
writeManPageSubcommands(wr, "", p.Command)
|
||||||
|
}
|
||||||
|
}
|
97
Godeps/_workspace/src/github.com/jessevdk/go-flags/marshal_test.go
generated
vendored
Normal file
97
Godeps/_workspace/src/github.com/jessevdk/go-flags/marshal_test.go
generated
vendored
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type marshalled bool
|
||||||
|
|
||||||
|
func (m *marshalled) UnmarshalFlag(value string) error {
|
||||||
|
if value == "yes" {
|
||||||
|
*m = true
|
||||||
|
} else if value == "no" {
|
||||||
|
*m = false
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("`%s' is not a valid value, please specify `yes' or `no'", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m marshalled) MarshalFlag() (string, error) {
|
||||||
|
if m {
|
||||||
|
return "yes", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "no", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type marshalledError bool
|
||||||
|
|
||||||
|
func (m marshalledError) MarshalFlag() (string, error) {
|
||||||
|
return "", newErrorf(ErrMarshal, "Failed to marshal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshal(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value marshalled `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v=yes")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalDefault(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value marshalled `short:"v" default:"yes"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts)
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalOptional(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value marshalled `short:"v" optional:"yes" optional-value:"yes"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalError(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value marshalled `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrMarshal, fmt.Sprintf("invalid argument for flag `%cv' (expected flags.marshalled): `invalid' is not a valid value, please specify `yes' or `no'", defaultShortOptDelimiter), &opts, "-vinvalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalError(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value marshalledError `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, Default)
|
||||||
|
o := p.Command.Groups()[0].Options()[0]
|
||||||
|
|
||||||
|
_, err := convertToString(o.value, o.tag)
|
||||||
|
|
||||||
|
assertError(t, err, ErrMarshal, "Failed to marshal")
|
||||||
|
}
|
140
Godeps/_workspace/src/github.com/jessevdk/go-flags/multitag.go
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/jessevdk/go-flags/multitag.go
generated
vendored
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multiTag struct {
|
||||||
|
value string
|
||||||
|
cache map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMultiTag(v string) multiTag {
|
||||||
|
return multiTag{
|
||||||
|
value: v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiTag) scan() (map[string][]string, error) {
|
||||||
|
v := x.value
|
||||||
|
|
||||||
|
ret := make(map[string][]string)
|
||||||
|
|
||||||
|
// This is mostly copied from reflect.StructTag.Get
|
||||||
|
for v != "" {
|
||||||
|
i := 0
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
for i < len(v) && v[i] == ' ' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
v = v[i:]
|
||||||
|
|
||||||
|
if v == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan to colon to find key
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
for i < len(v) && v[i] != ' ' && v[i] != ':' && v[i] != '"' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= len(v) {
|
||||||
|
return nil, newErrorf(ErrTag, "expected `:' after key name, but got end of tag (in `%v`)", x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v[i] != ':' {
|
||||||
|
return nil, newErrorf(ErrTag, "expected `:' after key name, but got `%v' (in `%v`)", v[i], x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i+1 >= len(v) {
|
||||||
|
return nil, newErrorf(ErrTag, "expected `\"' to start tag value at end of tag (in `%v`)", x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v[i+1] != '"' {
|
||||||
|
return nil, newErrorf(ErrTag, "expected `\"' to start tag value, but got `%v' (in `%v`)", v[i+1], x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := v[:i]
|
||||||
|
v = v[i+1:]
|
||||||
|
|
||||||
|
// Scan quoted string to find value
|
||||||
|
i = 1
|
||||||
|
|
||||||
|
for i < len(v) && v[i] != '"' {
|
||||||
|
if v[i] == '\n' {
|
||||||
|
return nil, newErrorf(ErrTag, "unexpected newline in tag value `%v' (in `%v`)", name, x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v[i] == '\\' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if i >= len(v) {
|
||||||
|
return nil, newErrorf(ErrTag, "expected end of tag value `\"' at end of tag (in `%v`)", x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val, err := strconv.Unquote(v[:i+1])
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, newErrorf(ErrTag, "Malformed value of tag `%v:%v` => %v (in `%v`)", name, v[:i+1], err, x.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
v = v[i+1:]
|
||||||
|
|
||||||
|
ret[name] = append(ret[name], val)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiTag) Parse() error {
|
||||||
|
vals, err := x.scan()
|
||||||
|
x.cache = vals
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiTag) cached() map[string][]string {
|
||||||
|
if x.cache == nil {
|
||||||
|
cache, _ := x.scan()
|
||||||
|
|
||||||
|
if cache == nil {
|
||||||
|
cache = make(map[string][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.cache = cache
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.cache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiTag) Get(key string) string {
|
||||||
|
c := x.cached()
|
||||||
|
|
||||||
|
if v, ok := c[key]; ok {
|
||||||
|
return v[len(v)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiTag) GetMany(key string) []string {
|
||||||
|
c := x.cached()
|
||||||
|
return c[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiTag) Set(key string, value string) {
|
||||||
|
c := x.cached()
|
||||||
|
c[key] = []string{value}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *multiTag) SetMany(key string, value []string) {
|
||||||
|
c := x.cached()
|
||||||
|
c[key] = value
|
||||||
|
}
|
157
Godeps/_workspace/src/github.com/jessevdk/go-flags/option.go
generated
vendored
Normal file
157
Godeps/_workspace/src/github.com/jessevdk/go-flags/option.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option flag information. Contains a description of the option, short and
|
||||||
|
// long name as well as a default value and whether an argument for this
|
||||||
|
// flag is optional.
|
||||||
|
type Option struct {
|
||||||
|
// The description of the option flag. This description is shown
|
||||||
|
// automatically in the built-in help.
|
||||||
|
Description string
|
||||||
|
|
||||||
|
// The short name of the option (a single character). If not 0, the
|
||||||
|
// option flag can be 'activated' using -<ShortName>. Either ShortName
|
||||||
|
// or LongName needs to be non-empty.
|
||||||
|
ShortName rune
|
||||||
|
|
||||||
|
// The long name of the option. If not "", the option flag can be
|
||||||
|
// activated using --<LongName>. Either ShortName or LongName needs
|
||||||
|
// to be non-empty.
|
||||||
|
LongName string
|
||||||
|
|
||||||
|
// The default value of the option.
|
||||||
|
Default []string
|
||||||
|
|
||||||
|
// The optional environment default value key name.
|
||||||
|
EnvDefaultKey string
|
||||||
|
|
||||||
|
// The optional delimiter string for EnvDefaultKey values.
|
||||||
|
EnvDefaultDelim string
|
||||||
|
|
||||||
|
// If true, specifies that the argument to an option flag is optional.
|
||||||
|
// When no argument to the flag is specified on the command line, the
|
||||||
|
// value of Default will be set in the field this option represents.
|
||||||
|
// This is only valid for non-boolean options.
|
||||||
|
OptionalArgument bool
|
||||||
|
|
||||||
|
// The optional value of the option. The optional value is used when
|
||||||
|
// the option flag is marked as having an OptionalArgument. This means
|
||||||
|
// that when the flag is specified, but no option argument is given,
|
||||||
|
// the value of the field this option represents will be set to
|
||||||
|
// OptionalValue. This is only valid for non-boolean options.
|
||||||
|
OptionalValue []string
|
||||||
|
|
||||||
|
// If true, the option _must_ be specified on the command line. If the
|
||||||
|
// option is not specified, the parser will generate an ErrRequired type
|
||||||
|
// error.
|
||||||
|
Required bool
|
||||||
|
|
||||||
|
// A name for the value of an option shown in the Help as --flag [ValueName]
|
||||||
|
ValueName string
|
||||||
|
|
||||||
|
// A mask value to show in the help instead of the default value. This
|
||||||
|
// is useful for hiding sensitive information in the help, such as
|
||||||
|
// passwords.
|
||||||
|
DefaultMask string
|
||||||
|
|
||||||
|
// The group which the option belongs to
|
||||||
|
group *Group
|
||||||
|
|
||||||
|
// The struct field which the option represents.
|
||||||
|
field reflect.StructField
|
||||||
|
|
||||||
|
// The struct field value which the option represents.
|
||||||
|
value reflect.Value
|
||||||
|
|
||||||
|
// Determines if the option will be always quoted in the INI output
|
||||||
|
iniQuote bool
|
||||||
|
|
||||||
|
tag multiTag
|
||||||
|
isSet bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// LongNameWithNamespace returns the option's long name with the group namespaces
|
||||||
|
// prepended by walking up the option's group tree. Namespaces and the long name
|
||||||
|
// itself are separated by the parser's namespace delimiter. If the long name is
|
||||||
|
// empty an empty string is returned.
|
||||||
|
func (option *Option) LongNameWithNamespace() string {
|
||||||
|
if len(option.LongName) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the namespace delimiter from the parser which is always at the
|
||||||
|
// end of the group hierarchy
|
||||||
|
namespaceDelimiter := ""
|
||||||
|
g := option.group
|
||||||
|
|
||||||
|
for {
|
||||||
|
if p, ok := g.parent.(*Parser); ok {
|
||||||
|
namespaceDelimiter = p.NamespaceDelimiter
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i := g.parent.(type) {
|
||||||
|
case *Command:
|
||||||
|
g = i.Group
|
||||||
|
case *Group:
|
||||||
|
g = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// concatenate long name with namespace
|
||||||
|
longName := option.LongName
|
||||||
|
g = option.group
|
||||||
|
|
||||||
|
for g != nil {
|
||||||
|
if g.Namespace != "" {
|
||||||
|
longName = g.Namespace + namespaceDelimiter + longName
|
||||||
|
}
|
||||||
|
|
||||||
|
switch i := g.parent.(type) {
|
||||||
|
case *Command:
|
||||||
|
g = i.Group
|
||||||
|
case *Group:
|
||||||
|
g = i
|
||||||
|
case *Parser:
|
||||||
|
g = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return longName
|
||||||
|
}
|
||||||
|
|
||||||
|
// String converts an option to a human friendly readable string describing the
|
||||||
|
// option.
|
||||||
|
func (option *Option) String() string {
|
||||||
|
var s string
|
||||||
|
var short string
|
||||||
|
|
||||||
|
if option.ShortName != 0 {
|
||||||
|
data := make([]byte, utf8.RuneLen(option.ShortName))
|
||||||
|
utf8.EncodeRune(data, option.ShortName)
|
||||||
|
short = string(data)
|
||||||
|
|
||||||
|
if len(option.LongName) != 0 {
|
||||||
|
s = fmt.Sprintf("%s%s, %s%s",
|
||||||
|
string(defaultShortOptDelimiter), short,
|
||||||
|
defaultLongOptDelimiter, option.LongNameWithNamespace())
|
||||||
|
} else {
|
||||||
|
s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short)
|
||||||
|
}
|
||||||
|
} else if len(option.LongName) != 0 {
|
||||||
|
s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongNameWithNamespace())
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the option value as an interface{}.
|
||||||
|
func (option *Option) Value() interface{} {
|
||||||
|
return option.value.Interface()
|
||||||
|
}
|
182
Godeps/_workspace/src/github.com/jessevdk/go-flags/option_private.go
generated
vendored
Normal file
182
Godeps/_workspace/src/github.com/jessevdk/go-flags/option_private.go
generated
vendored
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set the value of an option to the specified value. An error will be returned
|
||||||
|
// if the specified value could not be converted to the corresponding option
|
||||||
|
// value type.
|
||||||
|
func (option *Option) set(value *string) error {
|
||||||
|
option.isSet = true
|
||||||
|
|
||||||
|
if option.isFunc() {
|
||||||
|
return option.call(value)
|
||||||
|
} else if value != nil {
|
||||||
|
return convert(*value, option.value, option.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
return convert("", option.value, option.tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) canCli() bool {
|
||||||
|
return option.ShortName != 0 || len(option.LongName) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) canArgument() bool {
|
||||||
|
if u := option.isUnmarshaler(); u != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return !option.isBool()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) emptyValue() reflect.Value {
|
||||||
|
tp := option.value.Type()
|
||||||
|
|
||||||
|
if tp.Kind() == reflect.Map {
|
||||||
|
return reflect.MakeMap(tp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.Zero(tp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) empty() {
|
||||||
|
if !option.isFunc() {
|
||||||
|
option.value.Set(option.emptyValue())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) clearDefault() {
|
||||||
|
usedDefault := option.Default
|
||||||
|
if envKey := option.EnvDefaultKey; envKey != "" {
|
||||||
|
// os.Getenv() makes no distinction between undefined and
|
||||||
|
// empty values, so we use syscall.Getenv()
|
||||||
|
if value, ok := syscall.Getenv(envKey); ok {
|
||||||
|
if option.EnvDefaultDelim != "" {
|
||||||
|
usedDefault = strings.Split(value,
|
||||||
|
option.EnvDefaultDelim)
|
||||||
|
} else {
|
||||||
|
usedDefault = []string{value}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(usedDefault) > 0 {
|
||||||
|
option.empty()
|
||||||
|
|
||||||
|
for _, d := range usedDefault {
|
||||||
|
option.set(&d)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tp := option.value.Type()
|
||||||
|
|
||||||
|
switch tp.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
if option.value.IsNil() {
|
||||||
|
option.empty()
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
if option.value.IsNil() {
|
||||||
|
option.empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) valueIsDefault() bool {
|
||||||
|
// Check if the value of the option corresponds to its
|
||||||
|
// default value
|
||||||
|
emptyval := option.emptyValue()
|
||||||
|
|
||||||
|
checkvalptr := reflect.New(emptyval.Type())
|
||||||
|
checkval := reflect.Indirect(checkvalptr)
|
||||||
|
|
||||||
|
checkval.Set(emptyval)
|
||||||
|
|
||||||
|
if len(option.Default) != 0 {
|
||||||
|
for _, v := range option.Default {
|
||||||
|
convert(v, checkval, option.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.DeepEqual(option.value.Interface(), checkval.Interface())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) isUnmarshaler() Unmarshaler {
|
||||||
|
v := option.value
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !v.CanInterface() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i := v.Interface()
|
||||||
|
|
||||||
|
if u, ok := i.(Unmarshaler); ok {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.CanAddr() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) isBool() bool {
|
||||||
|
tp := option.value.Type()
|
||||||
|
|
||||||
|
for {
|
||||||
|
switch tp.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Slice:
|
||||||
|
return (tp.Elem().Kind() == reflect.Bool)
|
||||||
|
case reflect.Func:
|
||||||
|
return tp.NumIn() == 0
|
||||||
|
case reflect.Ptr:
|
||||||
|
tp = tp.Elem()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) isFunc() bool {
|
||||||
|
return option.value.Type().Kind() == reflect.Func
|
||||||
|
}
|
||||||
|
|
||||||
|
func (option *Option) call(value *string) error {
|
||||||
|
var retval []reflect.Value
|
||||||
|
|
||||||
|
if value == nil {
|
||||||
|
retval = option.value.Call(nil)
|
||||||
|
} else {
|
||||||
|
tp := option.value.Type().In(0)
|
||||||
|
|
||||||
|
val := reflect.New(tp)
|
||||||
|
val = reflect.Indirect(val)
|
||||||
|
|
||||||
|
if err := convert(*value, val, option.tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
retval = option.value.Call([]reflect.Value{val})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() {
|
||||||
|
if retval[0].Interface() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return retval[0].Interface().(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
45
Godeps/_workspace/src/github.com/jessevdk/go-flags/options_test.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/jessevdk/go-flags/options_test.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPassDoubleDash(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, PassDoubleDash)
|
||||||
|
ret, err := p.ParseArgs([]string{"-v", "--", "-v", "-g"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"-v", "-g"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPassAfterNonOption(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
p := NewParser(&opts, PassAfterNonOption)
|
||||||
|
ret, err := p.ParseArgs([]string{"-v", "arg", "-v", "-g"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"arg", "-v", "-g"})
|
||||||
|
}
|
67
Godeps/_workspace/src/github.com/jessevdk/go-flags/optstyle_other.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/jessevdk/go-flags/optstyle_other.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultShortOptDelimiter = '-'
|
||||||
|
defaultLongOptDelimiter = "--"
|
||||||
|
defaultNameArgDelimiter = '='
|
||||||
|
)
|
||||||
|
|
||||||
|
func argumentStartsOption(arg string) bool {
|
||||||
|
return len(arg) > 0 && arg[0] == '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
func argumentIsOption(arg string) bool {
|
||||||
|
if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripOptionPrefix returns the option without the prefix and whether or
|
||||||
|
// not the option is a long option or not.
|
||||||
|
func stripOptionPrefix(optname string) (prefix string, name string, islong bool) {
|
||||||
|
if strings.HasPrefix(optname, "--") {
|
||||||
|
return "--", optname[2:], true
|
||||||
|
} else if strings.HasPrefix(optname, "-") {
|
||||||
|
return "-", optname[1:], false
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", optname, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitOption attempts to split the passed option into a name and an argument.
|
||||||
|
// When there is no argument specified, nil will be returned for it.
|
||||||
|
func splitOption(prefix string, option string, islong bool) (string, string, *string) {
|
||||||
|
pos := strings.Index(option, "=")
|
||||||
|
|
||||||
|
if (islong && pos >= 0) || (!islong && pos == 1) {
|
||||||
|
rest := option[pos+1:]
|
||||||
|
return option[:pos], "=", &rest
|
||||||
|
}
|
||||||
|
|
||||||
|
return option, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addHelpGroup adds a new group that contains default help parameters.
|
||||||
|
func (c *Command) addHelpGroup(showHelp func() error) *Group {
|
||||||
|
var help struct {
|
||||||
|
ShowHelp func() error `short:"h" long:"help" description:"Show this help message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
help.ShowHelp = showHelp
|
||||||
|
ret, _ := c.AddGroup("Help Options", "", &help)
|
||||||
|
ret.isBuiltinHelp = true
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
106
Godeps/_workspace/src/github.com/jessevdk/go-flags/optstyle_windows.go
generated
vendored
Normal file
106
Godeps/_workspace/src/github.com/jessevdk/go-flags/optstyle_windows.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Windows uses a front slash for both short and long options. Also it uses
|
||||||
|
// a colon for name/argument delimter.
|
||||||
|
const (
|
||||||
|
defaultShortOptDelimiter = '/'
|
||||||
|
defaultLongOptDelimiter = "/"
|
||||||
|
defaultNameArgDelimiter = ':'
|
||||||
|
)
|
||||||
|
|
||||||
|
func argumentStartsOption(arg string) bool {
|
||||||
|
return len(arg) > 0 && (arg[0] == '-' || arg[0] == '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
func argumentIsOption(arg string) bool {
|
||||||
|
// Windows-style options allow front slash for the option
|
||||||
|
// delimiter.
|
||||||
|
if len(arg) > 1 && arg[0] == '/' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(arg) > 1 && arg[0] == '-' && arg[1] != '-' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(arg) > 2 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripOptionPrefix returns the option without the prefix and whether or
|
||||||
|
// not the option is a long option or not.
|
||||||
|
func stripOptionPrefix(optname string) (prefix string, name string, islong bool) {
|
||||||
|
// Determine if the argument is a long option or not. Windows
|
||||||
|
// typically supports both long and short options with a single
|
||||||
|
// front slash as the option delimiter, so handle this situation
|
||||||
|
// nicely.
|
||||||
|
possplit := 0
|
||||||
|
|
||||||
|
if strings.HasPrefix(optname, "--") {
|
||||||
|
possplit = 2
|
||||||
|
islong = true
|
||||||
|
} else if strings.HasPrefix(optname, "-") {
|
||||||
|
possplit = 1
|
||||||
|
islong = false
|
||||||
|
} else if strings.HasPrefix(optname, "/") {
|
||||||
|
possplit = 1
|
||||||
|
islong = len(optname) > 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return optname[:possplit], optname[possplit:], islong
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitOption attempts to split the passed option into a name and an argument.
|
||||||
|
// When there is no argument specified, nil will be returned for it.
|
||||||
|
func splitOption(prefix string, option string, islong bool) (string, string, *string) {
|
||||||
|
if len(option) == 0 {
|
||||||
|
return option, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows typically uses a colon for the option name and argument
|
||||||
|
// delimiter while POSIX typically uses an equals. Support both styles,
|
||||||
|
// but don't allow the two to be mixed. That is to say /foo:bar and
|
||||||
|
// --foo=bar are acceptable, but /foo=bar and --foo:bar are not.
|
||||||
|
var pos int
|
||||||
|
var sp string
|
||||||
|
|
||||||
|
if prefix == "/" {
|
||||||
|
sp = ":"
|
||||||
|
pos = strings.Index(option, sp)
|
||||||
|
} else if len(prefix) > 0 {
|
||||||
|
sp = "="
|
||||||
|
pos = strings.Index(option, sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (islong && pos >= 0) || (!islong && pos == 1) {
|
||||||
|
rest := option[pos+1:]
|
||||||
|
return option[:pos], sp, &rest
|
||||||
|
}
|
||||||
|
|
||||||
|
return option, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// addHelpGroup adds a new group that contains default help parameters.
|
||||||
|
func (c *Command) addHelpGroup(showHelp func() error) *Group {
|
||||||
|
// Windows CLI applications typically use /? for help, so make both
|
||||||
|
// that available as well as the POSIX style h and help.
|
||||||
|
var help struct {
|
||||||
|
ShowHelpWindows func() error `short:"?" description:"Show this help message"`
|
||||||
|
ShowHelpPosix func() error `short:"h" long:"help" description:"Show this help message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
help.ShowHelpWindows = showHelp
|
||||||
|
help.ShowHelpPosix = showHelp
|
||||||
|
|
||||||
|
ret, _ := c.AddGroup("Help Options", "", &help)
|
||||||
|
ret.isBuiltinHelp = true
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
286
Godeps/_workspace/src/github.com/jessevdk/go-flags/parser.go
generated
vendored
Normal file
286
Godeps/_workspace/src/github.com/jessevdk/go-flags/parser.go
generated
vendored
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
// Copyright 2012 Jesse van den Kieboom. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Parser provides command line option parsing. It can contain several
|
||||||
|
// option groups each with their own set of options.
|
||||||
|
type Parser struct {
|
||||||
|
// Embedded, see Command for more information
|
||||||
|
*Command
|
||||||
|
|
||||||
|
// A usage string to be displayed in the help message.
|
||||||
|
Usage string
|
||||||
|
|
||||||
|
// Option flags changing the behavior of the parser.
|
||||||
|
Options Options
|
||||||
|
|
||||||
|
// NamespaceDelimiter separates group namespaces and option long names
|
||||||
|
NamespaceDelimiter string
|
||||||
|
|
||||||
|
// UnknownOptionsHandler is a function which gets called when the parser
|
||||||
|
// encounters an unknown option. The function receives the unknown option
|
||||||
|
// name, a SplitArgument which specifies its value if set with an argument
|
||||||
|
// separator, and the remaining command line arguments.
|
||||||
|
// It should return a new list of remaining arguments to continue parsing,
|
||||||
|
// or an error to indicate a parse failure.
|
||||||
|
UnknownOptionHandler func(option string, arg SplitArgument, args []string) ([]string, error)
|
||||||
|
|
||||||
|
internalError error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SplitArgument represents the argument value of an option that was passed using
|
||||||
|
// an argument separator.
|
||||||
|
type SplitArgument interface {
|
||||||
|
// String returns the option's value as a string, and a boolean indicating
|
||||||
|
// if the option was present.
|
||||||
|
Value() (string, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type strArgument struct {
|
||||||
|
value *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s strArgument) Value() (string, bool) {
|
||||||
|
if s.value == nil {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return *s.value, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options provides parser options that change the behavior of the option
|
||||||
|
// parser.
|
||||||
|
type Options uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
// None indicates no options.
|
||||||
|
None Options = 0
|
||||||
|
|
||||||
|
// HelpFlag adds a default Help Options group to the parser containing
|
||||||
|
// -h and --help options. When either -h or --help is specified on the
|
||||||
|
// command line, the parser will return the special error of type
|
||||||
|
// ErrHelp. When PrintErrors is also specified, then the help message
|
||||||
|
// will also be automatically printed to os.Stderr.
|
||||||
|
HelpFlag = 1 << iota
|
||||||
|
|
||||||
|
// PassDoubleDash passes all arguments after a double dash, --, as
|
||||||
|
// remaining command line arguments (i.e. they will not be parsed for
|
||||||
|
// flags).
|
||||||
|
PassDoubleDash
|
||||||
|
|
||||||
|
// IgnoreUnknown ignores any unknown options and passes them as
|
||||||
|
// remaining command line arguments instead of generating an error.
|
||||||
|
IgnoreUnknown
|
||||||
|
|
||||||
|
// PrintErrors prints any errors which occurred during parsing to
|
||||||
|
// os.Stderr.
|
||||||
|
PrintErrors
|
||||||
|
|
||||||
|
// PassAfterNonOption passes all arguments after the first non option
|
||||||
|
// as remaining command line arguments. This is equivalent to strict
|
||||||
|
// POSIX processing.
|
||||||
|
PassAfterNonOption
|
||||||
|
|
||||||
|
// Default is a convenient default set of options which should cover
|
||||||
|
// most of the uses of the flags package.
|
||||||
|
Default = HelpFlag | PrintErrors | PassDoubleDash
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse is a convenience function to parse command line options with default
|
||||||
|
// settings. The provided data is a pointer to a struct representing the
|
||||||
|
// default option group (named "Application Options"). For more control, use
|
||||||
|
// flags.NewParser.
|
||||||
|
func Parse(data interface{}) ([]string, error) {
|
||||||
|
return NewParser(data, Default).Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseArgs is a convenience function to parse command line options with default
|
||||||
|
// settings. The provided data is a pointer to a struct representing the
|
||||||
|
// default option group (named "Application Options"). The args argument is
|
||||||
|
// the list of command line arguments to parse. If you just want to parse the
|
||||||
|
// default program command line arguments (i.e. os.Args), then use flags.Parse
|
||||||
|
// instead. For more control, use flags.NewParser.
|
||||||
|
func ParseArgs(data interface{}, args []string) ([]string, error) {
|
||||||
|
return NewParser(data, Default).ParseArgs(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewParser creates a new parser. It uses os.Args[0] as the application
|
||||||
|
// name and then calls Parser.NewNamedParser (see Parser.NewNamedParser for
|
||||||
|
// more details). The provided data is a pointer to a struct representing the
|
||||||
|
// default option group (named "Application Options"), or nil if the default
|
||||||
|
// group should not be added. The options parameter specifies a set of options
|
||||||
|
// for the parser.
|
||||||
|
func NewParser(data interface{}, options Options) *Parser {
|
||||||
|
p := NewNamedParser(path.Base(os.Args[0]), options)
|
||||||
|
|
||||||
|
if data != nil {
|
||||||
|
g, err := p.AddGroup("Application Options", "", data)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
g.parent = p
|
||||||
|
}
|
||||||
|
|
||||||
|
p.internalError = err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNamedParser creates a new parser. The appname is used to display the
|
||||||
|
// executable name in the built-in help message. Option groups and commands can
|
||||||
|
// be added to this parser by using AddGroup and AddCommand.
|
||||||
|
func NewNamedParser(appname string, options Options) *Parser {
|
||||||
|
p := &Parser{
|
||||||
|
Command: newCommand(appname, "", "", nil),
|
||||||
|
Options: options,
|
||||||
|
NamespaceDelimiter: ".",
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Command.parent = p
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses the command line arguments from os.Args using Parser.ParseArgs.
|
||||||
|
// For more detailed information see ParseArgs.
|
||||||
|
func (p *Parser) Parse() ([]string, error) {
|
||||||
|
return p.ParseArgs(os.Args[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseArgs parses the command line arguments according to the option groups that
|
||||||
|
// were added to the parser. On successful parsing of the arguments, the
|
||||||
|
// remaining, non-option, arguments (if any) are returned. The returned error
|
||||||
|
// indicates a parsing error and can be used with PrintError to display
|
||||||
|
// contextual information on where the error occurred exactly.
|
||||||
|
//
|
||||||
|
// When the common help group has been added (AddHelp) and either -h or --help
|
||||||
|
// was specified in the command line arguments, a help message will be
|
||||||
|
// automatically printed. Furthermore, the special error type ErrHelp is returned.
|
||||||
|
// It is up to the caller to exit the program if so desired.
|
||||||
|
func (p *Parser) ParseArgs(args []string) ([]string, error) {
|
||||||
|
if p.internalError != nil {
|
||||||
|
return nil, p.internalError
|
||||||
|
}
|
||||||
|
|
||||||
|
p.clearIsSet()
|
||||||
|
|
||||||
|
// Add built-in help group to all commands if necessary
|
||||||
|
if (p.Options & HelpFlag) != None {
|
||||||
|
p.addHelpGroups(p.showBuiltinHelp)
|
||||||
|
}
|
||||||
|
|
||||||
|
compval := os.Getenv("GO_FLAGS_COMPLETION")
|
||||||
|
|
||||||
|
if len(compval) != 0 {
|
||||||
|
comp := &completion{parser: p}
|
||||||
|
|
||||||
|
if compval == "verbose" {
|
||||||
|
comp.ShowDescriptions = true
|
||||||
|
}
|
||||||
|
|
||||||
|
comp.execute(args)
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &parseState{
|
||||||
|
args: args,
|
||||||
|
retargs: make([]string, 0, len(args)),
|
||||||
|
}
|
||||||
|
|
||||||
|
p.fillParseState(s)
|
||||||
|
|
||||||
|
for !s.eof() {
|
||||||
|
arg := s.pop()
|
||||||
|
|
||||||
|
// When PassDoubleDash is set and we encounter a --, then
|
||||||
|
// simply append all the rest as arguments and break out
|
||||||
|
if (p.Options&PassDoubleDash) != None && arg == "--" {
|
||||||
|
s.addArgs(s.args...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !argumentIsOption(arg) {
|
||||||
|
// Note: this also sets s.err, so we can just check for
|
||||||
|
// nil here and use s.err later
|
||||||
|
if p.parseNonOption(s) != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
prefix, optname, islong := stripOptionPrefix(arg)
|
||||||
|
optname, _, argument := splitOption(prefix, optname, islong)
|
||||||
|
|
||||||
|
if islong {
|
||||||
|
err = p.parseLong(s, optname, argument)
|
||||||
|
} else {
|
||||||
|
err = p.parseShort(s, optname, argument)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ignoreUnknown := (p.Options & IgnoreUnknown) != None
|
||||||
|
parseErr := wrapError(err)
|
||||||
|
|
||||||
|
if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) {
|
||||||
|
s.err = parseErr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if ignoreUnknown {
|
||||||
|
s.addArgs(arg)
|
||||||
|
} else if p.UnknownOptionHandler != nil {
|
||||||
|
modifiedArgs, err := p.UnknownOptionHandler(optname, strArgument{argument}, s.args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.err = err
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
s.args = modifiedArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.err == nil {
|
||||||
|
p.eachCommand(func(c *Command) {
|
||||||
|
c.eachGroup(func(g *Group) {
|
||||||
|
for _, option := range g.options {
|
||||||
|
if option.isSet {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
option.clearDefault()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, true)
|
||||||
|
|
||||||
|
s.checkRequired(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reterr error
|
||||||
|
|
||||||
|
if s.err != nil {
|
||||||
|
reterr = s.err
|
||||||
|
} else if len(s.command.commands) != 0 && !s.command.SubcommandsOptional {
|
||||||
|
reterr = s.estimateCommand()
|
||||||
|
} else if cmd, ok := s.command.data.(Commander); ok {
|
||||||
|
reterr = cmd.Execute(s.retargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reterr != nil {
|
||||||
|
return append([]string{s.arg}, s.args...), p.printError(reterr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.retargs, nil
|
||||||
|
}
|
340
Godeps/_workspace/src/github.com/jessevdk/go-flags/parser_private.go
generated
vendored
Normal file
340
Godeps/_workspace/src/github.com/jessevdk/go-flags/parser_private.go
generated
vendored
Normal file
@ -0,0 +1,340 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseState struct {
|
||||||
|
arg string
|
||||||
|
args []string
|
||||||
|
retargs []string
|
||||||
|
positional []*Arg
|
||||||
|
err error
|
||||||
|
|
||||||
|
command *Command
|
||||||
|
lookup lookup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseState) eof() bool {
|
||||||
|
return len(p.args) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseState) pop() string {
|
||||||
|
if p.eof() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
p.arg = p.args[0]
|
||||||
|
p.args = p.args[1:]
|
||||||
|
|
||||||
|
return p.arg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseState) peek() string {
|
||||||
|
if p.eof() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.args[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseState) checkRequired(parser *Parser) error {
|
||||||
|
c := parser.Command
|
||||||
|
|
||||||
|
var required []*Option
|
||||||
|
|
||||||
|
for c != nil {
|
||||||
|
c.eachGroup(func(g *Group) {
|
||||||
|
for _, option := range g.options {
|
||||||
|
if !option.isSet && option.Required {
|
||||||
|
required = append(required, option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
c = c.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(required) == 0 {
|
||||||
|
if len(p.positional) > 0 && p.command.ArgsRequired {
|
||||||
|
var reqnames []string
|
||||||
|
|
||||||
|
for _, arg := range p.positional {
|
||||||
|
if arg.isRemaining() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
reqnames = append(reqnames, "`"+arg.Name+"`")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(reqnames) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
if len(reqnames) == 1 {
|
||||||
|
msg = fmt.Sprintf("the required argument %s was not provided", reqnames[0])
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("the required arguments %s and %s were not provided",
|
||||||
|
strings.Join(reqnames[:len(reqnames)-1], ", "), reqnames[len(reqnames)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
p.err = newError(ErrRequired, msg)
|
||||||
|
return p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]string, 0, len(required))
|
||||||
|
|
||||||
|
for _, k := range required {
|
||||||
|
names = append(names, "`"+k.String()+"'")
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
var msg string
|
||||||
|
|
||||||
|
if len(names) == 1 {
|
||||||
|
msg = fmt.Sprintf("the required flag %s was not specified", names[0])
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("the required flags %s and %s were not specified",
|
||||||
|
strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
|
||||||
|
}
|
||||||
|
|
||||||
|
p.err = newError(ErrRequired, msg)
|
||||||
|
return p.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseState) estimateCommand() error {
|
||||||
|
commands := p.command.sortedCommands()
|
||||||
|
cmdnames := make([]string, len(commands))
|
||||||
|
|
||||||
|
for i, v := range commands {
|
||||||
|
cmdnames[i] = v.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg string
|
||||||
|
var errtype ErrorType
|
||||||
|
|
||||||
|
if len(p.retargs) != 0 {
|
||||||
|
c, l := closestChoice(p.retargs[0], cmdnames)
|
||||||
|
msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
|
||||||
|
errtype = ErrUnknownCommand
|
||||||
|
|
||||||
|
if float32(l)/float32(len(c)) < 0.5 {
|
||||||
|
msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c)
|
||||||
|
} else if len(cmdnames) == 1 {
|
||||||
|
msg = fmt.Sprintf("%s. You should use the %s command",
|
||||||
|
msg,
|
||||||
|
cmdnames[0])
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
|
||||||
|
msg,
|
||||||
|
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
|
||||||
|
cmdnames[len(cmdnames)-1])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errtype = ErrCommandRequired
|
||||||
|
|
||||||
|
if len(cmdnames) == 1 {
|
||||||
|
msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
|
||||||
|
} else {
|
||||||
|
msg = fmt.Sprintf("Please specify one command of: %s or %s",
|
||||||
|
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
|
||||||
|
cmdnames[len(cmdnames)-1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newError(errtype, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (err error) {
|
||||||
|
if !option.canArgument() {
|
||||||
|
if argument != nil {
|
||||||
|
return newErrorf(ErrNoArgumentForBool, "bool flag `%s' cannot have an argument", option)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = option.set(nil)
|
||||||
|
} else if argument != nil || (canarg && !s.eof()) {
|
||||||
|
var arg string
|
||||||
|
|
||||||
|
if argument != nil {
|
||||||
|
arg = *argument
|
||||||
|
} else {
|
||||||
|
arg = s.pop()
|
||||||
|
|
||||||
|
if argumentIsOption(arg) {
|
||||||
|
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got option `%s'", option, arg)
|
||||||
|
} else if p.Options&PassDoubleDash != 0 && arg == "--" {
|
||||||
|
return newErrorf(ErrExpectedArgument, "expected argument for flag `%s', but got double dash `--'", option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if option.tag.Get("unquote") != "false" {
|
||||||
|
arg, err = unquoteIfPossible(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = option.set(&arg)
|
||||||
|
}
|
||||||
|
} else if option.OptionalArgument {
|
||||||
|
option.empty()
|
||||||
|
|
||||||
|
for _, v := range option.OptionalValue {
|
||||||
|
err = option.set(&v)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = newErrorf(ErrExpectedArgument, "expected argument for flag `%s'", option)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*Error); !ok {
|
||||||
|
err = newErrorf(ErrMarshal, "invalid argument for flag `%s' (expected %s): %s",
|
||||||
|
option,
|
||||||
|
option.value.Type(),
|
||||||
|
err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseLong(s *parseState, name string, argument *string) error {
|
||||||
|
if option := s.lookup.longNames[name]; option != nil {
|
||||||
|
// Only long options that are required can consume an argument
|
||||||
|
// from the argument list
|
||||||
|
canarg := !option.OptionalArgument
|
||||||
|
|
||||||
|
return p.parseOption(s, name, option, canarg, argument)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) {
|
||||||
|
c, n := utf8.DecodeRuneInString(optname)
|
||||||
|
|
||||||
|
if n == len(optname) {
|
||||||
|
return optname, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
first := string(c)
|
||||||
|
|
||||||
|
if option := s.lookup.shortNames[first]; option != nil && option.canArgument() {
|
||||||
|
arg := optname[n:]
|
||||||
|
return first, &arg
|
||||||
|
}
|
||||||
|
|
||||||
|
return optname, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseShort(s *parseState, optname string, argument *string) error {
|
||||||
|
if argument == nil {
|
||||||
|
optname, argument = p.splitShortConcatArg(s, optname)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range optname {
|
||||||
|
shortname := string(c)
|
||||||
|
|
||||||
|
if option := s.lookup.shortNames[shortname]; option != nil {
|
||||||
|
// Only the last short argument can consume an argument from
|
||||||
|
// the arguments list, and only if it's non optional
|
||||||
|
canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument
|
||||||
|
|
||||||
|
if err := p.parseOption(s, shortname, option, canarg, argument); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return newErrorf(ErrUnknownFlag, "unknown flag `%s'", shortname)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only the first option can have a concatted argument, so just
|
||||||
|
// clear argument here
|
||||||
|
argument = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseState) addArgs(args ...string) error {
|
||||||
|
for len(p.positional) > 0 && len(args) > 0 {
|
||||||
|
arg := p.positional[0]
|
||||||
|
|
||||||
|
if err := convert(args[0], arg.value, arg.tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !arg.isRemaining() {
|
||||||
|
p.positional = p.positional[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
args = args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
p.retargs = append(p.retargs, args...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) parseNonOption(s *parseState) error {
|
||||||
|
if len(s.positional) > 0 {
|
||||||
|
return s.addArgs(s.arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd := s.lookup.commands[s.arg]; cmd != nil {
|
||||||
|
s.command.Active = cmd
|
||||||
|
cmd.fillParseState(s)
|
||||||
|
} else if (p.Options & PassAfterNonOption) != None {
|
||||||
|
// If PassAfterNonOption is set then all remaining arguments
|
||||||
|
// are considered positional
|
||||||
|
if err := s.addArgs(s.arg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := s.addArgs(s.args...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.args = []string{}
|
||||||
|
} else {
|
||||||
|
return s.addArgs(s.arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) showBuiltinHelp() error {
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
p.WriteHelp(&b)
|
||||||
|
return newError(ErrHelp, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) printError(err error) error {
|
||||||
|
if err != nil && (p.Options&PrintErrors) != None {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Parser) clearIsSet() {
|
||||||
|
p.eachCommand(func(c *Command) {
|
||||||
|
c.eachGroup(func(g *Group) {
|
||||||
|
for _, option := range g.options {
|
||||||
|
option.isSet = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, true)
|
||||||
|
}
|
431
Godeps/_workspace/src/github.com/jessevdk/go-flags/parser_test.go
generated
vendored
Normal file
431
Godeps/_workspace/src/github.com/jessevdk/go-flags/parser_test.go
generated
vendored
Normal file
@ -0,0 +1,431 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type defaultOptions struct {
|
||||||
|
Int int `long:"i"`
|
||||||
|
IntDefault int `long:"id" default:"1"`
|
||||||
|
|
||||||
|
String string `long:"str"`
|
||||||
|
StringDefault string `long:"strd" default:"abc"`
|
||||||
|
StringNotUnquoted string `long:"strnot" unquote:"false"`
|
||||||
|
|
||||||
|
Time time.Duration `long:"t"`
|
||||||
|
TimeDefault time.Duration `long:"td" default:"1m"`
|
||||||
|
|
||||||
|
Map map[string]int `long:"m"`
|
||||||
|
MapDefault map[string]int `long:"md" default:"a:1"`
|
||||||
|
|
||||||
|
Slice []int `long:"s"`
|
||||||
|
SliceDefault []int `long:"sd" default:"1" default:"2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaults(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
msg string
|
||||||
|
args []string
|
||||||
|
expected defaultOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
msg: "no arguments, expecting default values",
|
||||||
|
args: []string{},
|
||||||
|
expected: defaultOptions{
|
||||||
|
Int: 0,
|
||||||
|
IntDefault: 1,
|
||||||
|
|
||||||
|
String: "",
|
||||||
|
StringDefault: "abc",
|
||||||
|
|
||||||
|
Time: 0,
|
||||||
|
TimeDefault: time.Minute,
|
||||||
|
|
||||||
|
Map: map[string]int{},
|
||||||
|
MapDefault: map[string]int{"a": 1},
|
||||||
|
|
||||||
|
Slice: []int{},
|
||||||
|
SliceDefault: []int{1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "non-zero value arguments, expecting overwritten arguments",
|
||||||
|
args: []string{"--i=3", "--id=3", "--str=def", "--strd=def", "--t=3ms", "--td=3ms", "--m=c:3", "--md=c:3", "--s=3", "--sd=3"},
|
||||||
|
expected: defaultOptions{
|
||||||
|
Int: 3,
|
||||||
|
IntDefault: 3,
|
||||||
|
|
||||||
|
String: "def",
|
||||||
|
StringDefault: "def",
|
||||||
|
|
||||||
|
Time: 3 * time.Millisecond,
|
||||||
|
TimeDefault: 3 * time.Millisecond,
|
||||||
|
|
||||||
|
Map: map[string]int{"c": 3},
|
||||||
|
MapDefault: map[string]int{"c": 3},
|
||||||
|
|
||||||
|
Slice: []int{3},
|
||||||
|
SliceDefault: []int{3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "zero value arguments, expecting overwritten arguments",
|
||||||
|
args: []string{"--i=0", "--id=0", "--str", "", "--strd=\"\"", "--t=0ms", "--td=0s", "--m=:0", "--md=:0", "--s=0", "--sd=0"},
|
||||||
|
expected: defaultOptions{
|
||||||
|
Int: 0,
|
||||||
|
IntDefault: 0,
|
||||||
|
|
||||||
|
String: "",
|
||||||
|
StringDefault: "",
|
||||||
|
|
||||||
|
Time: 0,
|
||||||
|
TimeDefault: 0,
|
||||||
|
|
||||||
|
Map: map[string]int{"": 0},
|
||||||
|
MapDefault: map[string]int{"": 0},
|
||||||
|
|
||||||
|
Slice: []int{0},
|
||||||
|
SliceDefault: []int{0},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var opts defaultOptions
|
||||||
|
|
||||||
|
_, err := ParseArgs(&opts, test.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Slice == nil {
|
||||||
|
opts.Slice = []int{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(opts, test.expected) {
|
||||||
|
t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnquoting(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
arg string
|
||||||
|
err error
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
arg: "\"abc",
|
||||||
|
err: strconv.ErrSyntax,
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: "\"\"abc\"",
|
||||||
|
err: strconv.ErrSyntax,
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: "\"abc\"",
|
||||||
|
err: nil,
|
||||||
|
value: "abc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: "\"\\\"abc\\\"\"",
|
||||||
|
err: nil,
|
||||||
|
value: "\"abc\"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: "\"\\\"abc\"",
|
||||||
|
err: nil,
|
||||||
|
value: "\"abc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var opts defaultOptions
|
||||||
|
|
||||||
|
for _, delimiter := range []bool{false, true} {
|
||||||
|
p := NewParser(&opts, None)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if delimiter {
|
||||||
|
_, err = p.ParseArgs([]string{"--str=" + test.arg, "--strnot=" + test.arg})
|
||||||
|
} else {
|
||||||
|
_, err = p.ParseArgs([]string{"--str", test.arg, "--strnot", test.arg})
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.err == nil {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if test.value != opts.String {
|
||||||
|
t.Fatalf("Expected String to be %q but got %q", test.value, opts.String)
|
||||||
|
}
|
||||||
|
if q := strconv.Quote(test.value); q != opts.StringNotUnquoted {
|
||||||
|
t.Fatalf("Expected StringDefault to be %q but got %q", q, opts.StringNotUnquoted)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("Expected error")
|
||||||
|
} else if e, ok := err.(*Error); ok {
|
||||||
|
if strings.HasPrefix(e.Message, test.err.Error()) {
|
||||||
|
t.Fatalf("Expected error message to end with %q but got %v", test.err.Error(), e.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// envRestorer keeps a copy of a set of env variables and can restore the env from them
|
||||||
|
type envRestorer struct {
|
||||||
|
env map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *envRestorer) Restore() {
|
||||||
|
os.Clearenv()
|
||||||
|
for k, v := range r.env {
|
||||||
|
os.Setenv(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvSnapshot returns a snapshot of the currently set env variables
|
||||||
|
func EnvSnapshot() *envRestorer {
|
||||||
|
r := envRestorer{make(map[string]string)}
|
||||||
|
for _, kv := range os.Environ() {
|
||||||
|
parts := strings.SplitN(kv, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
panic("got a weird env variable: " + kv)
|
||||||
|
}
|
||||||
|
r.env[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
return &r
|
||||||
|
}
|
||||||
|
|
||||||
|
type envDefaultOptions struct {
|
||||||
|
Int int `long:"i" default:"1" env:"TEST_I"`
|
||||||
|
Time time.Duration `long:"t" default:"1m" env:"TEST_T"`
|
||||||
|
Map map[string]int `long:"m" default:"a:1" env:"TEST_M" env-delim:";"`
|
||||||
|
Slice []int `long:"s" default:"1" default:"2" env:"TEST_S" env-delim:","`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvDefaults(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
msg string
|
||||||
|
args []string
|
||||||
|
expected envDefaultOptions
|
||||||
|
env map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
msg: "no arguments, no env, expecting default values",
|
||||||
|
args: []string{},
|
||||||
|
expected: envDefaultOptions{
|
||||||
|
Int: 1,
|
||||||
|
Time: time.Minute,
|
||||||
|
Map: map[string]int{"a": 1},
|
||||||
|
Slice: []int{1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "no arguments, env defaults, expecting env default values",
|
||||||
|
args: []string{},
|
||||||
|
expected: envDefaultOptions{
|
||||||
|
Int: 2,
|
||||||
|
Time: 2 * time.Minute,
|
||||||
|
Map: map[string]int{"a": 2, "b": 3},
|
||||||
|
Slice: []int{4, 5, 6},
|
||||||
|
},
|
||||||
|
env: map[string]string{
|
||||||
|
"TEST_I": "2",
|
||||||
|
"TEST_T": "2m",
|
||||||
|
"TEST_M": "a:2;b:3",
|
||||||
|
"TEST_S": "4,5,6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "non-zero value arguments, expecting overwritten arguments",
|
||||||
|
args: []string{"--i=3", "--t=3ms", "--m=c:3", "--s=3"},
|
||||||
|
expected: envDefaultOptions{
|
||||||
|
Int: 3,
|
||||||
|
Time: 3 * time.Millisecond,
|
||||||
|
Map: map[string]int{"c": 3},
|
||||||
|
Slice: []int{3},
|
||||||
|
},
|
||||||
|
env: map[string]string{
|
||||||
|
"TEST_I": "2",
|
||||||
|
"TEST_T": "2m",
|
||||||
|
"TEST_M": "a:2;b:3",
|
||||||
|
"TEST_S": "4,5,6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
msg: "zero value arguments, expecting overwritten arguments",
|
||||||
|
args: []string{"--i=0", "--t=0ms", "--m=:0", "--s=0"},
|
||||||
|
expected: envDefaultOptions{
|
||||||
|
Int: 0,
|
||||||
|
Time: 0,
|
||||||
|
Map: map[string]int{"": 0},
|
||||||
|
Slice: []int{0},
|
||||||
|
},
|
||||||
|
env: map[string]string{
|
||||||
|
"TEST_I": "2",
|
||||||
|
"TEST_T": "2m",
|
||||||
|
"TEST_M": "a:2;b:3",
|
||||||
|
"TEST_S": "4,5,6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
oldEnv := EnvSnapshot()
|
||||||
|
defer oldEnv.Restore()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
var opts envDefaultOptions
|
||||||
|
oldEnv.Restore()
|
||||||
|
for envKey, envValue := range test.env {
|
||||||
|
os.Setenv(envKey, envValue)
|
||||||
|
}
|
||||||
|
_, err := ParseArgs(&opts, test.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Slice == nil {
|
||||||
|
opts.Slice = []int{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(opts, test.expected) {
|
||||||
|
t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOptionAsArgument(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
args []string
|
||||||
|
expectError bool
|
||||||
|
errType ErrorType
|
||||||
|
errMsg string
|
||||||
|
rest []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// short option must not be accepted as argument
|
||||||
|
args: []string{"--string-slice", "foobar", "--string-slice", "-o"},
|
||||||
|
expectError: true,
|
||||||
|
errType: ErrExpectedArgument,
|
||||||
|
errMsg: "expected argument for flag `--string-slice', but got option `-o'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// long option must not be accepted as argument
|
||||||
|
args: []string{"--string-slice", "foobar", "--string-slice", "--other-option"},
|
||||||
|
expectError: true,
|
||||||
|
errType: ErrExpectedArgument,
|
||||||
|
errMsg: "expected argument for flag `--string-slice', but got option `--other-option'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// long option must not be accepted as argument
|
||||||
|
args: []string{"--string-slice", "--"},
|
||||||
|
expectError: true,
|
||||||
|
errType: ErrExpectedArgument,
|
||||||
|
errMsg: "expected argument for flag `--string-slice', but got double dash `--'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// quoted and appended option should be accepted as argument (even if it looks like an option)
|
||||||
|
args: []string{"--string-slice", "foobar", "--string-slice=\"--other-option\""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Accept any single character arguments including '-'
|
||||||
|
args: []string{"--string-slice", "-"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
args: []string{"-o", "-", "-"},
|
||||||
|
rest: []string{"-", "-"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var opts struct {
|
||||||
|
StringSlice []string `long:"string-slice"`
|
||||||
|
OtherOption bool `long:"other-option" short:"o"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.expectError {
|
||||||
|
assertParseFail(t, test.errType, test.errMsg, &opts, test.args...)
|
||||||
|
} else {
|
||||||
|
args := assertParseSuccess(t, &opts, test.args...)
|
||||||
|
|
||||||
|
assertStringArray(t, args, test.rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnknownFlagHandler(t *testing.T) {
|
||||||
|
|
||||||
|
var opts struct {
|
||||||
|
Flag1 string `long:"flag1"`
|
||||||
|
Flag2 string `long:"flag2"`
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewParser(&opts, None)
|
||||||
|
|
||||||
|
var unknownFlag1 string
|
||||||
|
var unknownFlag2 bool
|
||||||
|
var unknownFlag3 string
|
||||||
|
|
||||||
|
// Set up a callback to intercept unknown options during parsing
|
||||||
|
p.UnknownOptionHandler = func(option string, arg SplitArgument, args []string) ([]string, error) {
|
||||||
|
if option == "unknownFlag1" {
|
||||||
|
if argValue, ok := arg.Value(); ok {
|
||||||
|
unknownFlag1 = argValue
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
// consume a value from remaining args list
|
||||||
|
unknownFlag1 = args[0]
|
||||||
|
return args[1:], nil
|
||||||
|
} else if option == "unknownFlag2" {
|
||||||
|
// treat this one as a bool switch, don't consume any args
|
||||||
|
unknownFlag2 = true
|
||||||
|
return args, nil
|
||||||
|
} else if option == "unknownFlag3" {
|
||||||
|
if argValue, ok := arg.Value(); ok {
|
||||||
|
unknownFlag3 = argValue
|
||||||
|
return args, nil
|
||||||
|
}
|
||||||
|
// consume a value from remaining args list
|
||||||
|
unknownFlag3 = args[0]
|
||||||
|
return args[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return args, fmt.Errorf("Unknown flag: %v", option)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse args containing some unknown flags, verify that
|
||||||
|
// our callback can handle all of them
|
||||||
|
_, err := p.ParseArgs([]string{"--flag1=stuff", "--unknownFlag1", "blah", "--unknownFlag2", "--unknownFlag3=baz", "--flag2=foo"})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
assertErrorf(t, "Parser returned unexpected error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertString(t, opts.Flag1, "stuff")
|
||||||
|
assertString(t, opts.Flag2, "foo")
|
||||||
|
assertString(t, unknownFlag1, "blah")
|
||||||
|
assertString(t, unknownFlag3, "baz")
|
||||||
|
|
||||||
|
if !unknownFlag2 {
|
||||||
|
assertErrorf(t, "Flag should have been set by unknown handler, but had value: %v", unknownFlag2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse args with unknown flags that callback doesn't handle, verify it returns error
|
||||||
|
_, err = p.ParseArgs([]string{"--flag1=stuff", "--unknownFlagX", "blah", "--flag2=foo"})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
assertErrorf(t, "Parser should have returned error, but returned nil")
|
||||||
|
}
|
||||||
|
}
|
81
Godeps/_workspace/src/github.com/jessevdk/go-flags/pointer_test.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/jessevdk/go-flags/pointer_test.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPointerBool(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value *bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !*opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointerString(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value *string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v", "value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, *opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointerSlice(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value *[]string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v", "value1", "-v", "value2")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertStringArray(t, *opts.Value, []string{"value1", "value2"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointerMap(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value *map[string]int `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v", "k1:2", "-v", "k2:-5")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if v, ok := (*opts.Value)["k1"]; !ok {
|
||||||
|
t.Errorf("Expected key \"k1\" to exist")
|
||||||
|
} else if v != 2 {
|
||||||
|
t.Errorf("Expected \"k1\" to be 2, but got %#v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := (*opts.Value)["k2"]; !ok {
|
||||||
|
t.Errorf("Expected key \"k2\" to exist")
|
||||||
|
} else if v != -5 {
|
||||||
|
t.Errorf("Expected \"k2\" to be -5, but got %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PointerGroup struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPointerGroup(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Group *PointerGroup `group:"Group Options"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.Group.Value {
|
||||||
|
t.Errorf("Expected Group.Value to be true")
|
||||||
|
}
|
||||||
|
}
|
194
Godeps/_workspace/src/github.com/jessevdk/go-flags/short_test.go
generated
vendored
Normal file
194
Godeps/_workspace/src/github.com/jessevdk/go-flags/short_test.go
generated
vendored
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShort(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.Value {
|
||||||
|
t.Errorf("Expected Value to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortTooLong(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"vv"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrShortNameTooLong, "short names can only be 1 character long, not `vv'", &opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortRequired(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v" required:"true"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrRequired, fmt.Sprintf("the required flag `%cv' was not specified", defaultShortOptDelimiter), &opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiConcat(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
V bool `short:"v"`
|
||||||
|
O bool `short:"o"`
|
||||||
|
F bool `short:"f"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-vo", "-f")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.V {
|
||||||
|
t.Errorf("Expected V to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.O {
|
||||||
|
t.Errorf("Expected O to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.F {
|
||||||
|
t.Errorf("Expected F to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiRequiredConcat(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
V bool `short:"v" required:"true"`
|
||||||
|
O bool `short:"o" required:"true"`
|
||||||
|
F bool `short:"f" required:"true"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-vo", "-f")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
|
||||||
|
if !opts.V {
|
||||||
|
t.Errorf("Expected V to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.O {
|
||||||
|
t.Errorf("Expected O to be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !opts.F {
|
||||||
|
t.Errorf("Expected F to be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiSlice(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Values []bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v", "-v")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertBoolArray(t, opts.Values, []bool{true, true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiSliceConcat(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Values []bool `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-vvv")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertBoolArray(t, opts.Values, []bool{true, true, true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortWithEqualArg(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v=value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortWithArg(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-vvalue")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortArg(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-v", "value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiWithEqualArg(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
F []bool `short:"f"`
|
||||||
|
Value string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrExpectedArgument, fmt.Sprintf("expected argument for flag `%cv'", defaultShortOptDelimiter), &opts, "-ffv=value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiArg(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
F []bool `short:"f"`
|
||||||
|
Value string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-ffv", "value")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertBoolArray(t, opts.F, []bool{true, true})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiArgConcatFail(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
F []bool `short:"f"`
|
||||||
|
Value string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrExpectedArgument, fmt.Sprintf("expected argument for flag `%cv'", defaultShortOptDelimiter), &opts, "-ffvvalue")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortMultiArgConcat(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
F []bool `short:"f"`
|
||||||
|
Value string `short:"v"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-vff")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{})
|
||||||
|
assertString(t, opts.Value, "ff")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortOptional(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
F []bool `short:"f"`
|
||||||
|
Value string `short:"v" optional:"yes" optional-value:"value"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
ret := assertParseSuccess(t, &opts, "-fv", "f")
|
||||||
|
|
||||||
|
assertStringArray(t, ret, []string{"f"})
|
||||||
|
assertString(t, opts.Value, "value")
|
||||||
|
}
|
38
Godeps/_workspace/src/github.com/jessevdk/go-flags/tag_test.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/jessevdk/go-flags/tag_test.go
generated
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTagMissingColon(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrTag, "expected `:' after key name, but got end of tag (in `short`)", &opts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTagMissingValue(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrTag, "expected `\"' to start tag value at end of tag (in `short:`)", &opts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTagMissingQuote(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `short:"v`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrTag, "expected end of tag value `\"' at end of tag (in `short:\"v`)", &opts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTagNewline(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Value bool `long:"verbose" description:"verbose
|
||||||
|
something"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
assertParseFail(t, ErrTag, "unexpected newline in tag value `description' (in `long:\"verbose\" description:\"verbose\nsomething\"`)", &opts, "")
|
||||||
|
}
|
28
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize.go
generated
vendored
Normal file
28
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// +build !windows,!plan9,!solaris
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type winsize struct {
|
||||||
|
row, col uint16
|
||||||
|
xpixel, ypixel uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTerminalColumns() int {
|
||||||
|
ws := winsize{}
|
||||||
|
|
||||||
|
if tIOCGWINSZ != 0 {
|
||||||
|
syscall.Syscall(syscall.SYS_IOCTL,
|
||||||
|
uintptr(0),
|
||||||
|
uintptr(tIOCGWINSZ),
|
||||||
|
uintptr(unsafe.Pointer(&ws)))
|
||||||
|
|
||||||
|
return int(ws.col)
|
||||||
|
}
|
||||||
|
|
||||||
|
return 80
|
||||||
|
}
|
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_linux.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_linux.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
const (
|
||||||
|
tIOCGWINSZ = 0x5413
|
||||||
|
)
|
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_nosysioctl.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_nosysioctl.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build windows plan9 solaris
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
func getTerminalColumns() int {
|
||||||
|
return 80
|
||||||
|
}
|
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_other.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_other.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build !darwin,!freebsd,!netbsd,!openbsd,!linux
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
const (
|
||||||
|
tIOCGWINSZ = 0
|
||||||
|
)
|
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_unix.go
generated
vendored
Normal file
7
Godeps/_workspace/src/github.com/jessevdk/go-flags/termsize_unix.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// +build darwin freebsd netbsd openbsd
|
||||||
|
|
||||||
|
package flags
|
||||||
|
|
||||||
|
const (
|
||||||
|
tIOCGWINSZ = 0x40087468
|
||||||
|
)
|
66
Godeps/_workspace/src/github.com/jessevdk/go-flags/unknown_test.go
generated
vendored
Normal file
66
Godeps/_workspace/src/github.com/jessevdk/go-flags/unknown_test.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnknownFlags(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"-f",
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewParser(&opts, 0)
|
||||||
|
args, err := p.ParseArgs(args)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error for unknown argument")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIgnoreUnknownFlags(t *testing.T) {
|
||||||
|
var opts = struct {
|
||||||
|
Verbose []bool `short:"v" long:"verbose" description:"Verbose output"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"hello",
|
||||||
|
"world",
|
||||||
|
"-v",
|
||||||
|
"--foo=bar",
|
||||||
|
"--verbose",
|
||||||
|
"-f",
|
||||||
|
}
|
||||||
|
|
||||||
|
p := NewParser(&opts, IgnoreUnknown)
|
||||||
|
args, err := p.ParseArgs(args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exargs := []string{
|
||||||
|
"hello",
|
||||||
|
"world",
|
||||||
|
"--foo=bar",
|
||||||
|
"-f",
|
||||||
|
}
|
||||||
|
|
||||||
|
issame := (len(args) == len(exargs))
|
||||||
|
|
||||||
|
if issame {
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
if args[i] != exargs[i] {
|
||||||
|
issame = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !issame {
|
||||||
|
t.Fatalf("Expected %v but got %v", exargs, args)
|
||||||
|
}
|
||||||
|
}
|
23
Godeps/_workspace/src/github.com/juju/errors/.gitignore
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/juju/errors/.gitignore
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
191
Godeps/_workspace/src/github.com/juju/errors/LICENSE
generated
vendored
Normal file
191
Godeps/_workspace/src/github.com/juju/errors/LICENSE
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
All files in this repository are licensed as follows. If you contribute
|
||||||
|
to this repository, it is assumed that you license your contribution
|
||||||
|
under the same license unless you state otherwise.
|
||||||
|
|
||||||
|
All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
|
||||||
|
|
||||||
|
This software is licensed under the LGPLv3, included below.
|
||||||
|
|
||||||
|
As a special exception to the GNU Lesser General Public License version 3
|
||||||
|
("LGPL3"), the copyright holders of this Library give you permission to
|
||||||
|
convey to a third party a Combined Work that links statically or dynamically
|
||||||
|
to this Library without providing any Minimal Corresponding Source or
|
||||||
|
Minimal Application Code as set out in 4d or providing the installation
|
||||||
|
information set out in section 4e, provided that you comply with the other
|
||||||
|
provisions of LGPL3 and provided that you meet, for the Application the
|
||||||
|
terms and conditions of the license(s) which apply to the Application.
|
||||||
|
|
||||||
|
Except as stated in this special exception, the provisions of LGPL3 will
|
||||||
|
continue to comply in full to this Library. If you modify this Library, you
|
||||||
|
may apply this exception to your version of this Library, but you are not
|
||||||
|
obliged to do so. If you do not wish to do so, delete this exception
|
||||||
|
statement from your version. This exception does not (and cannot) modify any
|
||||||
|
license terms which apply to the Application, with which you must still
|
||||||
|
comply.
|
||||||
|
|
||||||
|
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
11
Godeps/_workspace/src/github.com/juju/errors/Makefile
generated
vendored
Normal file
11
Godeps/_workspace/src/github.com/juju/errors/Makefile
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
default: check
|
||||||
|
|
||||||
|
check:
|
||||||
|
go test && go test -compiler gccgo
|
||||||
|
|
||||||
|
docs:
|
||||||
|
godoc2md github.com/juju/errors > README.md
|
||||||
|
sed -i 's|\[godoc-link-here\]|[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)|' README.md
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: default check docs
|
536
Godeps/_workspace/src/github.com/juju/errors/README.md
generated
vendored
Normal file
536
Godeps/_workspace/src/github.com/juju/errors/README.md
generated
vendored
Normal file
@ -0,0 +1,536 @@
|
|||||||
|
|
||||||
|
# errors
|
||||||
|
import "github.com/juju/errors"
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/juju/errors?status.svg)](https://godoc.org/github.com/juju/errors)
|
||||||
|
|
||||||
|
The juju/errors provides an easy way to annotate errors without losing the
|
||||||
|
orginal error context.
|
||||||
|
|
||||||
|
The exported `New` and `Errorf` functions are designed to replace the
|
||||||
|
`errors.New` and `fmt.Errorf` functions respectively. The same underlying
|
||||||
|
error is there, but the package also records the location at which the error
|
||||||
|
was created.
|
||||||
|
|
||||||
|
A primary use case for this library is to add extra context any time an
|
||||||
|
error is returned from a function.
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
This instead becomes:
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Trace(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
which just records the file and line number of the Trace call, or
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Annotate(err, "more context")
|
||||||
|
}
|
||||||
|
|
||||||
|
which also adds an annotation to the error.
|
||||||
|
|
||||||
|
When you want to check to see if an error is of a particular type, a helper
|
||||||
|
function is normally exported by the package that returned the error, like the
|
||||||
|
`os` package does. The underlying cause of the error is available using the
|
||||||
|
`Cause` function.
|
||||||
|
|
||||||
|
|
||||||
|
os.IsNotExist(errors.Cause(err))
|
||||||
|
|
||||||
|
The result of the `Error()` call on an annotated error is the annotations joined
|
||||||
|
with colons, then the result of the `Error()` method for the underlying error
|
||||||
|
that was the cause.
|
||||||
|
|
||||||
|
|
||||||
|
err := errors.Errorf("original")
|
||||||
|
err = errors.Annotatef(err, "context")
|
||||||
|
err = errors.Annotatef(err, "more context")
|
||||||
|
err.Error() -> "more context: context: original"
|
||||||
|
|
||||||
|
Obviously recording the file, line and functions is not very useful if you
|
||||||
|
cannot get them back out again.
|
||||||
|
|
||||||
|
|
||||||
|
errors.ErrorStack(err)
|
||||||
|
|
||||||
|
will return something like:
|
||||||
|
|
||||||
|
|
||||||
|
first error
|
||||||
|
github.com/juju/errors/annotation_test.go:193:
|
||||||
|
github.com/juju/errors/annotation_test.go:194: annotation
|
||||||
|
github.com/juju/errors/annotation_test.go:195:
|
||||||
|
github.com/juju/errors/annotation_test.go:196: more context
|
||||||
|
github.com/juju/errors/annotation_test.go:197:
|
||||||
|
|
||||||
|
The first error was generated by an external system, so there was no location
|
||||||
|
associated. The second, fourth, and last lines were generated with Trace calls,
|
||||||
|
and the other two through Annotate.
|
||||||
|
|
||||||
|
Sometimes when responding to an error you want to return a more specific error
|
||||||
|
for the situation.
|
||||||
|
|
||||||
|
|
||||||
|
if err := FindField(field); err != nil {
|
||||||
|
return errors.Wrap(err, errors.NotFoundf(field))
|
||||||
|
}
|
||||||
|
|
||||||
|
This returns an error where the complete error stack is still available, and
|
||||||
|
`errors.Cause()` will return the `NotFound` error.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## func AlreadyExistsf
|
||||||
|
``` go
|
||||||
|
func AlreadyExistsf(format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
AlreadyExistsf returns an error which satisfies IsAlreadyExists().
|
||||||
|
|
||||||
|
|
||||||
|
## func Annotate
|
||||||
|
``` go
|
||||||
|
func Annotate(other error, message string) error
|
||||||
|
```
|
||||||
|
Annotate is used to add extra context to an existing error. The location of
|
||||||
|
the Annotate call is recorded with the annotations. The file, line and
|
||||||
|
function are also recorded.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Annotate(err, "failed to frombulate")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## func Annotatef
|
||||||
|
``` go
|
||||||
|
func Annotatef(other error, format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
Annotatef is used to add extra context to an existing error. The location of
|
||||||
|
the Annotate call is recorded with the annotations. The file, line and
|
||||||
|
function are also recorded.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Annotatef(err, "failed to frombulate the %s", arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## func Cause
|
||||||
|
``` go
|
||||||
|
func Cause(err error) error
|
||||||
|
```
|
||||||
|
Cause returns the cause of the given error. This will be either the
|
||||||
|
original error, or the result of a Wrap or Mask call.
|
||||||
|
|
||||||
|
Cause is the usual way to diagnose errors that may have been wrapped by
|
||||||
|
the other errors functions.
|
||||||
|
|
||||||
|
|
||||||
|
## func DeferredAnnotatef
|
||||||
|
``` go
|
||||||
|
func DeferredAnnotatef(err *error, format string, args ...interface{})
|
||||||
|
```
|
||||||
|
DeferredAnnotatef annotates the given error (when it is not nil) with the given
|
||||||
|
format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef
|
||||||
|
does nothing. This method is used in a defer statement in order to annotate any
|
||||||
|
resulting error with the same message.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg)
|
||||||
|
|
||||||
|
|
||||||
|
## func Details
|
||||||
|
``` go
|
||||||
|
func Details(err error) string
|
||||||
|
```
|
||||||
|
Details returns information about the stack of errors wrapped by err, in
|
||||||
|
the format:
|
||||||
|
|
||||||
|
|
||||||
|
[{filename:99: error one} {otherfile:55: cause of error one}]
|
||||||
|
|
||||||
|
This is a terse alternative to ErrorStack as it returns a single line.
|
||||||
|
|
||||||
|
|
||||||
|
## func ErrorStack
|
||||||
|
``` go
|
||||||
|
func ErrorStack(err error) string
|
||||||
|
```
|
||||||
|
ErrorStack returns a string representation of the annotated error. If the
|
||||||
|
error passed as the parameter is not an annotated error, the result is
|
||||||
|
simply the result of the Error() method on that error.
|
||||||
|
|
||||||
|
If the error is an annotated error, a multi-line string is returned where
|
||||||
|
each line represents one entry in the annotation stack. The full filename
|
||||||
|
from the call stack is used in the output.
|
||||||
|
|
||||||
|
|
||||||
|
first error
|
||||||
|
github.com/juju/errors/annotation_test.go:193:
|
||||||
|
github.com/juju/errors/annotation_test.go:194: annotation
|
||||||
|
github.com/juju/errors/annotation_test.go:195:
|
||||||
|
github.com/juju/errors/annotation_test.go:196: more context
|
||||||
|
github.com/juju/errors/annotation_test.go:197:
|
||||||
|
|
||||||
|
|
||||||
|
## func Errorf
|
||||||
|
``` go
|
||||||
|
func Errorf(format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
Errorf creates a new annotated error and records the location that the
|
||||||
|
error is created. This should be a drop in replacement for fmt.Errorf.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
return errors.Errorf("validation failed: %s", message)
|
||||||
|
|
||||||
|
|
||||||
|
## func IsAlreadyExists
|
||||||
|
``` go
|
||||||
|
func IsAlreadyExists(err error) bool
|
||||||
|
```
|
||||||
|
IsAlreadyExists reports whether the error was created with
|
||||||
|
AlreadyExistsf() or NewAlreadyExists().
|
||||||
|
|
||||||
|
|
||||||
|
## func IsNotFound
|
||||||
|
``` go
|
||||||
|
func IsNotFound(err error) bool
|
||||||
|
```
|
||||||
|
IsNotFound reports whether err was created with NotFoundf() or
|
||||||
|
NewNotFound().
|
||||||
|
|
||||||
|
|
||||||
|
## func IsNotImplemented
|
||||||
|
``` go
|
||||||
|
func IsNotImplemented(err error) bool
|
||||||
|
```
|
||||||
|
IsNotImplemented reports whether err was created with
|
||||||
|
NotImplementedf() or NewNotImplemented().
|
||||||
|
|
||||||
|
|
||||||
|
## func IsNotSupported
|
||||||
|
``` go
|
||||||
|
func IsNotSupported(err error) bool
|
||||||
|
```
|
||||||
|
IsNotSupported reports whether the error was created with
|
||||||
|
NotSupportedf() or NewNotSupported().
|
||||||
|
|
||||||
|
|
||||||
|
## func IsNotValid
|
||||||
|
``` go
|
||||||
|
func IsNotValid(err error) bool
|
||||||
|
```
|
||||||
|
IsNotValid reports whether the error was created with NotValidf() or
|
||||||
|
NewNotValid().
|
||||||
|
|
||||||
|
|
||||||
|
## func IsUnauthorized
|
||||||
|
``` go
|
||||||
|
func IsUnauthorized(err error) bool
|
||||||
|
```
|
||||||
|
IsUnauthorized reports whether err was created with Unauthorizedf() or
|
||||||
|
NewUnauthorized().
|
||||||
|
|
||||||
|
|
||||||
|
## func Mask
|
||||||
|
``` go
|
||||||
|
func Mask(other error) error
|
||||||
|
```
|
||||||
|
Mask hides the underlying error type, and records the location of the masking.
|
||||||
|
|
||||||
|
|
||||||
|
## func Maskf
|
||||||
|
``` go
|
||||||
|
func Maskf(other error, format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
Mask masks the given error with the given format string and arguments (like
|
||||||
|
fmt.Sprintf), returning a new error that maintains the error stack, but
|
||||||
|
hides the underlying error type. The error string still contains the full
|
||||||
|
annotations. If you want to hide the annotations, call Wrap.
|
||||||
|
|
||||||
|
|
||||||
|
## func New
|
||||||
|
``` go
|
||||||
|
func New(message string) error
|
||||||
|
```
|
||||||
|
New is a drop in replacement for the standard libary errors module that records
|
||||||
|
the location that the error is created.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
return errors.New("validation failed")
|
||||||
|
|
||||||
|
|
||||||
|
## func NewAlreadyExists
|
||||||
|
``` go
|
||||||
|
func NewAlreadyExists(err error, msg string) error
|
||||||
|
```
|
||||||
|
NewAlreadyExists returns an error which wraps err and satisfies
|
||||||
|
IsAlreadyExists().
|
||||||
|
|
||||||
|
|
||||||
|
## func NewNotFound
|
||||||
|
``` go
|
||||||
|
func NewNotFound(err error, msg string) error
|
||||||
|
```
|
||||||
|
NewNotFound returns an error which wraps err that satisfies
|
||||||
|
IsNotFound().
|
||||||
|
|
||||||
|
|
||||||
|
## func NewNotImplemented
|
||||||
|
``` go
|
||||||
|
func NewNotImplemented(err error, msg string) error
|
||||||
|
```
|
||||||
|
NewNotImplemented returns an error which wraps err and satisfies
|
||||||
|
IsNotImplemented().
|
||||||
|
|
||||||
|
|
||||||
|
## func NewNotSupported
|
||||||
|
``` go
|
||||||
|
func NewNotSupported(err error, msg string) error
|
||||||
|
```
|
||||||
|
NewNotSupported returns an error which wraps err and satisfies
|
||||||
|
IsNotSupported().
|
||||||
|
|
||||||
|
|
||||||
|
## func NewNotValid
|
||||||
|
``` go
|
||||||
|
func NewNotValid(err error, msg string) error
|
||||||
|
```
|
||||||
|
NewNotValid returns an error which wraps err and satisfies IsNotValid().
|
||||||
|
|
||||||
|
|
||||||
|
## func NewUnauthorized
|
||||||
|
``` go
|
||||||
|
func NewUnauthorized(err error, msg string) error
|
||||||
|
```
|
||||||
|
NewUnauthorized returns an error which wraps err and satisfies
|
||||||
|
IsUnauthorized().
|
||||||
|
|
||||||
|
|
||||||
|
## func NotFoundf
|
||||||
|
``` go
|
||||||
|
func NotFoundf(format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
NotFoundf returns an error which satisfies IsNotFound().
|
||||||
|
|
||||||
|
|
||||||
|
## func NotImplementedf
|
||||||
|
``` go
|
||||||
|
func NotImplementedf(format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
NotImplementedf returns an error which satisfies IsNotImplemented().
|
||||||
|
|
||||||
|
|
||||||
|
## func NotSupportedf
|
||||||
|
``` go
|
||||||
|
func NotSupportedf(format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
NotSupportedf returns an error which satisfies IsNotSupported().
|
||||||
|
|
||||||
|
|
||||||
|
## func NotValidf
|
||||||
|
``` go
|
||||||
|
func NotValidf(format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
NotValidf returns an error which satisfies IsNotValid().
|
||||||
|
|
||||||
|
|
||||||
|
## func Trace
|
||||||
|
``` go
|
||||||
|
func Trace(other error) error
|
||||||
|
```
|
||||||
|
Trace adds the location of the Trace call to the stack. The Cause of the
|
||||||
|
resulting error is the same as the error parameter. If the other error is
|
||||||
|
nil, the result will be nil.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Trace(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## func Unauthorizedf
|
||||||
|
``` go
|
||||||
|
func Unauthorizedf(format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
Unauthorizedf returns an error which satisfies IsUnauthorized().
|
||||||
|
|
||||||
|
|
||||||
|
## func Wrap
|
||||||
|
``` go
|
||||||
|
func Wrap(other, newDescriptive error) error
|
||||||
|
```
|
||||||
|
Wrap changes the Cause of the error. The location of the Wrap call is also
|
||||||
|
stored in the error stack.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
newErr := &packageError{"more context", private_value}
|
||||||
|
return errors.Wrap(err, newErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## func Wrapf
|
||||||
|
``` go
|
||||||
|
func Wrapf(other, newDescriptive error, format string, args ...interface{}) error
|
||||||
|
```
|
||||||
|
Wrapf changes the Cause of the error, and adds an annotation. The location
|
||||||
|
of the Wrap call is also stored in the error stack.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Wrapf(err, simpleErrorType, "invalid value %q", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## type Err
|
||||||
|
``` go
|
||||||
|
type Err struct {
|
||||||
|
// contains filtered or unexported fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Err holds a description of an error along with information about
|
||||||
|
where the error was created.
|
||||||
|
|
||||||
|
It may be embedded in custom error types to add extra information that
|
||||||
|
this errors package can understand.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func NewErr
|
||||||
|
``` go
|
||||||
|
func NewErr(format string, args ...interface{}) Err
|
||||||
|
```
|
||||||
|
NewErr is used to return an Err for the purpose of embedding in other
|
||||||
|
structures. The location is not specified, and needs to be set with a call
|
||||||
|
to SetLocation.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
|
||||||
|
type FooError struct {
|
||||||
|
errors.Err
|
||||||
|
code int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFooError(code int) error {
|
||||||
|
err := &FooError{errors.NewErr("foo"), code}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Err) Cause
|
||||||
|
``` go
|
||||||
|
func (e *Err) Cause() error
|
||||||
|
```
|
||||||
|
The Cause of an error is the most recent error in the error stack that
|
||||||
|
meets one of these criteria: the original error that was raised; the new
|
||||||
|
error that was passed into the Wrap function; the most recently masked
|
||||||
|
error; or nil if the error itself is considered the Cause. Normally this
|
||||||
|
method is not invoked directly, but instead through the Cause stand alone
|
||||||
|
function.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Err) Error
|
||||||
|
``` go
|
||||||
|
func (e *Err) Error() string
|
||||||
|
```
|
||||||
|
Error implements error.Error.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Err) Location
|
||||||
|
``` go
|
||||||
|
func (e *Err) Location() (filename string, line int)
|
||||||
|
```
|
||||||
|
Location is the file and line of where the error was most recently
|
||||||
|
created or annotated.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Err) Message
|
||||||
|
``` go
|
||||||
|
func (e *Err) Message() string
|
||||||
|
```
|
||||||
|
Message returns the message stored with the most recent location. This is
|
||||||
|
the empty string if the most recent call was Trace, or the message stored
|
||||||
|
with Annotate or Mask.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Err) SetLocation
|
||||||
|
``` go
|
||||||
|
func (e *Err) SetLocation(callDepth int)
|
||||||
|
```
|
||||||
|
SetLocation records the source location of the error at callDepth stack
|
||||||
|
frames above the call.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Err) StackTrace
|
||||||
|
``` go
|
||||||
|
func (e *Err) StackTrace() []string
|
||||||
|
```
|
||||||
|
StackTrace returns one string for each location recorded in the stack of
|
||||||
|
errors. The first value is the originating error, with a line for each
|
||||||
|
other annotation or tracing of the error.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### func (\*Err) Underlying
|
||||||
|
``` go
|
||||||
|
func (e *Err) Underlying() error
|
||||||
|
```
|
||||||
|
Underlying returns the previous error in the error stack, if any. A client
|
||||||
|
should not ever really call this method. It is used to build the error
|
||||||
|
stack and should not be introspected by client calls. Or more
|
||||||
|
specifically, clients should not depend on anything but the `Cause` of an
|
||||||
|
error.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- - -
|
||||||
|
Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)
|
81
Godeps/_workspace/src/github.com/juju/errors/doc.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/juju/errors/doc.go
generated
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2013, 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
/*
|
||||||
|
[godoc-link-here]
|
||||||
|
|
||||||
|
The juju/errors provides an easy way to annotate errors without losing the
|
||||||
|
orginal error context.
|
||||||
|
|
||||||
|
The exported `New` and `Errorf` functions are designed to replace the
|
||||||
|
`errors.New` and `fmt.Errorf` functions respectively. The same underlying
|
||||||
|
error is there, but the package also records the location at which the error
|
||||||
|
was created.
|
||||||
|
|
||||||
|
A primary use case for this library is to add extra context any time an
|
||||||
|
error is returned from a function.
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
This instead becomes:
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Trace(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
which just records the file and line number of the Trace call, or
|
||||||
|
|
||||||
|
if err := SomeFunc(); err != nil {
|
||||||
|
return errors.Annotate(err, "more context")
|
||||||
|
}
|
||||||
|
|
||||||
|
which also adds an annotation to the error.
|
||||||
|
|
||||||
|
When you want to check to see if an error is of a particular type, a helper
|
||||||
|
function is normally exported by the package that returned the error, like the
|
||||||
|
`os` package does. The underlying cause of the error is available using the
|
||||||
|
`Cause` function.
|
||||||
|
|
||||||
|
os.IsNotExist(errors.Cause(err))
|
||||||
|
|
||||||
|
The result of the `Error()` call on an annotated error is the annotations joined
|
||||||
|
with colons, then the result of the `Error()` method for the underlying error
|
||||||
|
that was the cause.
|
||||||
|
|
||||||
|
err := errors.Errorf("original")
|
||||||
|
err = errors.Annotatef(err, "context")
|
||||||
|
err = errors.Annotatef(err, "more context")
|
||||||
|
err.Error() -> "more context: context: original"
|
||||||
|
|
||||||
|
Obviously recording the file, line and functions is not very useful if you
|
||||||
|
cannot get them back out again.
|
||||||
|
|
||||||
|
errors.ErrorStack(err)
|
||||||
|
|
||||||
|
will return something like:
|
||||||
|
|
||||||
|
first error
|
||||||
|
github.com/juju/errors/annotation_test.go:193:
|
||||||
|
github.com/juju/errors/annotation_test.go:194: annotation
|
||||||
|
github.com/juju/errors/annotation_test.go:195:
|
||||||
|
github.com/juju/errors/annotation_test.go:196: more context
|
||||||
|
github.com/juju/errors/annotation_test.go:197:
|
||||||
|
|
||||||
|
The first error was generated by an external system, so there was no location
|
||||||
|
associated. The second, fourth, and last lines were generated with Trace calls,
|
||||||
|
and the other two through Annotate.
|
||||||
|
|
||||||
|
Sometimes when responding to an error you want to return a more specific error
|
||||||
|
for the situation.
|
||||||
|
|
||||||
|
if err := FindField(field); err != nil {
|
||||||
|
return errors.Wrap(err, errors.NotFoundf(field))
|
||||||
|
}
|
||||||
|
|
||||||
|
This returns an error where the complete error stack is still available, and
|
||||||
|
`errors.Cause()` will return the `NotFound` error.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package errors
|
122
Godeps/_workspace/src/github.com/juju/errors/error.go
generated
vendored
Normal file
122
Godeps/_workspace/src/github.com/juju/errors/error.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Err holds a description of an error along with information about
|
||||||
|
// where the error was created.
|
||||||
|
//
|
||||||
|
// It may be embedded in custom error types to add extra information that
|
||||||
|
// this errors package can understand.
|
||||||
|
type Err struct {
|
||||||
|
// message holds an annotation of the error.
|
||||||
|
message string
|
||||||
|
|
||||||
|
// cause holds the cause of the error as returned
|
||||||
|
// by the Cause method.
|
||||||
|
cause error
|
||||||
|
|
||||||
|
// previous holds the previous error in the error stack, if any.
|
||||||
|
previous error
|
||||||
|
|
||||||
|
// file and line hold the source code location where the error was
|
||||||
|
// created.
|
||||||
|
file string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErr is used to return an Err for the purpose of embedding in other
|
||||||
|
// structures. The location is not specified, and needs to be set with a call
|
||||||
|
// to SetLocation.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// type FooError struct {
|
||||||
|
// errors.Err
|
||||||
|
// code int
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func NewFooError(code int) error {
|
||||||
|
// err := &FooError{errors.NewErr("foo"), code}
|
||||||
|
// err.SetLocation(1)
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
func NewErr(format string, args ...interface{}) Err {
|
||||||
|
return Err{
|
||||||
|
message: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location is the file and line of where the error was most recently
|
||||||
|
// created or annotated.
|
||||||
|
func (e *Err) Location() (filename string, line int) {
|
||||||
|
return e.file, e.line
|
||||||
|
}
|
||||||
|
|
||||||
|
// Underlying returns the previous error in the error stack, if any. A client
|
||||||
|
// should not ever really call this method. It is used to build the error
|
||||||
|
// stack and should not be introspected by client calls. Or more
|
||||||
|
// specifically, clients should not depend on anything but the `Cause` of an
|
||||||
|
// error.
|
||||||
|
func (e *Err) Underlying() error {
|
||||||
|
return e.previous
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Cause of an error is the most recent error in the error stack that
|
||||||
|
// meets one of these criteria: the original error that was raised; the new
|
||||||
|
// error that was passed into the Wrap function; the most recently masked
|
||||||
|
// error; or nil if the error itself is considered the Cause. Normally this
|
||||||
|
// method is not invoked directly, but instead through the Cause stand alone
|
||||||
|
// function.
|
||||||
|
func (e *Err) Cause() error {
|
||||||
|
return e.cause
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message returns the message stored with the most recent location. This is
|
||||||
|
// the empty string if the most recent call was Trace, or the message stored
|
||||||
|
// with Annotate or Mask.
|
||||||
|
func (e *Err) Message() string {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements error.Error.
|
||||||
|
func (e *Err) Error() string {
|
||||||
|
// We want to walk up the stack of errors showing the annotations
|
||||||
|
// as long as the cause is the same.
|
||||||
|
err := e.previous
|
||||||
|
if !sameError(Cause(err), e.cause) && e.cause != nil {
|
||||||
|
err = e.cause
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return e.message
|
||||||
|
case e.message == "":
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s: %v", e.message, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLocation records the source location of the error at callDepth stack
|
||||||
|
// frames above the call.
|
||||||
|
func (e *Err) SetLocation(callDepth int) {
|
||||||
|
_, file, line, _ := runtime.Caller(callDepth + 1)
|
||||||
|
e.file = trimGoPath(file)
|
||||||
|
e.line = line
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackTrace returns one string for each location recorded in the stack of
|
||||||
|
// errors. The first value is the originating error, with a line for each
|
||||||
|
// other annotation or tracing of the error.
|
||||||
|
func (e *Err) StackTrace() []string {
|
||||||
|
return errorStack(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ideally we'd have a way to check identity, but deep equals will do.
|
||||||
|
func sameError(e1, e2 error) bool {
|
||||||
|
return reflect.DeepEqual(e1, e2)
|
||||||
|
}
|
161
Godeps/_workspace/src/github.com/juju/errors/error_test.go
generated
vendored
Normal file
161
Godeps/_workspace/src/github.com/juju/errors/error_test.go
generated
vendored
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
jc "github.com/juju/testing/checkers"
|
||||||
|
gc "gopkg.in/check.v1"
|
||||||
|
|
||||||
|
"github.com/juju/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type errorsSuite struct{}
|
||||||
|
|
||||||
|
var _ = gc.Suite(&errorsSuite{})
|
||||||
|
|
||||||
|
var someErr = errors.New("some error") //err varSomeErr
|
||||||
|
|
||||||
|
func (*errorsSuite) TestErrorString(c *gc.C) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
message string
|
||||||
|
generator func() error
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
message: "uncomparable errors",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.Annotatef(newNonComparableError("uncomparable"), "annotation")
|
||||||
|
return errors.Annotatef(err, "another")
|
||||||
|
},
|
||||||
|
expected: "another: annotation: uncomparable",
|
||||||
|
}, {
|
||||||
|
message: "Errorf",
|
||||||
|
generator: func() error {
|
||||||
|
return errors.Errorf("first error")
|
||||||
|
},
|
||||||
|
expected: "first error",
|
||||||
|
}, {
|
||||||
|
message: "annotated error",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.Errorf("first error")
|
||||||
|
return errors.Annotatef(err, "annotation")
|
||||||
|
},
|
||||||
|
expected: "annotation: first error",
|
||||||
|
}, {
|
||||||
|
message: "test annotation format",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.Errorf("first %s", "error")
|
||||||
|
return errors.Annotatef(err, "%s", "annotation")
|
||||||
|
},
|
||||||
|
expected: "annotation: first error",
|
||||||
|
}, {
|
||||||
|
message: "wrapped error",
|
||||||
|
generator: func() error {
|
||||||
|
err := newError("first error")
|
||||||
|
return errors.Wrap(err, newError("detailed error"))
|
||||||
|
},
|
||||||
|
expected: "detailed error",
|
||||||
|
}, {
|
||||||
|
message: "wrapped annotated error",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.Errorf("first error")
|
||||||
|
err = errors.Annotatef(err, "annotated")
|
||||||
|
return errors.Wrap(err, fmt.Errorf("detailed error"))
|
||||||
|
},
|
||||||
|
expected: "detailed error",
|
||||||
|
}, {
|
||||||
|
message: "annotated wrapped error",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.Errorf("first error")
|
||||||
|
err = errors.Wrap(err, fmt.Errorf("detailed error"))
|
||||||
|
return errors.Annotatef(err, "annotated")
|
||||||
|
},
|
||||||
|
expected: "annotated: detailed error",
|
||||||
|
}, {
|
||||||
|
message: "traced, and annotated",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.New("first error")
|
||||||
|
err = errors.Trace(err)
|
||||||
|
err = errors.Annotate(err, "some context")
|
||||||
|
err = errors.Trace(err)
|
||||||
|
err = errors.Annotate(err, "more context")
|
||||||
|
return errors.Trace(err)
|
||||||
|
},
|
||||||
|
expected: "more context: some context: first error",
|
||||||
|
}, {
|
||||||
|
message: "traced, and annotated, masked and annotated",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.New("first error")
|
||||||
|
err = errors.Trace(err)
|
||||||
|
err = errors.Annotate(err, "some context")
|
||||||
|
err = errors.Maskf(err, "masked")
|
||||||
|
err = errors.Annotate(err, "more context")
|
||||||
|
return errors.Trace(err)
|
||||||
|
},
|
||||||
|
expected: "more context: masked: some context: first error",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
c.Logf("%v: %s", i, test.message)
|
||||||
|
err := test.generator()
|
||||||
|
ok := c.Check(err.Error(), gc.Equals, test.expected)
|
||||||
|
if !ok {
|
||||||
|
c.Logf("%#v", test.generator())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type embed struct {
|
||||||
|
errors.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEmbed(format string, args ...interface{}) *embed {
|
||||||
|
err := &embed{errors.NewErr(format, args...)}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*errorsSuite) TestNewErr(c *gc.C) {
|
||||||
|
if runtime.Compiler == "gccgo" {
|
||||||
|
c.Skip("gccgo can't determine the location")
|
||||||
|
}
|
||||||
|
err := newEmbed("testing %d", 42) //err embedErr
|
||||||
|
c.Assert(err.Error(), gc.Equals, "testing 42")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["embedErr"].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ error = (*embed)(nil)
|
||||||
|
|
||||||
|
// This is an uncomparable error type, as it is a struct that supports the
|
||||||
|
// error interface (as opposed to a pointer type).
|
||||||
|
type error_ struct {
|
||||||
|
info string
|
||||||
|
slice []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a non-comparable error
|
||||||
|
func newNonComparableError(message string) error {
|
||||||
|
return error_{info: message}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e error_) Error() string {
|
||||||
|
return e.info
|
||||||
|
}
|
||||||
|
|
||||||
|
func newError(message string) error {
|
||||||
|
return testError{message}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The testError is a value type error for ease of seeing results
|
||||||
|
// when the test fails.
|
||||||
|
type testError struct {
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e testError) Error() string {
|
||||||
|
return e.message
|
||||||
|
}
|
235
Godeps/_workspace/src/github.com/juju/errors/errortypes.go
generated
vendored
Normal file
235
Godeps/_workspace/src/github.com/juju/errors/errortypes.go
generated
vendored
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// wrap is a helper to construct an *wrapper.
|
||||||
|
func wrap(err error, format, suffix string, args ...interface{}) Err {
|
||||||
|
newErr := Err{
|
||||||
|
message: fmt.Sprintf(format+suffix, args...),
|
||||||
|
previous: err,
|
||||||
|
}
|
||||||
|
newErr.SetLocation(2)
|
||||||
|
return newErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// notFound represents an error when something has not been found.
|
||||||
|
type notFound struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotFoundf returns an error which satisfies IsNotFound().
|
||||||
|
func NotFoundf(format string, args ...interface{}) error {
|
||||||
|
return ¬Found{wrap(nil, format, " not found", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotFound returns an error which wraps err that satisfies
|
||||||
|
// IsNotFound().
|
||||||
|
func NewNotFound(err error, msg string) error {
|
||||||
|
return ¬Found{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotFound reports whether err was created with NotFoundf() or
|
||||||
|
// NewNotFound().
|
||||||
|
func IsNotFound(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*notFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// userNotFound represents an error when an inexistent user is looked up.
|
||||||
|
type userNotFound struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserNotFoundf returns an error which satisfies IsUserNotFound().
|
||||||
|
func UserNotFoundf(format string, args ...interface{}) error {
|
||||||
|
return &userNotFound{wrap(nil, format, " user not found", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUserNotFound returns an error which wraps err and satisfies
|
||||||
|
// IsUserNotFound().
|
||||||
|
func NewUserNotFound(err error, msg string) error {
|
||||||
|
return &userNotFound{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUserNotFound reports whether err was created with UserNotFoundf() or
|
||||||
|
// NewUserNotFound().
|
||||||
|
func IsUserNotFound(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*userNotFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// unauthorized represents an error when an operation is unauthorized.
|
||||||
|
type unauthorized struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unauthorizedf returns an error which satisfies IsUnauthorized().
|
||||||
|
func Unauthorizedf(format string, args ...interface{}) error {
|
||||||
|
return &unauthorized{wrap(nil, format, "", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnauthorized returns an error which wraps err and satisfies
|
||||||
|
// IsUnauthorized().
|
||||||
|
func NewUnauthorized(err error, msg string) error {
|
||||||
|
return &unauthorized{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsUnauthorized reports whether err was created with Unauthorizedf() or
|
||||||
|
// NewUnauthorized().
|
||||||
|
func IsUnauthorized(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*unauthorized)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// notImplemented represents an error when something is not
|
||||||
|
// implemented.
|
||||||
|
type notImplemented struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotImplementedf returns an error which satisfies IsNotImplemented().
|
||||||
|
func NotImplementedf(format string, args ...interface{}) error {
|
||||||
|
return ¬Implemented{wrap(nil, format, " not implemented", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotImplemented returns an error which wraps err and satisfies
|
||||||
|
// IsNotImplemented().
|
||||||
|
func NewNotImplemented(err error, msg string) error {
|
||||||
|
return ¬Implemented{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotImplemented reports whether err was created with
|
||||||
|
// NotImplementedf() or NewNotImplemented().
|
||||||
|
func IsNotImplemented(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*notImplemented)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// alreadyExists represents and error when something already exists.
|
||||||
|
type alreadyExists struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AlreadyExistsf returns an error which satisfies IsAlreadyExists().
|
||||||
|
func AlreadyExistsf(format string, args ...interface{}) error {
|
||||||
|
return &alreadyExists{wrap(nil, format, " already exists", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAlreadyExists returns an error which wraps err and satisfies
|
||||||
|
// IsAlreadyExists().
|
||||||
|
func NewAlreadyExists(err error, msg string) error {
|
||||||
|
return &alreadyExists{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAlreadyExists reports whether the error was created with
|
||||||
|
// AlreadyExistsf() or NewAlreadyExists().
|
||||||
|
func IsAlreadyExists(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*alreadyExists)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// notSupported represents an error when something is not supported.
|
||||||
|
type notSupported struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotSupportedf returns an error which satisfies IsNotSupported().
|
||||||
|
func NotSupportedf(format string, args ...interface{}) error {
|
||||||
|
return ¬Supported{wrap(nil, format, " not supported", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotSupported returns an error which wraps err and satisfies
|
||||||
|
// IsNotSupported().
|
||||||
|
func NewNotSupported(err error, msg string) error {
|
||||||
|
return ¬Supported{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotSupported reports whether the error was created with
|
||||||
|
// NotSupportedf() or NewNotSupported().
|
||||||
|
func IsNotSupported(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*notSupported)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// notValid represents an error when something is not valid.
|
||||||
|
type notValid struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotValidf returns an error which satisfies IsNotValid().
|
||||||
|
func NotValidf(format string, args ...interface{}) error {
|
||||||
|
return ¬Valid{wrap(nil, format, " not valid", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotValid returns an error which wraps err and satisfies IsNotValid().
|
||||||
|
func NewNotValid(err error, msg string) error {
|
||||||
|
return ¬Valid{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotValid reports whether the error was created with NotValidf() or
|
||||||
|
// NewNotValid().
|
||||||
|
func IsNotValid(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*notValid)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// notProvisioned represents an error when something is not yet provisioned.
|
||||||
|
type notProvisioned struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotProvisionedf returns an error which satisfies IsNotProvisioned().
|
||||||
|
func NotProvisionedf(format string, args ...interface{}) error {
|
||||||
|
return ¬Provisioned{wrap(nil, format, " not provisioned", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotProvisioned returns an error which wraps err that satisfies
|
||||||
|
// IsNotProvisioned().
|
||||||
|
func NewNotProvisioned(err error, msg string) error {
|
||||||
|
return ¬Provisioned{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotProvisioned reports whether err was created with NotProvisionedf() or
|
||||||
|
// NewNotProvisioned().
|
||||||
|
func IsNotProvisioned(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*notProvisioned)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// notAssigned represents an error when something is not yet assigned to
|
||||||
|
// something else.
|
||||||
|
type notAssigned struct {
|
||||||
|
Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotAssignedf returns an error which satisfies IsNotAssigned().
|
||||||
|
func NotAssignedf(format string, args ...interface{}) error {
|
||||||
|
return ¬Assigned{wrap(nil, format, " not assigned", args...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNotAssigned returns an error which wraps err that satisfies
|
||||||
|
// IsNotAssigned().
|
||||||
|
func NewNotAssigned(err error, msg string) error {
|
||||||
|
return ¬Assigned{wrap(err, msg, "")}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNotAssigned reports whether err was created with NotAssignedf() or
|
||||||
|
// NewNotAssigned().
|
||||||
|
func IsNotAssigned(err error) bool {
|
||||||
|
err = Cause(err)
|
||||||
|
_, ok := err.(*notAssigned)
|
||||||
|
return ok
|
||||||
|
}
|
171
Godeps/_workspace/src/github.com/juju/errors/errortypes_test.go
generated
vendored
Normal file
171
Godeps/_workspace/src/github.com/juju/errors/errortypes_test.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Copyright 2013, 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
stderrors "errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/juju/errors"
|
||||||
|
jc "github.com/juju/testing/checkers"
|
||||||
|
gc "gopkg.in/check.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errorInfo holds information about a single error type: a satisfier
|
||||||
|
// function, wrapping and variable arguments constructors and message
|
||||||
|
// suffix.
|
||||||
|
type errorInfo struct {
|
||||||
|
satisfier func(error) bool
|
||||||
|
argsConstructor func(string, ...interface{}) error
|
||||||
|
wrapConstructor func(error, string) error
|
||||||
|
suffix string
|
||||||
|
}
|
||||||
|
|
||||||
|
// allErrors holds information for all defined errors. When adding new
|
||||||
|
// errors, add them here as well to include them in tests.
|
||||||
|
var allErrors = []*errorInfo{
|
||||||
|
&errorInfo{errors.IsNotFound, errors.NotFoundf, errors.NewNotFound, " not found"},
|
||||||
|
&errorInfo{errors.IsUserNotFound, errors.UserNotFoundf, errors.NewUserNotFound, " user not found"},
|
||||||
|
&errorInfo{errors.IsUnauthorized, errors.Unauthorizedf, errors.NewUnauthorized, ""},
|
||||||
|
&errorInfo{errors.IsNotImplemented, errors.NotImplementedf, errors.NewNotImplemented, " not implemented"},
|
||||||
|
&errorInfo{errors.IsAlreadyExists, errors.AlreadyExistsf, errors.NewAlreadyExists, " already exists"},
|
||||||
|
&errorInfo{errors.IsNotSupported, errors.NotSupportedf, errors.NewNotSupported, " not supported"},
|
||||||
|
&errorInfo{errors.IsNotValid, errors.NotValidf, errors.NewNotValid, " not valid"},
|
||||||
|
&errorInfo{errors.IsNotProvisioned, errors.NotProvisionedf, errors.NewNotProvisioned, " not provisioned"},
|
||||||
|
&errorInfo{errors.IsNotAssigned, errors.NotAssignedf, errors.NewNotAssigned, " not assigned"},
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorTypeSuite struct{}
|
||||||
|
|
||||||
|
var _ = gc.Suite(&errorTypeSuite{})
|
||||||
|
|
||||||
|
func (t *errorInfo) satisfierName() string {
|
||||||
|
value := reflect.ValueOf(t.satisfier)
|
||||||
|
f := runtime.FuncForPC(value.Pointer())
|
||||||
|
return f.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *errorInfo) equal(t0 *errorInfo) bool {
|
||||||
|
if t0 == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.satisfierName() == t0.satisfierName()
|
||||||
|
}
|
||||||
|
|
||||||
|
type errorTest struct {
|
||||||
|
err error
|
||||||
|
message string
|
||||||
|
errInfo *errorInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func deferredAnnotatef(err error, format string, args ...interface{}) error {
|
||||||
|
errors.DeferredAnnotatef(&err, format, args...)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustSatisfy(c *gc.C, err error, errInfo *errorInfo) {
|
||||||
|
if errInfo != nil {
|
||||||
|
msg := fmt.Sprintf("%#v must satisfy %v", err, errInfo.satisfierName())
|
||||||
|
c.Check(err, jc.Satisfies, errInfo.satisfier, gc.Commentf(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustNotSatisfy(c *gc.C, err error, errInfo *errorInfo) {
|
||||||
|
if errInfo != nil {
|
||||||
|
msg := fmt.Sprintf("%#v must not satisfy %v", err, errInfo.satisfierName())
|
||||||
|
c.Check(err, gc.Not(jc.Satisfies), errInfo.satisfier, gc.Commentf(msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErrorMatches(c *gc.C, err error, message string, errInfo *errorInfo) {
|
||||||
|
if message == "<nil>" {
|
||||||
|
c.Check(err, gc.IsNil)
|
||||||
|
c.Check(errInfo, gc.IsNil)
|
||||||
|
} else {
|
||||||
|
c.Check(err, gc.ErrorMatches, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runErrorTests(c *gc.C, errorTests []errorTest, checkMustSatisfy bool) {
|
||||||
|
for i, t := range errorTests {
|
||||||
|
c.Logf("test %d: %T: %v", i, t.err, t.err)
|
||||||
|
checkErrorMatches(c, t.err, t.message, t.errInfo)
|
||||||
|
if checkMustSatisfy {
|
||||||
|
mustSatisfy(c, t.err, t.errInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all other satisfiers to make sure none match.
|
||||||
|
for _, otherErrInfo := range allErrors {
|
||||||
|
if checkMustSatisfy && otherErrInfo.equal(t.errInfo) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mustNotSatisfy(c, t.err, otherErrInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*errorTypeSuite) TestDeferredAnnotatef(c *gc.C) {
|
||||||
|
// Ensure DeferredAnnotatef annotates the errors.
|
||||||
|
errorTests := []errorTest{}
|
||||||
|
for _, errInfo := range allErrors {
|
||||||
|
errorTests = append(errorTests, []errorTest{{
|
||||||
|
deferredAnnotatef(nil, "comment"),
|
||||||
|
"<nil>",
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
deferredAnnotatef(stderrors.New("blast"), "comment"),
|
||||||
|
"comment: blast",
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
deferredAnnotatef(errInfo.argsConstructor("foo %d", 42), "comment %d", 69),
|
||||||
|
"comment 69: foo 42" + errInfo.suffix,
|
||||||
|
errInfo,
|
||||||
|
}, {
|
||||||
|
deferredAnnotatef(errInfo.argsConstructor(""), "comment"),
|
||||||
|
"comment: " + errInfo.suffix,
|
||||||
|
errInfo,
|
||||||
|
}, {
|
||||||
|
deferredAnnotatef(errInfo.wrapConstructor(stderrors.New("pow!"), "woo"), "comment"),
|
||||||
|
"comment: woo: pow!",
|
||||||
|
errInfo,
|
||||||
|
}}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
runErrorTests(c, errorTests, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*errorTypeSuite) TestAllErrors(c *gc.C) {
|
||||||
|
errorTests := []errorTest{}
|
||||||
|
for _, errInfo := range allErrors {
|
||||||
|
errorTests = append(errorTests, []errorTest{{
|
||||||
|
nil,
|
||||||
|
"<nil>",
|
||||||
|
nil,
|
||||||
|
}, {
|
||||||
|
errInfo.argsConstructor("foo %d", 42),
|
||||||
|
"foo 42" + errInfo.suffix,
|
||||||
|
errInfo,
|
||||||
|
}, {
|
||||||
|
errInfo.argsConstructor(""),
|
||||||
|
errInfo.suffix,
|
||||||
|
errInfo,
|
||||||
|
}, {
|
||||||
|
errInfo.wrapConstructor(stderrors.New("pow!"), "prefix"),
|
||||||
|
"prefix: pow!",
|
||||||
|
errInfo,
|
||||||
|
}, {
|
||||||
|
errInfo.wrapConstructor(stderrors.New("pow!"), ""),
|
||||||
|
"pow!",
|
||||||
|
errInfo,
|
||||||
|
}, {
|
||||||
|
errInfo.wrapConstructor(nil, "prefix"),
|
||||||
|
"prefix",
|
||||||
|
errInfo,
|
||||||
|
}}...)
|
||||||
|
}
|
||||||
|
|
||||||
|
runErrorTests(c, errorTests, true)
|
||||||
|
}
|
23
Godeps/_workspace/src/github.com/juju/errors/example_test.go
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/juju/errors/example_test.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2013, 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/juju/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleTrace() {
|
||||||
|
var err1 error = fmt.Errorf("something wicked this way comes")
|
||||||
|
var err2 error = nil
|
||||||
|
|
||||||
|
// Tracing a non nil error will return an error
|
||||||
|
fmt.Println(errors.Trace(err1))
|
||||||
|
// Tracing nil will return nil
|
||||||
|
fmt.Println(errors.Trace(err2))
|
||||||
|
|
||||||
|
// Output: something wicked this way comes
|
||||||
|
// <nil>
|
||||||
|
}
|
12
Godeps/_workspace/src/github.com/juju/errors/export_test.go
generated
vendored
Normal file
12
Godeps/_workspace/src/github.com/juju/errors/export_test.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2013, 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
// Since variables are declared before the init block, in order to get the goPath
|
||||||
|
// we need to return it rather than just reference it.
|
||||||
|
func GoPath() string {
|
||||||
|
return goPath
|
||||||
|
}
|
||||||
|
|
||||||
|
var TrimGoPath = trimGoPath
|
330
Godeps/_workspace/src/github.com/juju/errors/functions.go
generated
vendored
Normal file
330
Godeps/_workspace/src/github.com/juju/errors/functions.go
generated
vendored
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New is a drop in replacement for the standard libary errors module that records
|
||||||
|
// the location that the error is created.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// return errors.New("validation failed")
|
||||||
|
//
|
||||||
|
func New(message string) error {
|
||||||
|
err := &Err{message: message}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf creates a new annotated error and records the location that the
|
||||||
|
// error is created. This should be a drop in replacement for fmt.Errorf.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// return errors.Errorf("validation failed: %s", message)
|
||||||
|
//
|
||||||
|
func Errorf(format string, args ...interface{}) error {
|
||||||
|
err := &Err{message: fmt.Sprintf(format, args...)}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace adds the location of the Trace call to the stack. The Cause of the
|
||||||
|
// resulting error is the same as the error parameter. If the other error is
|
||||||
|
// nil, the result will be nil.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// if err := SomeFunc(); err != nil {
|
||||||
|
// return errors.Trace(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func Trace(other error) error {
|
||||||
|
if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := &Err{previous: other, cause: Cause(other)}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotate is used to add extra context to an existing error. The location of
|
||||||
|
// the Annotate call is recorded with the annotations. The file, line and
|
||||||
|
// function are also recorded.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// if err := SomeFunc(); err != nil {
|
||||||
|
// return errors.Annotate(err, "failed to frombulate")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func Annotate(other error, message string) error {
|
||||||
|
if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := &Err{
|
||||||
|
previous: other,
|
||||||
|
cause: Cause(other),
|
||||||
|
message: message,
|
||||||
|
}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotatef is used to add extra context to an existing error. The location of
|
||||||
|
// the Annotate call is recorded with the annotations. The file, line and
|
||||||
|
// function are also recorded.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// if err := SomeFunc(); err != nil {
|
||||||
|
// return errors.Annotatef(err, "failed to frombulate the %s", arg)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func Annotatef(other error, format string, args ...interface{}) error {
|
||||||
|
if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := &Err{
|
||||||
|
previous: other,
|
||||||
|
cause: Cause(other),
|
||||||
|
message: fmt.Sprintf(format, args...),
|
||||||
|
}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeferredAnnotatef annotates the given error (when it is not nil) with the given
|
||||||
|
// format string and arguments (like fmt.Sprintf). If *err is nil, DeferredAnnotatef
|
||||||
|
// does nothing. This method is used in a defer statement in order to annotate any
|
||||||
|
// resulting error with the same message.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// defer DeferredAnnotatef(&err, "failed to frombulate the %s", arg)
|
||||||
|
//
|
||||||
|
func DeferredAnnotatef(err *error, format string, args ...interface{}) {
|
||||||
|
if *err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newErr := &Err{
|
||||||
|
message: fmt.Sprintf(format, args...),
|
||||||
|
cause: Cause(*err),
|
||||||
|
previous: *err,
|
||||||
|
}
|
||||||
|
newErr.SetLocation(1)
|
||||||
|
*err = newErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap changes the Cause of the error. The location of the Wrap call is also
|
||||||
|
// stored in the error stack.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// if err := SomeFunc(); err != nil {
|
||||||
|
// newErr := &packageError{"more context", private_value}
|
||||||
|
// return errors.Wrap(err, newErr)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func Wrap(other, newDescriptive error) error {
|
||||||
|
err := &Err{
|
||||||
|
previous: other,
|
||||||
|
cause: newDescriptive,
|
||||||
|
}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapf changes the Cause of the error, and adds an annotation. The location
|
||||||
|
// of the Wrap call is also stored in the error stack.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
// if err := SomeFunc(); err != nil {
|
||||||
|
// return errors.Wrapf(err, simpleErrorType, "invalid value %q", value)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func Wrapf(other, newDescriptive error, format string, args ...interface{}) error {
|
||||||
|
err := &Err{
|
||||||
|
message: fmt.Sprintf(format, args...),
|
||||||
|
previous: other,
|
||||||
|
cause: newDescriptive,
|
||||||
|
}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask masks the given error with the given format string and arguments (like
|
||||||
|
// fmt.Sprintf), returning a new error that maintains the error stack, but
|
||||||
|
// hides the underlying error type. The error string still contains the full
|
||||||
|
// annotations. If you want to hide the annotations, call Wrap.
|
||||||
|
func Maskf(other error, format string, args ...interface{}) error {
|
||||||
|
if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := &Err{
|
||||||
|
message: fmt.Sprintf(format, args...),
|
||||||
|
previous: other,
|
||||||
|
}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask hides the underlying error type, and records the location of the masking.
|
||||||
|
func Mask(other error) error {
|
||||||
|
if other == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err := &Err{
|
||||||
|
previous: other,
|
||||||
|
}
|
||||||
|
err.SetLocation(1)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cause returns the cause of the given error. This will be either the
|
||||||
|
// original error, or the result of a Wrap or Mask call.
|
||||||
|
//
|
||||||
|
// Cause is the usual way to diagnose errors that may have been wrapped by
|
||||||
|
// the other errors functions.
|
||||||
|
func Cause(err error) error {
|
||||||
|
var diag error
|
||||||
|
if err, ok := err.(causer); ok {
|
||||||
|
diag = err.Cause()
|
||||||
|
}
|
||||||
|
if diag != nil {
|
||||||
|
return diag
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type causer interface {
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapper interface {
|
||||||
|
// Message returns the top level error message,
|
||||||
|
// not including the message from the Previous
|
||||||
|
// error.
|
||||||
|
Message() string
|
||||||
|
|
||||||
|
// Underlying returns the Previous error, or nil
|
||||||
|
// if there is none.
|
||||||
|
Underlying() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type locationer interface {
|
||||||
|
Location() (string, int)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ wrapper = (*Err)(nil)
|
||||||
|
_ locationer = (*Err)(nil)
|
||||||
|
_ causer = (*Err)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Details returns information about the stack of errors wrapped by err, in
|
||||||
|
// the format:
|
||||||
|
//
|
||||||
|
// [{filename:99: error one} {otherfile:55: cause of error one}]
|
||||||
|
//
|
||||||
|
// This is a terse alternative to ErrorStack as it returns a single line.
|
||||||
|
func Details(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return "[]"
|
||||||
|
}
|
||||||
|
var s []byte
|
||||||
|
s = append(s, '[')
|
||||||
|
for {
|
||||||
|
s = append(s, '{')
|
||||||
|
if err, ok := err.(locationer); ok {
|
||||||
|
file, line := err.Location()
|
||||||
|
if file != "" {
|
||||||
|
s = append(s, fmt.Sprintf("%s:%d", file, line)...)
|
||||||
|
s = append(s, ": "...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cerr, ok := err.(wrapper); ok {
|
||||||
|
s = append(s, cerr.Message()...)
|
||||||
|
err = cerr.Underlying()
|
||||||
|
} else {
|
||||||
|
s = append(s, err.Error()...)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
s = append(s, '}')
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s = append(s, ' ')
|
||||||
|
}
|
||||||
|
s = append(s, ']')
|
||||||
|
return string(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorStack returns a string representation of the annotated error. If the
|
||||||
|
// error passed as the parameter is not an annotated error, the result is
|
||||||
|
// simply the result of the Error() method on that error.
|
||||||
|
//
|
||||||
|
// If the error is an annotated error, a multi-line string is returned where
|
||||||
|
// each line represents one entry in the annotation stack. The full filename
|
||||||
|
// from the call stack is used in the output.
|
||||||
|
//
|
||||||
|
// first error
|
||||||
|
// github.com/juju/errors/annotation_test.go:193:
|
||||||
|
// github.com/juju/errors/annotation_test.go:194: annotation
|
||||||
|
// github.com/juju/errors/annotation_test.go:195:
|
||||||
|
// github.com/juju/errors/annotation_test.go:196: more context
|
||||||
|
// github.com/juju/errors/annotation_test.go:197:
|
||||||
|
func ErrorStack(err error) string {
|
||||||
|
return strings.Join(errorStack(err), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func errorStack(err error) []string {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want the first error first
|
||||||
|
var lines []string
|
||||||
|
for {
|
||||||
|
var buff []byte
|
||||||
|
if err, ok := err.(locationer); ok {
|
||||||
|
file, line := err.Location()
|
||||||
|
// Strip off the leading GOPATH/src path elements.
|
||||||
|
file = trimGoPath(file)
|
||||||
|
if file != "" {
|
||||||
|
buff = append(buff, fmt.Sprintf("%s:%d", file, line)...)
|
||||||
|
buff = append(buff, ": "...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cerr, ok := err.(wrapper); ok {
|
||||||
|
message := cerr.Message()
|
||||||
|
buff = append(buff, message...)
|
||||||
|
// If there is a cause for this error, and it is different to the cause
|
||||||
|
// of the underlying error, then output the error string in the stack trace.
|
||||||
|
var cause error
|
||||||
|
if err1, ok := err.(causer); ok {
|
||||||
|
cause = err1.Cause()
|
||||||
|
}
|
||||||
|
err = cerr.Underlying()
|
||||||
|
if cause != nil && !sameError(Cause(err), cause) {
|
||||||
|
if message != "" {
|
||||||
|
buff = append(buff, ": "...)
|
||||||
|
}
|
||||||
|
buff = append(buff, cause.Error()...)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buff = append(buff, err.Error()...)
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
lines = append(lines, string(buff))
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// reverse the lines to get the original error, which was at the end of
|
||||||
|
// the list, back to the start.
|
||||||
|
var result []string
|
||||||
|
for i := len(lines); i > 0; i-- {
|
||||||
|
result = append(result, lines[i-1])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
305
Godeps/_workspace/src/github.com/juju/errors/functions_test.go
generated
vendored
Normal file
305
Godeps/_workspace/src/github.com/juju/errors/functions_test.go
generated
vendored
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
// Copyright 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jc "github.com/juju/testing/checkers"
|
||||||
|
gc "gopkg.in/check.v1"
|
||||||
|
|
||||||
|
"github.com/juju/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type functionSuite struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = gc.Suite(&functionSuite{})
|
||||||
|
|
||||||
|
func (*functionSuite) TestNew(c *gc.C) {
|
||||||
|
err := errors.New("testing") //err newTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "testing")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["newTest"].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestErrorf(c *gc.C) {
|
||||||
|
err := errors.Errorf("testing %d", 42) //err errorfTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "testing 42")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["errorfTest"].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestTrace(c *gc.C) {
|
||||||
|
first := errors.New("first")
|
||||||
|
err := errors.Trace(first) //err traceTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "first")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["traceTest"].String())
|
||||||
|
|
||||||
|
c.Assert(errors.Trace(nil), gc.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestAnnotate(c *gc.C) {
|
||||||
|
first := errors.New("first")
|
||||||
|
err := errors.Annotate(first, "annotation") //err annotateTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "annotation: first")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotateTest"].String())
|
||||||
|
|
||||||
|
c.Assert(errors.Annotate(nil, "annotate"), gc.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestAnnotatef(c *gc.C) {
|
||||||
|
first := errors.New("first")
|
||||||
|
err := errors.Annotatef(first, "annotation %d", 2) //err annotatefTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "annotation 2: first")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["annotatefTest"].String())
|
||||||
|
|
||||||
|
c.Assert(errors.Annotatef(nil, "annotate"), gc.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestDeferredAnnotatef(c *gc.C) {
|
||||||
|
// NOTE: this test fails with gccgo
|
||||||
|
if runtime.Compiler == "gccgo" {
|
||||||
|
c.Skip("gccgo can't determine the location")
|
||||||
|
}
|
||||||
|
first := errors.New("first")
|
||||||
|
test := func() (err error) {
|
||||||
|
defer errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
|
||||||
|
return first
|
||||||
|
} //err deferredAnnotate
|
||||||
|
err := test()
|
||||||
|
c.Assert(err.Error(), gc.Equals, "deferred annotate: first")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, first)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["deferredAnnotate"].String())
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
errors.DeferredAnnotatef(&err, "deferred %s", "annotate")
|
||||||
|
c.Assert(err, gc.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestWrap(c *gc.C) {
|
||||||
|
first := errors.New("first") //err wrapFirst
|
||||||
|
detailed := errors.New("detailed")
|
||||||
|
err := errors.Wrap(first, detailed) //err wrapTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "detailed")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapFirst"].String())
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapTest"].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestWrapOfNil(c *gc.C) {
|
||||||
|
detailed := errors.New("detailed")
|
||||||
|
err := errors.Wrap(nil, detailed) //err nilWrapTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "detailed")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapTest"].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestWrapf(c *gc.C) {
|
||||||
|
first := errors.New("first") //err wrapfFirst
|
||||||
|
detailed := errors.New("detailed")
|
||||||
|
err := errors.Wrapf(first, detailed, "value %d", 42) //err wrapfTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfFirst"].String())
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["wrapfTest"].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestWrapfOfNil(c *gc.C) {
|
||||||
|
detailed := errors.New("detailed")
|
||||||
|
err := errors.Wrapf(nil, detailed, "value %d", 42) //err nilWrapfTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "value 42: detailed")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, detailed)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["nilWrapfTest"].String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestMask(c *gc.C) {
|
||||||
|
first := errors.New("first")
|
||||||
|
err := errors.Mask(first) //err maskTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "first")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskTest"].String())
|
||||||
|
|
||||||
|
c.Assert(errors.Mask(nil), gc.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestMaskf(c *gc.C) {
|
||||||
|
first := errors.New("first")
|
||||||
|
err := errors.Maskf(first, "masked %d", 42) //err maskfTest
|
||||||
|
c.Assert(err.Error(), gc.Equals, "masked 42: first")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
||||||
|
c.Assert(errors.Details(err), jc.Contains, tagToLocation["maskfTest"].String())
|
||||||
|
|
||||||
|
c.Assert(errors.Maskf(nil, "mask"), gc.IsNil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestCause(c *gc.C) {
|
||||||
|
c.Assert(errors.Cause(nil), gc.IsNil)
|
||||||
|
c.Assert(errors.Cause(someErr), gc.Equals, someErr)
|
||||||
|
|
||||||
|
fmtErr := fmt.Errorf("simple")
|
||||||
|
c.Assert(errors.Cause(fmtErr), gc.Equals, fmtErr)
|
||||||
|
|
||||||
|
err := errors.Wrap(someErr, fmtErr)
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
|
||||||
|
|
||||||
|
err = errors.Annotate(err, "annotated")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, fmtErr)
|
||||||
|
|
||||||
|
err = errors.Maskf(err, "maksed")
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, err)
|
||||||
|
|
||||||
|
// Look for a file that we know isn't there.
|
||||||
|
dir := c.MkDir()
|
||||||
|
_, err = os.Stat(filepath.Join(dir, "not-there"))
|
||||||
|
c.Assert(os.IsNotExist(err), jc.IsTrue)
|
||||||
|
|
||||||
|
err = errors.Annotatef(err, "wrap it")
|
||||||
|
// Now the error itself isn't a 'IsNotExist'.
|
||||||
|
c.Assert(os.IsNotExist(err), jc.IsFalse)
|
||||||
|
// However if we use the Check method, it is.
|
||||||
|
c.Assert(os.IsNotExist(errors.Cause(err)), jc.IsTrue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *functionSuite) TestDetails(c *gc.C) {
|
||||||
|
if runtime.Compiler == "gccgo" {
|
||||||
|
c.Skip("gccgo can't determine the location")
|
||||||
|
}
|
||||||
|
c.Assert(errors.Details(nil), gc.Equals, "[]")
|
||||||
|
|
||||||
|
otherErr := fmt.Errorf("other")
|
||||||
|
checkDetails(c, otherErr, "[{other}]")
|
||||||
|
|
||||||
|
err0 := newEmbed("foo") //err TestStack#0
|
||||||
|
checkDetails(c, err0, "[{$TestStack#0$: foo}]")
|
||||||
|
|
||||||
|
err1 := errors.Annotate(err0, "bar") //err TestStack#1
|
||||||
|
checkDetails(c, err1, "[{$TestStack#1$: bar} {$TestStack#0$: foo}]")
|
||||||
|
|
||||||
|
err2 := errors.Trace(err1) //err TestStack#2
|
||||||
|
checkDetails(c, err2, "[{$TestStack#2$: } {$TestStack#1$: bar} {$TestStack#0$: foo}]")
|
||||||
|
}
|
||||||
|
|
||||||
|
type tracer interface {
|
||||||
|
StackTrace() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*functionSuite) TestErrorStack(c *gc.C) {
|
||||||
|
for i, test := range []struct {
|
||||||
|
message string
|
||||||
|
generator func() error
|
||||||
|
expected string
|
||||||
|
tracer bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
message: "nil",
|
||||||
|
generator: func() error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
message: "raw error",
|
||||||
|
generator: func() error {
|
||||||
|
return fmt.Errorf("raw")
|
||||||
|
},
|
||||||
|
expected: "raw",
|
||||||
|
}, {
|
||||||
|
message: "single error stack",
|
||||||
|
generator: func() error {
|
||||||
|
return errors.New("first error") //err single
|
||||||
|
},
|
||||||
|
expected: "$single$: first error",
|
||||||
|
tracer: true,
|
||||||
|
}, {
|
||||||
|
message: "annotated error",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.New("first error") //err annotated-0
|
||||||
|
return errors.Annotate(err, "annotation") //err annotated-1
|
||||||
|
},
|
||||||
|
expected: "" +
|
||||||
|
"$annotated-0$: first error\n" +
|
||||||
|
"$annotated-1$: annotation",
|
||||||
|
tracer: true,
|
||||||
|
}, {
|
||||||
|
message: "wrapped error",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.New("first error") //err wrapped-0
|
||||||
|
return errors.Wrap(err, newError("detailed error")) //err wrapped-1
|
||||||
|
},
|
||||||
|
expected: "" +
|
||||||
|
"$wrapped-0$: first error\n" +
|
||||||
|
"$wrapped-1$: detailed error",
|
||||||
|
tracer: true,
|
||||||
|
}, {
|
||||||
|
message: "annotated wrapped error",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.Errorf("first error") //err ann-wrap-0
|
||||||
|
err = errors.Wrap(err, fmt.Errorf("detailed error")) //err ann-wrap-1
|
||||||
|
return errors.Annotatef(err, "annotated") //err ann-wrap-2
|
||||||
|
},
|
||||||
|
expected: "" +
|
||||||
|
"$ann-wrap-0$: first error\n" +
|
||||||
|
"$ann-wrap-1$: detailed error\n" +
|
||||||
|
"$ann-wrap-2$: annotated",
|
||||||
|
tracer: true,
|
||||||
|
}, {
|
||||||
|
message: "traced, and annotated",
|
||||||
|
generator: func() error {
|
||||||
|
err := errors.New("first error") //err stack-0
|
||||||
|
err = errors.Trace(err) //err stack-1
|
||||||
|
err = errors.Annotate(err, "some context") //err stack-2
|
||||||
|
err = errors.Trace(err) //err stack-3
|
||||||
|
err = errors.Annotate(err, "more context") //err stack-4
|
||||||
|
return errors.Trace(err) //err stack-5
|
||||||
|
},
|
||||||
|
expected: "" +
|
||||||
|
"$stack-0$: first error\n" +
|
||||||
|
"$stack-1$: \n" +
|
||||||
|
"$stack-2$: some context\n" +
|
||||||
|
"$stack-3$: \n" +
|
||||||
|
"$stack-4$: more context\n" +
|
||||||
|
"$stack-5$: ",
|
||||||
|
tracer: true,
|
||||||
|
}, {
|
||||||
|
message: "uncomparable, wrapped with a value error",
|
||||||
|
generator: func() error {
|
||||||
|
err := newNonComparableError("first error") //err mixed-0
|
||||||
|
err = errors.Trace(err) //err mixed-1
|
||||||
|
err = errors.Wrap(err, newError("value error")) //err mixed-2
|
||||||
|
err = errors.Maskf(err, "masked") //err mixed-3
|
||||||
|
err = errors.Annotate(err, "more context") //err mixed-4
|
||||||
|
return errors.Trace(err) //err mixed-5
|
||||||
|
},
|
||||||
|
expected: "" +
|
||||||
|
"first error\n" +
|
||||||
|
"$mixed-1$: \n" +
|
||||||
|
"$mixed-2$: value error\n" +
|
||||||
|
"$mixed-3$: masked\n" +
|
||||||
|
"$mixed-4$: more context\n" +
|
||||||
|
"$mixed-5$: ",
|
||||||
|
tracer: true,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
c.Logf("%v: %s", i, test.message)
|
||||||
|
err := test.generator()
|
||||||
|
expected := replaceLocations(test.expected)
|
||||||
|
stack := errors.ErrorStack(err)
|
||||||
|
ok := c.Check(stack, gc.Equals, expected)
|
||||||
|
if !ok {
|
||||||
|
c.Logf("%#v", err)
|
||||||
|
}
|
||||||
|
tracer, ok := err.(tracer)
|
||||||
|
c.Check(ok, gc.Equals, test.tracer)
|
||||||
|
if ok {
|
||||||
|
stackTrace := tracer.StackTrace()
|
||||||
|
c.Check(stackTrace, gc.DeepEquals, strings.Split(stack, "\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
Godeps/_workspace/src/github.com/juju/errors/package_test.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/juju/errors/package_test.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2013, 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
gc "gopkg.in/check.v1"
|
||||||
|
|
||||||
|
"github.com/juju/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test(t *testing.T) {
|
||||||
|
gc.TestingT(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkDetails(c *gc.C, err error, details string) {
|
||||||
|
c.Assert(err, gc.NotNil)
|
||||||
|
expectedDetails := replaceLocations(details)
|
||||||
|
c.Assert(errors.Details(err), gc.Equals, expectedDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkErr(c *gc.C, err, cause error, msg string, details string) {
|
||||||
|
c.Assert(err, gc.NotNil)
|
||||||
|
c.Assert(err.Error(), gc.Equals, msg)
|
||||||
|
c.Assert(errors.Cause(err), gc.Equals, cause)
|
||||||
|
expectedDetails := replaceLocations(details)
|
||||||
|
c.Assert(errors.Details(err), gc.Equals, expectedDetails)
|
||||||
|
}
|
||||||
|
|
||||||
|
func replaceLocations(line string) string {
|
||||||
|
result := ""
|
||||||
|
for {
|
||||||
|
i := strings.Index(line, "$")
|
||||||
|
if i == -1 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
result += line[0:i]
|
||||||
|
line = line[i+1:]
|
||||||
|
i = strings.Index(line, "$")
|
||||||
|
if i == -1 {
|
||||||
|
panic("no second $")
|
||||||
|
}
|
||||||
|
result += location(line[0:i]).String()
|
||||||
|
line = line[i+1:]
|
||||||
|
}
|
||||||
|
result += line
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func location(tag string) Location {
|
||||||
|
loc, ok := tagToLocation[tag]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("tag %q not found", tag))
|
||||||
|
}
|
||||||
|
return loc
|
||||||
|
}
|
||||||
|
|
||||||
|
type Location struct {
|
||||||
|
file string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (loc Location) String() string {
|
||||||
|
return fmt.Sprintf("%s:%d", loc.file, loc.line)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tagToLocation = make(map[string]Location)
|
||||||
|
|
||||||
|
func setLocationsForErrorTags(filename string) {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
filename = "github.com/juju/errors/" + filename
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
for i, line := range lines {
|
||||||
|
if j := strings.Index(line, "//err "); j >= 0 {
|
||||||
|
tag := line[j+len("//err "):]
|
||||||
|
if _, found := tagToLocation[tag]; found {
|
||||||
|
panic(fmt.Sprintf("tag %q already processed previously", tag))
|
||||||
|
}
|
||||||
|
tagToLocation[tag] = Location{file: filename, line: i + 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setLocationsForErrorTags("error_test.go")
|
||||||
|
setLocationsForErrorTags("functions_test.go")
|
||||||
|
}
|
35
Godeps/_workspace/src/github.com/juju/errors/path.go
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/juju/errors/path.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2013, 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// prefixSize is used internally to trim the user specific path from the
|
||||||
|
// front of the returned filenames from the runtime call stack.
|
||||||
|
var prefixSize int
|
||||||
|
|
||||||
|
// goPath is the deduced path based on the location of this file as compiled.
|
||||||
|
var goPath string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_, file, _, ok := runtime.Caller(0)
|
||||||
|
if ok {
|
||||||
|
// We know that the end of the file should be:
|
||||||
|
// github.com/juju/errors/path.go
|
||||||
|
size := len(file)
|
||||||
|
suffix := len("github.com/juju/errors/path.go")
|
||||||
|
goPath = file[:size-suffix]
|
||||||
|
prefixSize = len(goPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func trimGoPath(filename string) string {
|
||||||
|
if strings.HasPrefix(filename, goPath) {
|
||||||
|
return filename[prefixSize:]
|
||||||
|
}
|
||||||
|
return filename
|
||||||
|
}
|
29
Godeps/_workspace/src/github.com/juju/errors/path_test.go
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/juju/errors/path_test.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2013, 2014 Canonical Ltd.
|
||||||
|
// Licensed under the LGPLv3, see LICENCE file for details.
|
||||||
|
|
||||||
|
package errors_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
|
||||||
|
gc "gopkg.in/check.v1"
|
||||||
|
|
||||||
|
"github.com/juju/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pathSuite struct{}
|
||||||
|
|
||||||
|
var _ = gc.Suite(&pathSuite{})
|
||||||
|
|
||||||
|
func (*pathSuite) TestGoPathSet(c *gc.C) {
|
||||||
|
c.Assert(errors.GoPath(), gc.Not(gc.Equals), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*pathSuite) TestTrimGoPath(c *gc.C) {
|
||||||
|
relativeImport := "github.com/foo/bar/baz.go"
|
||||||
|
filename := path.Join(errors.GoPath(), relativeImport)
|
||||||
|
c.Assert(errors.TrimGoPath(filename), gc.Equals, relativeImport)
|
||||||
|
|
||||||
|
absoluteImport := "/usr/share/foo/bar/baz.go"
|
||||||
|
c.Assert(errors.TrimGoPath(absoluteImport), gc.Equals, absoluteImport)
|
||||||
|
}
|
27
Godeps/_workspace/src/github.com/kr/fs/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/kr/fs/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.
|
3
Godeps/_workspace/src/github.com/kr/fs/Readme
generated
vendored
Normal file
3
Godeps/_workspace/src/github.com/kr/fs/Readme
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Filesystem Package
|
||||||
|
|
||||||
|
http://godoc.org/github.com/kr/fs
|
19
Godeps/_workspace/src/github.com/kr/fs/example_test.go
generated
vendored
Normal file
19
Godeps/_workspace/src/github.com/kr/fs/example_test.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package fs_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/kr/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleWalker() {
|
||||||
|
walker := fs.Walk("/usr/lib")
|
||||||
|
for walker.Step() {
|
||||||
|
if err := walker.Err(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println(walker.Path())
|
||||||
|
}
|
||||||
|
}
|
36
Godeps/_workspace/src/github.com/kr/fs/filesystem.go
generated
vendored
Normal file
36
Godeps/_workspace/src/github.com/kr/fs/filesystem.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileSystem defines the methods of an abstract filesystem.
|
||||||
|
type FileSystem interface {
|
||||||
|
|
||||||
|
// ReadDir reads the directory named by dirname and returns a
|
||||||
|
// list of directory entries.
|
||||||
|
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||||
|
|
||||||
|
// Lstat returns a FileInfo describing the named file. If the file is a
|
||||||
|
// symbolic link, the returned FileInfo describes the symbolic link. Lstat
|
||||||
|
// makes no attempt to follow the link.
|
||||||
|
Lstat(name string) (os.FileInfo, error)
|
||||||
|
|
||||||
|
// Join joins any number of path elements into a single path, adding a
|
||||||
|
// separator if necessary. The result is Cleaned; in particular, all
|
||||||
|
// empty strings are ignored.
|
||||||
|
//
|
||||||
|
// The separator is FileSystem specific.
|
||||||
|
Join(elem ...string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// fs represents a FileSystem provided by the os package.
|
||||||
|
type fs struct{}
|
||||||
|
|
||||||
|
func (f *fs) ReadDir(dirname string) ([]os.FileInfo, error) { return ioutil.ReadDir(dirname) }
|
||||||
|
|
||||||
|
func (f *fs) Lstat(name string) (os.FileInfo, error) { return os.Lstat(name) }
|
||||||
|
|
||||||
|
func (f *fs) Join(elem ...string) string { return filepath.Join(elem...) }
|
95
Godeps/_workspace/src/github.com/kr/fs/walk.go
generated
vendored
Normal file
95
Godeps/_workspace/src/github.com/kr/fs/walk.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Package fs provides filesystem-related functions.
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walker provides a convenient interface for iterating over the
|
||||||
|
// descendants of a filesystem path.
|
||||||
|
// Successive calls to the Step method will step through each
|
||||||
|
// file or directory in the tree, including the root. The files
|
||||||
|
// are walked in lexical order, which makes the output deterministic
|
||||||
|
// but means that for very large directories Walker can be inefficient.
|
||||||
|
// Walker does not follow symbolic links.
|
||||||
|
type Walker struct {
|
||||||
|
fs FileSystem
|
||||||
|
cur item
|
||||||
|
stack []item
|
||||||
|
descend bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type item struct {
|
||||||
|
path string
|
||||||
|
info os.FileInfo
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk returns a new Walker rooted at root.
|
||||||
|
func Walk(root string) *Walker {
|
||||||
|
return WalkFS(root, new(fs))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WalkFS returns a new Walker rooted at root on the FileSystem fs.
|
||||||
|
func WalkFS(root string, fs FileSystem) *Walker {
|
||||||
|
info, err := fs.Lstat(root)
|
||||||
|
return &Walker{
|
||||||
|
fs: fs,
|
||||||
|
stack: []item{{root, info, err}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step advances the Walker to the next file or directory,
|
||||||
|
// which will then be available through the Path, Stat,
|
||||||
|
// and Err methods.
|
||||||
|
// It returns false when the walk stops at the end of the tree.
|
||||||
|
func (w *Walker) Step() bool {
|
||||||
|
if w.descend && w.cur.err == nil && w.cur.info.IsDir() {
|
||||||
|
list, err := w.fs.ReadDir(w.cur.path)
|
||||||
|
if err != nil {
|
||||||
|
w.cur.err = err
|
||||||
|
w.stack = append(w.stack, w.cur)
|
||||||
|
} else {
|
||||||
|
for i := len(list) - 1; i >= 0; i-- {
|
||||||
|
path := w.fs.Join(w.cur.path, list[i].Name())
|
||||||
|
w.stack = append(w.stack, item{path, list[i], nil})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(w.stack) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
i := len(w.stack) - 1
|
||||||
|
w.cur = w.stack[i]
|
||||||
|
w.stack = w.stack[:i]
|
||||||
|
w.descend = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path returns the path to the most recent file or directory
|
||||||
|
// visited by a call to Step. It contains the argument to Walk
|
||||||
|
// as a prefix; that is, if Walk is called with "dir", which is
|
||||||
|
// a directory containing the file "a", Path will return "dir/a".
|
||||||
|
func (w *Walker) Path() string {
|
||||||
|
return w.cur.path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns info for the most recent file or directory
|
||||||
|
// visited by a call to Step.
|
||||||
|
func (w *Walker) Stat() os.FileInfo {
|
||||||
|
return w.cur.info
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error, if any, for the most recent attempt
|
||||||
|
// by Step to visit a file or directory. If a directory has
|
||||||
|
// an error, w will not descend into that directory.
|
||||||
|
func (w *Walker) Err() error {
|
||||||
|
return w.cur.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipDir causes the currently visited directory to be skipped.
|
||||||
|
// If w is not on a directory, SkipDir has no effect.
|
||||||
|
func (w *Walker) SkipDir() {
|
||||||
|
w.descend = false
|
||||||
|
}
|
209
Godeps/_workspace/src/github.com/kr/fs/walk_test.go
generated
vendored
Normal file
209
Godeps/_workspace/src/github.com/kr/fs/walk_test.go
generated
vendored
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package fs_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kr/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PathTest struct {
|
||||||
|
path, result string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
name string
|
||||||
|
entries []*Node // nil if the entry is a file
|
||||||
|
mark int
|
||||||
|
}
|
||||||
|
|
||||||
|
var tree = &Node{
|
||||||
|
"testdata",
|
||||||
|
[]*Node{
|
||||||
|
{"a", nil, 0},
|
||||||
|
{"b", []*Node{}, 0},
|
||||||
|
{"c", nil, 0},
|
||||||
|
{
|
||||||
|
"d",
|
||||||
|
[]*Node{
|
||||||
|
{"x", nil, 0},
|
||||||
|
{"y", []*Node{}, 0},
|
||||||
|
{
|
||||||
|
"z",
|
||||||
|
[]*Node{
|
||||||
|
{"u", nil, 0},
|
||||||
|
{"v", nil, 0},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkTree(n *Node, path string, f func(path string, n *Node)) {
|
||||||
|
f(path, n)
|
||||||
|
for _, e := range n.entries {
|
||||||
|
walkTree(e, filepath.Join(path, e.name), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTree(t *testing.T) {
|
||||||
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||||
|
if n.entries == nil {
|
||||||
|
fd, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("makeTree: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
} else {
|
||||||
|
os.Mkdir(path, 0770)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
|
||||||
|
|
||||||
|
func checkMarks(t *testing.T, report bool) {
|
||||||
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||||
|
if n.mark != 1 && report {
|
||||||
|
t.Errorf("node %s mark = %d; expected 1", path, n.mark)
|
||||||
|
}
|
||||||
|
n.mark = 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes that each node name is unique. Good enough for a test.
|
||||||
|
// If clear is true, any incoming error is cleared before return. The errors
|
||||||
|
// are always accumulated, though.
|
||||||
|
func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error {
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
if clear {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
name := info.Name()
|
||||||
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||||
|
if n.name == name {
|
||||||
|
n.mark++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWalk(t *testing.T) {
|
||||||
|
makeTree(t)
|
||||||
|
errors := make([]error, 0, 10)
|
||||||
|
clear := true
|
||||||
|
markFn := func(walker *fs.Walker) (err error) {
|
||||||
|
for walker.Step() {
|
||||||
|
err = mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Expect no errors.
|
||||||
|
err := markFn(fs.Walk(tree.name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("no error expected, found: %s", err)
|
||||||
|
}
|
||||||
|
if len(errors) != 0 {
|
||||||
|
t.Fatalf("unexpected errors: %s", errors)
|
||||||
|
}
|
||||||
|
checkMarks(t, true)
|
||||||
|
errors = errors[0:0]
|
||||||
|
|
||||||
|
// Test permission errors. Only possible if we're not root
|
||||||
|
// and only on some file systems (AFS, FAT). To avoid errors during
|
||||||
|
// all.bash on those file systems, skip during go test -short.
|
||||||
|
if os.Getuid() > 0 && !testing.Short() {
|
||||||
|
// introduce 2 errors: chmod top-level directories to 0
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
|
||||||
|
|
||||||
|
// 3) capture errors, expect two.
|
||||||
|
// mark respective subtrees manually
|
||||||
|
markTree(tree.entries[1])
|
||||||
|
markTree(tree.entries[3])
|
||||||
|
// correct double-marking of directory itself
|
||||||
|
tree.entries[1].mark--
|
||||||
|
tree.entries[3].mark--
|
||||||
|
err := markFn(fs.Walk(tree.name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error return from Walk, got %s", err)
|
||||||
|
}
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
|
||||||
|
}
|
||||||
|
// the inaccessible subtrees were marked manually
|
||||||
|
checkMarks(t, true)
|
||||||
|
errors = errors[0:0]
|
||||||
|
|
||||||
|
// 4) capture errors, stop after first error.
|
||||||
|
// mark respective subtrees manually
|
||||||
|
markTree(tree.entries[1])
|
||||||
|
markTree(tree.entries[3])
|
||||||
|
// correct double-marking of directory itself
|
||||||
|
tree.entries[1].mark--
|
||||||
|
tree.entries[3].mark--
|
||||||
|
clear = false // error will stop processing
|
||||||
|
err = markFn(fs.Walk(tree.name))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error return from Walk")
|
||||||
|
}
|
||||||
|
if len(errors) != 1 {
|
||||||
|
t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
|
||||||
|
}
|
||||||
|
// the inaccessible subtrees were marked manually
|
||||||
|
checkMarks(t, false)
|
||||||
|
errors = errors[0:0]
|
||||||
|
|
||||||
|
// restore permissions
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
if err := os.RemoveAll(tree.name); err != nil {
|
||||||
|
t.Errorf("removeTree: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBug3486(t *testing.T) { // http://code.google.com/p/go/issues/detail?id=3486
|
||||||
|
root, err := filepath.EvalSymlinks(runtime.GOROOT())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
lib := filepath.Join(root, "lib")
|
||||||
|
src := filepath.Join(root, "src")
|
||||||
|
seenSrc := false
|
||||||
|
walker := fs.Walk(root)
|
||||||
|
for walker.Step() {
|
||||||
|
if walker.Err() != nil {
|
||||||
|
t.Fatal(walker.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch walker.Path() {
|
||||||
|
case lib:
|
||||||
|
walker.SkipDir()
|
||||||
|
case src:
|
||||||
|
seenSrc = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !seenSrc {
|
||||||
|
t.Fatalf("%q not seen", src)
|
||||||
|
}
|
||||||
|
}
|
2
Godeps/_workspace/src/github.com/pkg/sftp/CONTRIBUTORS
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/pkg/sftp/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Dave Cheney <dave@cheney.net>
|
||||||
|
Saulius Gurklys <s4uliu5@gmail.com>
|
9
Godeps/_workspace/src/github.com/pkg/sftp/LICENSE
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/pkg/sftp/LICENSE
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Copyright (c) 2013, Dave Cheney
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
Godeps/_workspace/src/github.com/pkg/sftp/README.md
generated
vendored
Normal file
27
Godeps/_workspace/src/github.com/pkg/sftp/README.md
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
sftp
|
||||||
|
----
|
||||||
|
|
||||||
|
The `sftp` package provides support for file system operations on remote ssh servers using the SFTP subsystem.
|
||||||
|
|
||||||
|
[![Build Status](https://drone.io/github.com/pkg/sftp/status.png)](https://drone.io/github.com/pkg/sftp/latest)
|
||||||
|
|
||||||
|
usage and examples
|
||||||
|
------------------
|
||||||
|
|
||||||
|
See [godoc.org/github.com/pkg/sftp](http://godoc.org/github.com/pkg/sftp) for examples and usage.
|
||||||
|
|
||||||
|
The basic operation of the package mirrors the facilities of the [os](http://golang.org/pkg/os) package.
|
||||||
|
|
||||||
|
The Walker interface for directory traversal is heavily inspired by Keith Rarick's [fs](http://godoc.org/github.com/kr/fs) package.
|
||||||
|
|
||||||
|
roadmap
|
||||||
|
-------
|
||||||
|
|
||||||
|
* Currently all traffic with the server is serialized, this can be improved by allowing overlapping requests/responses.
|
||||||
|
* There is way too much duplication in the Client methods. If there was an unmarshal(interface{}) method this would reduce a heap of the duplication.
|
||||||
|
* Implement integration tests by talking directly to a real opensftp-server process. This shouldn't be too difficult to implement with a small refactoring to the sftp.NewClient method. These tests should be gated on an -sftp.integration test flag. _in progress_
|
||||||
|
|
||||||
|
contributing
|
||||||
|
------------
|
||||||
|
|
||||||
|
Features, Issues, and Pull Requests are always welcome.
|
138
Godeps/_workspace/src/github.com/pkg/sftp/attrs.go
generated
vendored
Normal file
138
Godeps/_workspace/src/github.com/pkg/sftp/attrs.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
// ssh_FXP_ATTRS support
|
||||||
|
// see http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ssh_FILEXFER_ATTR_SIZE = 0x00000001
|
||||||
|
ssh_FILEXFER_ATTR_UIDGID = 0x00000002
|
||||||
|
ssh_FILEXFER_ATTR_PERMISSIONS = 0x00000004
|
||||||
|
ssh_FILEXFER_ATTR_ACMODTIME = 0x00000008
|
||||||
|
ssh_FILEXFER_ATTR_EXTENDED = 0x80000000
|
||||||
|
)
|
||||||
|
|
||||||
|
// fileInfo is an artificial type designed to satisfy os.FileInfo.
|
||||||
|
type fileInfo struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
mode os.FileMode
|
||||||
|
mtime time.Time
|
||||||
|
sys interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the base name of the file.
|
||||||
|
func (fi *fileInfo) Name() string { return fi.name }
|
||||||
|
|
||||||
|
// Size returns the length in bytes for regular files; system-dependent for others.
|
||||||
|
func (fi *fileInfo) Size() int64 { return fi.size }
|
||||||
|
|
||||||
|
// Mode returns file mode bits.
|
||||||
|
func (fi *fileInfo) Mode() os.FileMode { return fi.mode }
|
||||||
|
|
||||||
|
// ModTime returns the last modification time of the file.
|
||||||
|
func (fi *fileInfo) ModTime() time.Time { return fi.mtime }
|
||||||
|
|
||||||
|
// IsDir returns true if the file is a directory.
|
||||||
|
func (fi *fileInfo) IsDir() bool { return fi.Mode().IsDir() }
|
||||||
|
|
||||||
|
func (fi *fileInfo) Sys() interface{} { return fi.sys }
|
||||||
|
|
||||||
|
// FileStat holds the original unmarshalled values from a call to READDIR or *STAT.
|
||||||
|
// It is exported for the purposes of accessing the raw values via os.FileInfo.Sys()
|
||||||
|
type FileStat struct {
|
||||||
|
Size uint64
|
||||||
|
Mode uint32
|
||||||
|
Mtime uint32
|
||||||
|
Atime uint32
|
||||||
|
Uid uint32
|
||||||
|
Gid uint32
|
||||||
|
Extended []StatExtended
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatExtended struct {
|
||||||
|
ExtType string
|
||||||
|
ExtData string
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileInfoFromStat(st *FileStat, name string) os.FileInfo {
|
||||||
|
fs := &fileInfo{
|
||||||
|
name: name,
|
||||||
|
size: int64(st.Size),
|
||||||
|
mode: toFileMode(st.Mode),
|
||||||
|
mtime: time.Unix(int64(st.Mtime), 0),
|
||||||
|
sys: st,
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalAttrs(b []byte) (*FileStat, []byte) {
|
||||||
|
flags, b := unmarshalUint32(b)
|
||||||
|
var fs FileStat
|
||||||
|
if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE {
|
||||||
|
fs.Size, b = unmarshalUint64(b)
|
||||||
|
}
|
||||||
|
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
|
||||||
|
fs.Uid, b = unmarshalUint32(b)
|
||||||
|
}
|
||||||
|
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
|
||||||
|
fs.Gid, b = unmarshalUint32(b)
|
||||||
|
}
|
||||||
|
if flags&ssh_FILEXFER_ATTR_PERMISSIONS == ssh_FILEXFER_ATTR_PERMISSIONS {
|
||||||
|
fs.Mode, b = unmarshalUint32(b)
|
||||||
|
}
|
||||||
|
if flags&ssh_FILEXFER_ATTR_ACMODTIME == ssh_FILEXFER_ATTR_ACMODTIME {
|
||||||
|
fs.Atime, b = unmarshalUint32(b)
|
||||||
|
fs.Mtime, b = unmarshalUint32(b)
|
||||||
|
}
|
||||||
|
if flags&ssh_FILEXFER_ATTR_EXTENDED == ssh_FILEXFER_ATTR_EXTENDED {
|
||||||
|
var count uint32
|
||||||
|
count, b = unmarshalUint32(b)
|
||||||
|
ext := make([]StatExtended, count, count)
|
||||||
|
for i := uint32(0); i < count; i++ {
|
||||||
|
var typ string
|
||||||
|
var data string
|
||||||
|
typ, b = unmarshalString(b)
|
||||||
|
data, b = unmarshalString(b)
|
||||||
|
ext[i] = StatExtended{typ, data}
|
||||||
|
}
|
||||||
|
fs.Extended = ext
|
||||||
|
}
|
||||||
|
return &fs, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// toFileMode converts sftp filemode bits to the os.FileMode specification
|
||||||
|
func toFileMode(mode uint32) os.FileMode {
|
||||||
|
var fm = os.FileMode(mode & 0777)
|
||||||
|
switch mode & syscall.S_IFMT {
|
||||||
|
case syscall.S_IFBLK:
|
||||||
|
fm |= os.ModeDevice
|
||||||
|
case syscall.S_IFCHR:
|
||||||
|
fm |= os.ModeDevice | os.ModeCharDevice
|
||||||
|
case syscall.S_IFDIR:
|
||||||
|
fm |= os.ModeDir
|
||||||
|
case syscall.S_IFIFO:
|
||||||
|
fm |= os.ModeNamedPipe
|
||||||
|
case syscall.S_IFLNK:
|
||||||
|
fm |= os.ModeSymlink
|
||||||
|
case syscall.S_IFREG:
|
||||||
|
// nothing to do
|
||||||
|
case syscall.S_IFSOCK:
|
||||||
|
fm |= os.ModeSocket
|
||||||
|
}
|
||||||
|
if mode&syscall.S_ISGID != 0 {
|
||||||
|
fm |= os.ModeSetgid
|
||||||
|
}
|
||||||
|
if mode&syscall.S_ISUID != 0 {
|
||||||
|
fm |= os.ModeSetuid
|
||||||
|
}
|
||||||
|
if mode&syscall.S_ISVTX != 0 {
|
||||||
|
fm |= os.ModeSticky
|
||||||
|
}
|
||||||
|
return fm
|
||||||
|
}
|
45
Godeps/_workspace/src/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/pkg/sftp/attrs_test.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ensure that attrs implemenst os.FileInfo
|
||||||
|
var _ os.FileInfo = new(fileInfo)
|
||||||
|
|
||||||
|
var unmarshalAttrsTests = []struct {
|
||||||
|
b []byte
|
||||||
|
want *fileInfo
|
||||||
|
rest []byte
|
||||||
|
}{
|
||||||
|
{marshal(nil, struct{ Flags uint32 }{}), &fileInfo{mtime: time.Unix(int64(0), 0)}, nil},
|
||||||
|
{marshal(nil, struct {
|
||||||
|
Flags uint32
|
||||||
|
Size uint64
|
||||||
|
}{ssh_FILEXFER_ATTR_SIZE, 20}), &fileInfo{size: 20, mtime: time.Unix(int64(0), 0)}, nil},
|
||||||
|
{marshal(nil, struct {
|
||||||
|
Flags uint32
|
||||||
|
Size uint64
|
||||||
|
Permissions uint32
|
||||||
|
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||||
|
{marshal(nil, struct {
|
||||||
|
Flags uint32
|
||||||
|
Size uint64
|
||||||
|
Uid, Gid, Permissions uint32
|
||||||
|
}{ssh_FILEXFER_ATTR_SIZE | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_UIDGID | ssh_FILEXFER_ATTR_PERMISSIONS, 20, 1000, 1000, 0644}), &fileInfo{size: 20, mode: os.FileMode(0644), mtime: time.Unix(int64(0), 0)}, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalAttrs(t *testing.T) {
|
||||||
|
for _, tt := range unmarshalAttrsTests {
|
||||||
|
stat, rest := unmarshalAttrs(tt.b)
|
||||||
|
got := fileInfoFromStat(stat, "")
|
||||||
|
tt.want.sys = got.Sys()
|
||||||
|
if !reflect.DeepEqual(got, tt.want) || !bytes.Equal(tt.rest, rest) {
|
||||||
|
t.Errorf("unmarshalAttrs(%#v): want %#v, %#v, got: %#v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
717
Godeps/_workspace/src/github.com/pkg/sftp/client.go
generated
vendored
Normal file
717
Godeps/_workspace/src/github.com/pkg/sftp/client.go
generated
vendored
Normal file
@ -0,0 +1,717 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/kr/fs"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// New creates a new SFTP client on conn.
|
||||||
|
func NewClient(conn *ssh.Client) (*Client, error) {
|
||||||
|
s, err := conn.NewSession()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := s.RequestSubsystem("sftp"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pw, err := s.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pr, err := s.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewClientPipe(pr, pw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientPipe creates a new SFTP client given a Reader and a WriteCloser.
|
||||||
|
// This can be used for connecting to an SFTP server over TCP/TLS or by using
|
||||||
|
// the system's ssh client program (e.g. via exec.Command).
|
||||||
|
func NewClientPipe(rd io.Reader, wr io.WriteCloser) (*Client, error) {
|
||||||
|
sftp := &Client{
|
||||||
|
w: wr,
|
||||||
|
r: rd,
|
||||||
|
}
|
||||||
|
if err := sftp.sendInit(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return sftp, sftp.recvVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client represents an SFTP session on a *ssh.ClientConn SSH connection.
|
||||||
|
// Multiple Clients can be active on a single SSH connection, and a Client
|
||||||
|
// may be called concurrently from multiple Goroutines.
|
||||||
|
//
|
||||||
|
// Client implements the github.com/kr/fs.FileSystem interface.
|
||||||
|
type Client struct {
|
||||||
|
w io.WriteCloser
|
||||||
|
r io.Reader
|
||||||
|
mu sync.Mutex // locks mu and seralises commands to the server
|
||||||
|
nextid uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the SFTP session.
|
||||||
|
func (c *Client) Close() error { return c.w.Close() }
|
||||||
|
|
||||||
|
// Create creates the named file mode 0666 (before umask), truncating it if
|
||||||
|
// it already exists. If successful, methods on the returned File can be
|
||||||
|
// used for I/O; the associated file descriptor has mode O_RDWR.
|
||||||
|
func (c *Client) Create(path string) (*File, error) {
|
||||||
|
return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))
|
||||||
|
}
|
||||||
|
|
||||||
|
const sftpProtocolVersion = 3 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||||
|
|
||||||
|
func (c *Client) sendInit() error {
|
||||||
|
return sendPacket(c.w, sshFxInitPacket{
|
||||||
|
Version: sftpProtocolVersion, // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the current value of c.nextid and increments it
|
||||||
|
// callers is expected to hold c.mu
|
||||||
|
func (c *Client) nextId() uint32 {
|
||||||
|
v := c.nextid
|
||||||
|
c.nextid++
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) recvVersion() error {
|
||||||
|
typ, data, err := recvPacket(c.r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if typ != ssh_FXP_VERSION {
|
||||||
|
return &unexpectedPacketErr{ssh_FXP_VERSION, typ}
|
||||||
|
}
|
||||||
|
|
||||||
|
version, _ := unmarshalUint32(data)
|
||||||
|
if version != sftpProtocolVersion {
|
||||||
|
return &unexpectedVersionErr{sftpProtocolVersion, version}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk returns a new Walker rooted at root.
|
||||||
|
func (c *Client) Walk(root string) *fs.Walker {
|
||||||
|
return fs.WalkFS(root, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir reads the directory named by dirname and returns a list of
|
||||||
|
// directory entries.
|
||||||
|
func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
|
||||||
|
handle, err := c.opendir(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer c.close(handle) // this has to defer earlier than the lock below
|
||||||
|
var attrs []os.FileInfo
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
var done = false
|
||||||
|
for !done {
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err1 := c.sendRequest(sshFxpReaddirPacket{
|
||||||
|
Id: id,
|
||||||
|
Handle: handle,
|
||||||
|
})
|
||||||
|
if err1 != nil {
|
||||||
|
err = err1
|
||||||
|
done = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_NAME:
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return nil, &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
count, data := unmarshalUint32(data)
|
||||||
|
for i := uint32(0); i < count; i++ {
|
||||||
|
var filename string
|
||||||
|
filename, data = unmarshalString(data)
|
||||||
|
_, data = unmarshalString(data) // discard longname
|
||||||
|
var attr *FileStat
|
||||||
|
attr, data = unmarshalAttrs(data)
|
||||||
|
if filename == "." || filename == ".." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename)))
|
||||||
|
}
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
// TODO(dfc) scope warning!
|
||||||
|
err = eofOrErr(unmarshalStatus(id, data))
|
||||||
|
done = true
|
||||||
|
default:
|
||||||
|
return nil, unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == io.EOF {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return attrs, err
|
||||||
|
}
|
||||||
|
func (c *Client) opendir(path string) (string, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpOpendirPacket{
|
||||||
|
Id: id,
|
||||||
|
Path: path,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_HANDLE:
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return "", &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
handle, _ := unmarshalString(data)
|
||||||
|
return handle, nil
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return "", unmarshalStatus(id, data)
|
||||||
|
default:
|
||||||
|
return "", unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Lstat(p string) (os.FileInfo, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpLstatPacket{
|
||||||
|
Id: id,
|
||||||
|
Path: p,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_ATTRS:
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return nil, &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
attr, _ := unmarshalAttrs(data)
|
||||||
|
return fileInfoFromStat(attr, path.Base(p)), nil
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return nil, unmarshalStatus(id, data)
|
||||||
|
default:
|
||||||
|
return nil, unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLink reads the target of a symbolic link.
|
||||||
|
func (c *Client) ReadLink(p string) (string, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpReadlinkPacket{
|
||||||
|
Id: id,
|
||||||
|
Path: p,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_NAME:
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return "", &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
count, data := unmarshalUint32(data)
|
||||||
|
if count != 1 {
|
||||||
|
return "", unexpectedCount(1, count)
|
||||||
|
}
|
||||||
|
filename, _ := unmarshalString(data) // ignore dummy attributes
|
||||||
|
return filename, nil
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return "", unmarshalStatus(id, data)
|
||||||
|
default:
|
||||||
|
return "", unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setstat is a convience wrapper to allow for changing of various parts of the file descriptor.
|
||||||
|
func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpSetstatPacket{
|
||||||
|
Id: id,
|
||||||
|
Path: path,
|
||||||
|
Flags: flags,
|
||||||
|
Attrs: attrs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return okOrErr(unmarshalStatus(id, data))
|
||||||
|
default:
|
||||||
|
return unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chtimes changes the access and modification times of the named file.
|
||||||
|
func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error {
|
||||||
|
type times struct {
|
||||||
|
Atime uint32
|
||||||
|
Mtime uint32
|
||||||
|
}
|
||||||
|
attrs := times{uint32(atime.Unix()), uint32(mtime.Unix())}
|
||||||
|
return c.setstat(path, ssh_FILEXFER_ATTR_ACMODTIME, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chown changes the user and group owners of the named file.
|
||||||
|
func (c *Client) Chown(path string, uid, gid int) error {
|
||||||
|
type owner struct {
|
||||||
|
Uid uint32
|
||||||
|
Gid uint32
|
||||||
|
}
|
||||||
|
attrs := owner{uint32(uid), uint32(gid)}
|
||||||
|
return c.setstat(path, ssh_FILEXFER_ATTR_UIDGID, attrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmod changes the permissions of the named file.
|
||||||
|
func (c *Client) Chmod(path string, mode os.FileMode) error {
|
||||||
|
return c.setstat(path, ssh_FILEXFER_ATTR_PERMISSIONS, uint32(mode))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate sets the size of the named file. Although it may be safely assumed
|
||||||
|
// that if the size is less than its current size it will be truncated to fit,
|
||||||
|
// the SFTP protocol does not specify what behavior the server should do when setting
|
||||||
|
// size greater than the current size.
|
||||||
|
func (c *Client) Truncate(path string, size int64) error {
|
||||||
|
return c.setstat(path, ssh_FILEXFER_ATTR_SIZE, uint64(size))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens the named file for reading. If successful, methods on the
|
||||||
|
// returned file can be used for reading; the associated file descriptor
|
||||||
|
// has mode O_RDONLY.
|
||||||
|
func (c *Client) Open(path string) (*File, error) {
|
||||||
|
return c.open(path, flags(os.O_RDONLY))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenFile is the generalized open call; most users will use Open or
|
||||||
|
// Create instead. It opens the named file with specified flag (O_RDONLY
|
||||||
|
// etc.). If successful, methods on the returned File can be used for I/O.
|
||||||
|
func (c *Client) OpenFile(path string, f int) (*File, error) {
|
||||||
|
return c.open(path, flags(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) open(path string, pflags uint32) (*File, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpOpenPacket{
|
||||||
|
Id: id,
|
||||||
|
Path: path,
|
||||||
|
Pflags: pflags,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_HANDLE:
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return nil, &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
handle, _ := unmarshalString(data)
|
||||||
|
return &File{c: c, path: path, handle: handle}, nil
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return nil, unmarshalStatus(id, data)
|
||||||
|
default:
|
||||||
|
return nil, unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readAt reads len(buf) bytes from the remote file indicated by handle starting
|
||||||
|
// from offset.
|
||||||
|
func (c *Client) readAt(handle string, offset uint64, buf []byte) (uint32, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpReadPacket{
|
||||||
|
Id: id,
|
||||||
|
Handle: handle,
|
||||||
|
Offset: offset,
|
||||||
|
Len: uint32(len(buf)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_DATA:
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return 0, &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
l, data := unmarshalUint32(data)
|
||||||
|
n := copy(buf, data[:l])
|
||||||
|
return uint32(n), nil
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return 0, eofOrErr(unmarshalStatus(id, data))
|
||||||
|
default:
|
||||||
|
return 0, unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// close closes a handle handle previously returned in the response
|
||||||
|
// to SSH_FXP_OPEN or SSH_FXP_OPENDIR. The handle becomes invalid
|
||||||
|
// immediately after this request has been sent.
|
||||||
|
func (c *Client) close(handle string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpClosePacket{
|
||||||
|
Id: id,
|
||||||
|
Handle: handle,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return okOrErr(unmarshalStatus(id, data))
|
||||||
|
default:
|
||||||
|
return unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) fstat(handle string) (*FileStat, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpFstatPacket{
|
||||||
|
Id: id,
|
||||||
|
Handle: handle,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_ATTRS:
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return nil, &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
attr, _ := unmarshalAttrs(data)
|
||||||
|
return attr, nil
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return nil, unmarshalStatus(id, data)
|
||||||
|
default:
|
||||||
|
return nil, unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join joins any number of path elements into a single path, adding a
|
||||||
|
// separating slash if necessary. The result is Cleaned; in particular, all
|
||||||
|
// empty strings are ignored.
|
||||||
|
func (c *Client) Join(elem ...string) string { return path.Join(elem...) }
|
||||||
|
|
||||||
|
// Remove removes the specified file or directory. An error will be returned if no
|
||||||
|
// file or directory with the specified path exists, or if the specified directory
|
||||||
|
// is not empty.
|
||||||
|
func (c *Client) Remove(path string) error {
|
||||||
|
err := c.removeFile(path)
|
||||||
|
if status, ok := err.(*StatusError); ok && status.Code == ssh_FX_FAILURE {
|
||||||
|
err = c.removeDirectory(path)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) removeFile(path string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpRemovePacket{
|
||||||
|
Id: id,
|
||||||
|
Filename: path,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return okOrErr(unmarshalStatus(id, data))
|
||||||
|
default:
|
||||||
|
return unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) removeDirectory(path string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpRmdirPacket{
|
||||||
|
Id: id,
|
||||||
|
Path: path,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return okOrErr(unmarshalStatus(id, data))
|
||||||
|
default:
|
||||||
|
return unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename renames a file.
|
||||||
|
func (c *Client) Rename(oldname, newname string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpRenamePacket{
|
||||||
|
Id: id,
|
||||||
|
Oldpath: oldname,
|
||||||
|
Newpath: newname,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return okOrErr(unmarshalStatus(id, data))
|
||||||
|
default:
|
||||||
|
return unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendRequest(p encoding.BinaryMarshaler) (byte, []byte, error) {
|
||||||
|
if err := sendPacket(c.w, p); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return recvPacket(c.r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeAt writes len(buf) bytes from the remote file indicated by handle starting
|
||||||
|
// from offset.
|
||||||
|
func (c *Client) writeAt(handle string, offset uint64, buf []byte) (uint32, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpWritePacket{
|
||||||
|
Id: id,
|
||||||
|
Handle: handle,
|
||||||
|
Offset: offset,
|
||||||
|
Length: uint32(len(buf)),
|
||||||
|
Data: buf,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
if err := okOrErr(unmarshalStatus(id, data)); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return uint32(len(buf)), nil
|
||||||
|
default:
|
||||||
|
return 0, unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates the specified directory. An error will be returned if a file or
|
||||||
|
// directory with the specified path already exists, or if the directory's
|
||||||
|
// parent folder does not exist (the method cannot create complete paths).
|
||||||
|
func (c *Client) Mkdir(path string) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
id := c.nextId()
|
||||||
|
typ, data, err := c.sendRequest(sshFxpMkdirPacket{
|
||||||
|
Id: id,
|
||||||
|
Path: path,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch typ {
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return okOrErr(unmarshalStatus(id, data))
|
||||||
|
default:
|
||||||
|
return unimplementedPacketErr(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// File represents a remote file.
|
||||||
|
type File struct {
|
||||||
|
c *Client
|
||||||
|
path string
|
||||||
|
handle string
|
||||||
|
offset uint64 // current offset within remote file
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the File, rendering it unusable for I/O. It returns an
|
||||||
|
// error, if any.
|
||||||
|
func (f *File) Close() error {
|
||||||
|
return f.c.close(f.handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads up to len(b) bytes from the File. It returns the number of
|
||||||
|
// bytes read and an error, if any. EOF is signaled by a zero count with
|
||||||
|
// err set to io.EOF.
|
||||||
|
func (f *File) Read(b []byte) (int, error) {
|
||||||
|
var read int
|
||||||
|
for len(b) > 0 {
|
||||||
|
n, err := f.c.readAt(f.handle, f.offset, b[:min(len(b), maxWritePacket)])
|
||||||
|
f.offset += uint64(n)
|
||||||
|
read += int(n)
|
||||||
|
if err != nil {
|
||||||
|
return read, err
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
return read, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns the FileInfo structure describing file. If there is an
|
||||||
|
// error.
|
||||||
|
func (f *File) Stat() (os.FileInfo, error) {
|
||||||
|
fs, err := f.c.fstat(f.handle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return fileInfoFromStat(fs, path.Base(f.path)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clamp writes to less than 32k
|
||||||
|
const maxWritePacket = 1 << 15
|
||||||
|
|
||||||
|
// Write writes len(b) bytes to the File. It returns the number of bytes
|
||||||
|
// written and an error, if any. Write returns a non-nil error when n !=
|
||||||
|
// len(b).
|
||||||
|
func (f *File) Write(b []byte) (int, error) {
|
||||||
|
var written int
|
||||||
|
for len(b) > 0 {
|
||||||
|
n, err := f.c.writeAt(f.handle, f.offset, b[:min(len(b), maxWritePacket)])
|
||||||
|
f.offset += uint64(n)
|
||||||
|
written += int(n)
|
||||||
|
if err != nil {
|
||||||
|
return written, err
|
||||||
|
}
|
||||||
|
b = b[n:]
|
||||||
|
}
|
||||||
|
return written, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek implements io.Seeker by setting the client offset for the next Read or
|
||||||
|
// Write. It returns the next offset read. Seeking before or after the end of
|
||||||
|
// the file is undefined. Seeking relative to the end calls Stat.
|
||||||
|
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
switch whence {
|
||||||
|
case os.SEEK_SET:
|
||||||
|
f.offset = uint64(offset)
|
||||||
|
case os.SEEK_CUR:
|
||||||
|
f.offset = uint64(int64(f.offset) + offset)
|
||||||
|
case os.SEEK_END:
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return int64(f.offset), err
|
||||||
|
}
|
||||||
|
f.offset = uint64(fi.Size() + offset)
|
||||||
|
default:
|
||||||
|
return int64(f.offset), unimplementedSeekWhence(whence)
|
||||||
|
}
|
||||||
|
return int64(f.offset), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chown changes the uid/gid of the current file.
|
||||||
|
func (f *File) Chown(uid, gid int) error {
|
||||||
|
return f.c.Chown(f.path, uid, gid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chmod changes the permissions of the current file.
|
||||||
|
func (f *File) Chmod(mode os.FileMode) error {
|
||||||
|
return f.c.Chmod(f.path, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate sets the size of the current file. Although it may be safely assumed
|
||||||
|
// that if the size is less than its current size it will be truncated to fit,
|
||||||
|
// the SFTP protocol does not specify what behavior the server should do when setting
|
||||||
|
// size greater than the current size.
|
||||||
|
func (f *File) Truncate(size int64) error {
|
||||||
|
return f.c.Truncate(f.path, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func min(a, b int) int {
|
||||||
|
if a > b {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
// okOrErr returns nil if Err.Code is SSH_FX_OK, otherwise it returns the error.
|
||||||
|
func okOrErr(err error) error {
|
||||||
|
if err, ok := err.(*StatusError); ok && err.Code == ssh_FX_OK {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func eofOrErr(err error) error {
|
||||||
|
if err, ok := err.(*StatusError); ok && err.Code == ssh_FX_EOF {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalStatus(id uint32, data []byte) error {
|
||||||
|
sid, data := unmarshalUint32(data)
|
||||||
|
if sid != id {
|
||||||
|
return &unexpectedIdErr{id, sid}
|
||||||
|
}
|
||||||
|
code, data := unmarshalUint32(data)
|
||||||
|
msg, data := unmarshalString(data)
|
||||||
|
lang, _ := unmarshalString(data)
|
||||||
|
return &StatusError{
|
||||||
|
Code: code,
|
||||||
|
msg: msg,
|
||||||
|
lang: lang,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// flags converts the flags passed to OpenFile into ssh flags.
|
||||||
|
// Unsupported flags are ignored.
|
||||||
|
func flags(f int) uint32 {
|
||||||
|
var out uint32
|
||||||
|
switch f & os.O_WRONLY {
|
||||||
|
case os.O_WRONLY:
|
||||||
|
out |= ssh_FXF_WRITE
|
||||||
|
case os.O_RDONLY:
|
||||||
|
out |= ssh_FXF_READ
|
||||||
|
}
|
||||||
|
if f&os.O_RDWR == os.O_RDWR {
|
||||||
|
out |= ssh_FXF_READ | ssh_FXF_WRITE
|
||||||
|
}
|
||||||
|
if f&os.O_APPEND == os.O_APPEND {
|
||||||
|
out |= ssh_FXF_APPEND
|
||||||
|
}
|
||||||
|
if f&os.O_CREATE == os.O_CREATE {
|
||||||
|
out |= ssh_FXF_CREAT
|
||||||
|
}
|
||||||
|
if f&os.O_TRUNC == os.O_TRUNC {
|
||||||
|
out |= ssh_FXF_TRUNC
|
||||||
|
}
|
||||||
|
if f&os.O_EXCL == os.O_EXCL {
|
||||||
|
out |= ssh_FXF_EXCL
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
898
Godeps/_workspace/src/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
898
Godeps/_workspace/src/github.com/pkg/sftp/client_integration_test.go
generated
vendored
Normal file
@ -0,0 +1,898 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
// sftp integration tests
|
||||||
|
// enable with -integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
|
||||||
|
"github.com/kr/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
READONLY = true
|
||||||
|
READWRITE = false
|
||||||
|
|
||||||
|
debuglevel = "ERROR" // set to "DEBUG" for debugging
|
||||||
|
)
|
||||||
|
|
||||||
|
var testIntegration = flag.Bool("integration", false, "perform integration tests against sftp server process")
|
||||||
|
var testSftp = flag.String("sftp", "/usr/lib/openssh/sftp-server", "location of the sftp server binary")
|
||||||
|
|
||||||
|
// testClient returns a *Client connected to a localy running sftp-server
|
||||||
|
// the *exec.Cmd returned must be defer Wait'd.
|
||||||
|
func testClient(t testing.TB, readonly bool) (*Client, *exec.Cmd) {
|
||||||
|
if !*testIntegration {
|
||||||
|
t.Skip("skipping intergration test")
|
||||||
|
}
|
||||||
|
cmd := exec.Command(*testSftp, "-e", "-R", "-l", debuglevel) // log to stderr, read only
|
||||||
|
if !readonly {
|
||||||
|
cmd = exec.Command(*testSftp, "-e", "-l", debuglevel) // log to stderr
|
||||||
|
}
|
||||||
|
cmd.Stderr = os.Stdout
|
||||||
|
pw, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pr, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
t.Skipf("could not start sftp-server process: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sftp, err := NewClientPipe(pr, pw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sftp.sendInit(); err != nil {
|
||||||
|
defer cmd.Wait()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := sftp.recvVersion(); err != nil {
|
||||||
|
defer cmd.Wait()
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return sftp, cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClient(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
|
||||||
|
if err := sftp.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientLstat(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
want, err := os.Lstat(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := sftp.Lstat(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sameFile(want, got) {
|
||||||
|
t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientLstatMissing(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
os.Remove(f.Name())
|
||||||
|
|
||||||
|
_, err = sftp.Lstat(f.Name())
|
||||||
|
if err1, ok := err.(*StatusError); !ok || err1.Code != ssh_FX_NO_SUCH_FILE {
|
||||||
|
t.Fatalf("Lstat: want: %v, got %#v", ssh_FX_NO_SUCH_FILE, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientMkdir(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sub := path.Join(dir, "mkdir1")
|
||||||
|
if err := sftp.Mkdir(sub); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(sub); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientOpen(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
got, err := sftp.Open(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := got.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const seekBytes = 128 * 1024
|
||||||
|
|
||||||
|
type seek struct {
|
||||||
|
offset int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s seek) Generate(r *rand.Rand, _ int) reflect.Value {
|
||||||
|
s.offset = int64(r.Int31n(seekBytes))
|
||||||
|
return reflect.ValueOf(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s seek) set(t *testing.T, r io.ReadSeeker) {
|
||||||
|
if _, err := r.Seek(s.offset, os.SEEK_SET); err != nil {
|
||||||
|
t.Fatalf("error while seeking with %+v: %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s seek) current(t *testing.T, r io.ReadSeeker) {
|
||||||
|
const mid = seekBytes / 2
|
||||||
|
|
||||||
|
skip := s.offset / 2
|
||||||
|
if s.offset > mid {
|
||||||
|
skip = -skip
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := r.Seek(mid, os.SEEK_SET); err != nil {
|
||||||
|
t.Fatalf("error seeking to midpoint with %+v: %v", s, err)
|
||||||
|
}
|
||||||
|
if _, err := r.Seek(skip, os.SEEK_CUR); err != nil {
|
||||||
|
t.Fatalf("error seeking from %d with %+v: %v", mid, s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s seek) end(t *testing.T, r io.ReadSeeker) {
|
||||||
|
if _, err := r.Seek(-s.offset, os.SEEK_END); err != nil {
|
||||||
|
t.Fatalf("error seeking from end with %+v: %v", s, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientSeek(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
fOS, err := ioutil.TempFile("", "seek-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer fOS.Close()
|
||||||
|
|
||||||
|
fSFTP, err := sftp.Open(fOS.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer fSFTP.Close()
|
||||||
|
|
||||||
|
writeN(t, fOS, seekBytes)
|
||||||
|
|
||||||
|
if err := quick.CheckEqual(
|
||||||
|
func(s seek) (string, int64) { s.set(t, fOS); return readHash(t, fOS) },
|
||||||
|
func(s seek) (string, int64) { s.set(t, fSFTP); return readHash(t, fSFTP) },
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("Seek: expected equal absolute seeks: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := quick.CheckEqual(
|
||||||
|
func(s seek) (string, int64) { s.current(t, fOS); return readHash(t, fOS) },
|
||||||
|
func(s seek) (string, int64) { s.current(t, fSFTP); return readHash(t, fSFTP) },
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("Seek: expected equal seeks from middle: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := quick.CheckEqual(
|
||||||
|
func(s seek) (string, int64) { s.end(t, fOS); return readHash(t, fOS) },
|
||||||
|
func(s seek) (string, int64) { s.end(t, fSFTP); return readHash(t, fSFTP) },
|
||||||
|
nil,
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("Seek: expected equal seeks from end: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientCreate(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
f2, err := sftp.Create(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientAppend(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
f2, err := sftp.OpenFile(f.Name(), os.O_RDWR|os.O_APPEND)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientCreateFailed(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
f2, err := sftp.Create(f.Name())
|
||||||
|
if err1, ok := err.(*StatusError); !ok || err1.Code != ssh_FX_PERMISSION_DENIED {
|
||||||
|
t.Fatalf("Create: want: %v, got %#v", ssh_FX_PERMISSION_DENIED, err)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
f2.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientFileStat(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
want, err := os.Lstat(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
f2, err := sftp.Open(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := f2.Stat()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sameFile(want, got) {
|
||||||
|
t.Fatalf("Lstat(%q): want %#v, got %#v", f.Name(), want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientRemove(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := sftp.Remove(f.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientRemoveDir(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := sftp.Remove(dir); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(dir); !os.IsNotExist(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientRemoveFailed(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := sftp.Remove(f.Name()); err == nil {
|
||||||
|
t.Fatalf("Remove(%v): want: permission denied, got %v", f.Name(), err)
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(f.Name()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientRename(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f2 := f.Name() + ".new"
|
||||||
|
if err := sftp.Rename(f.Name(), f2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(f.Name()); !os.IsNotExist(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := os.Lstat(f2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientReadLine(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f2 := f.Name() + ".sym"
|
||||||
|
if err := os.Symlink(f.Name(), f2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := sftp.ReadLink(f2); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameFile(want, got os.FileInfo) bool {
|
||||||
|
return want.Name() == got.Name() &&
|
||||||
|
want.Size() == got.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientReadTests = []struct {
|
||||||
|
n int64
|
||||||
|
}{
|
||||||
|
{0},
|
||||||
|
{1},
|
||||||
|
{1000},
|
||||||
|
{1024},
|
||||||
|
{1025},
|
||||||
|
{2048},
|
||||||
|
{4096},
|
||||||
|
{1 << 12},
|
||||||
|
{1 << 13},
|
||||||
|
{1 << 14},
|
||||||
|
{1 << 15},
|
||||||
|
{1 << 16},
|
||||||
|
{1 << 17},
|
||||||
|
{1 << 18},
|
||||||
|
{1 << 19},
|
||||||
|
{1 << 20},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientRead(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
d, err := ioutil.TempDir("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(d)
|
||||||
|
|
||||||
|
for _, tt := range clientReadTests {
|
||||||
|
f, err := ioutil.TempFile(d, "read-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
hash := writeN(t, f, tt.n)
|
||||||
|
f2, err := sftp.Open(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
hash2, n := readHash(t, f2)
|
||||||
|
if hash != hash2 || tt.n != n {
|
||||||
|
t.Errorf("Read: hash: want: %q, got %q, read: want: %v, got %v", hash, hash2, tt.n, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// readHash reads r until EOF returning the number of bytes read
|
||||||
|
// and the hash of the contents.
|
||||||
|
func readHash(t *testing.T, r io.Reader) (string, int64) {
|
||||||
|
h := sha1.New()
|
||||||
|
tr := io.TeeReader(r, h)
|
||||||
|
read, err := io.Copy(ioutil.Discard, tr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return string(h.Sum(nil)), read
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeN writes n bytes of random data to w and returns the
|
||||||
|
// hash of that data.
|
||||||
|
func writeN(t *testing.T, w io.Writer, n int64) string {
|
||||||
|
rand, err := os.Open("/dev/urandom")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rand.Close()
|
||||||
|
|
||||||
|
h := sha1.New()
|
||||||
|
|
||||||
|
mw := io.MultiWriter(w, h)
|
||||||
|
|
||||||
|
written, err := io.CopyN(mw, rand, n)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if written != n {
|
||||||
|
t.Fatalf("CopyN(%v): wrote: %v", n, written)
|
||||||
|
}
|
||||||
|
return string(h.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
var clientWriteTests = []struct {
|
||||||
|
n int
|
||||||
|
total int64 // cumulative file size
|
||||||
|
}{
|
||||||
|
{0, 0},
|
||||||
|
{1, 1},
|
||||||
|
{0, 1},
|
||||||
|
{999, 1000},
|
||||||
|
{24, 1024},
|
||||||
|
{1023, 2047},
|
||||||
|
{2048, 4095},
|
||||||
|
{1 << 12, 8191},
|
||||||
|
{1 << 13, 16383},
|
||||||
|
{1 << 14, 32767},
|
||||||
|
{1 << 15, 65535},
|
||||||
|
{1 << 16, 131071},
|
||||||
|
{1 << 17, 262143},
|
||||||
|
{1 << 18, 524287},
|
||||||
|
{1 << 19, 1048575},
|
||||||
|
{1 << 20, 2097151},
|
||||||
|
{1 << 21, 4194303},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientWrite(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READWRITE)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
d, err := ioutil.TempDir("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(d)
|
||||||
|
|
||||||
|
f := path.Join(d, "writeTest")
|
||||||
|
w, err := sftp.Create(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
for _, tt := range clientWriteTests {
|
||||||
|
got, err := w.Write(make([]byte, tt.n))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != tt.n {
|
||||||
|
t.Errorf("Write(%v): wrote: want: %v, got %v", tt.n, tt.n, got)
|
||||||
|
}
|
||||||
|
fi, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if total := fi.Size(); total != tt.total {
|
||||||
|
t.Errorf("Write(%v): size: want: %v, got %v", tt.n, tt.total, total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// taken from github.com/kr/fs/walk_test.go
|
||||||
|
|
||||||
|
type PathTest struct {
|
||||||
|
path, result string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
name string
|
||||||
|
entries []*Node // nil if the entry is a file
|
||||||
|
mark int
|
||||||
|
}
|
||||||
|
|
||||||
|
var tree = &Node{
|
||||||
|
"testdata",
|
||||||
|
[]*Node{
|
||||||
|
{"a", nil, 0},
|
||||||
|
{"b", []*Node{}, 0},
|
||||||
|
{"c", nil, 0},
|
||||||
|
{
|
||||||
|
"d",
|
||||||
|
[]*Node{
|
||||||
|
{"x", nil, 0},
|
||||||
|
{"y", []*Node{}, 0},
|
||||||
|
{
|
||||||
|
"z",
|
||||||
|
[]*Node{
|
||||||
|
{"u", nil, 0},
|
||||||
|
{"v", nil, 0},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
}
|
||||||
|
|
||||||
|
func walkTree(n *Node, path string, f func(path string, n *Node)) {
|
||||||
|
f(path, n)
|
||||||
|
for _, e := range n.entries {
|
||||||
|
walkTree(e, filepath.Join(path, e.name), f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTree(t *testing.T) {
|
||||||
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||||
|
if n.entries == nil {
|
||||||
|
fd, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("makeTree: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fd.Close()
|
||||||
|
} else {
|
||||||
|
os.Mkdir(path, 0770)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func markTree(n *Node) { walkTree(n, "", func(path string, n *Node) { n.mark++ }) }
|
||||||
|
|
||||||
|
func checkMarks(t *testing.T, report bool) {
|
||||||
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||||
|
if n.mark != 1 && report {
|
||||||
|
t.Errorf("node %s mark = %d; expected 1", path, n.mark)
|
||||||
|
}
|
||||||
|
n.mark = 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes that each node name is unique. Good enough for a test.
|
||||||
|
// If clear is true, any incoming error is cleared before return. The errors
|
||||||
|
// are always accumulated, though.
|
||||||
|
func mark(path string, info os.FileInfo, err error, errors *[]error, clear bool) error {
|
||||||
|
if err != nil {
|
||||||
|
*errors = append(*errors, err)
|
||||||
|
if clear {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
name := info.Name()
|
||||||
|
walkTree(tree, tree.name, func(path string, n *Node) {
|
||||||
|
if n.name == name {
|
||||||
|
n.mark++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientWalk(t *testing.T) {
|
||||||
|
sftp, cmd := testClient(t, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
makeTree(t)
|
||||||
|
errors := make([]error, 0, 10)
|
||||||
|
clear := true
|
||||||
|
markFn := func(walker *fs.Walker) (err error) {
|
||||||
|
for walker.Step() {
|
||||||
|
err = mark(walker.Path(), walker.Stat(), walker.Err(), &errors, clear)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Expect no errors.
|
||||||
|
err := markFn(sftp.Walk(tree.name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("no error expected, found: %s", err)
|
||||||
|
}
|
||||||
|
if len(errors) != 0 {
|
||||||
|
t.Fatalf("unexpected errors: %s", errors)
|
||||||
|
}
|
||||||
|
checkMarks(t, true)
|
||||||
|
errors = errors[0:0]
|
||||||
|
|
||||||
|
// Test permission errors. Only possible if we're not root
|
||||||
|
// and only on some file systems (AFS, FAT). To avoid errors during
|
||||||
|
// all.bash on those file systems, skip during go test -short.
|
||||||
|
if os.Getuid() > 0 && !testing.Short() {
|
||||||
|
// introduce 2 errors: chmod top-level directories to 0
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0)
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0)
|
||||||
|
|
||||||
|
// 3) capture errors, expect two.
|
||||||
|
// mark respective subtrees manually
|
||||||
|
markTree(tree.entries[1])
|
||||||
|
markTree(tree.entries[3])
|
||||||
|
// correct double-marking of directory itself
|
||||||
|
tree.entries[1].mark--
|
||||||
|
tree.entries[3].mark--
|
||||||
|
err := markFn(sftp.Walk(tree.name))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error return from Walk, got %s", err)
|
||||||
|
}
|
||||||
|
if len(errors) != 2 {
|
||||||
|
t.Errorf("expected 2 errors, got %d: %s", len(errors), errors)
|
||||||
|
}
|
||||||
|
// the inaccessible subtrees were marked manually
|
||||||
|
checkMarks(t, true)
|
||||||
|
errors = errors[0:0]
|
||||||
|
|
||||||
|
// 4) capture errors, stop after first error.
|
||||||
|
// mark respective subtrees manually
|
||||||
|
markTree(tree.entries[1])
|
||||||
|
markTree(tree.entries[3])
|
||||||
|
// correct double-marking of directory itself
|
||||||
|
tree.entries[1].mark--
|
||||||
|
tree.entries[3].mark--
|
||||||
|
clear = false // error will stop processing
|
||||||
|
err = markFn(sftp.Walk(tree.name))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error return from Walk")
|
||||||
|
}
|
||||||
|
if len(errors) != 1 {
|
||||||
|
t.Errorf("expected 1 error, got %d: %s", len(errors), errors)
|
||||||
|
}
|
||||||
|
// the inaccessible subtrees were marked manually
|
||||||
|
checkMarks(t, false)
|
||||||
|
errors = errors[0:0]
|
||||||
|
|
||||||
|
// restore permissions
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[1].name), 0770)
|
||||||
|
os.Chmod(filepath.Join(tree.name, tree.entries[3].name), 0770)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup
|
||||||
|
if err := os.RemoveAll(tree.name); err != nil {
|
||||||
|
t.Errorf("removeTree: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkRead(b *testing.B, bufsize int) {
|
||||||
|
size := 10*1024*1024 + 123 // ~10MiB
|
||||||
|
|
||||||
|
// open sftp client
|
||||||
|
sftp, cmd := testClient(b, READONLY)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
buf := make([]byte, bufsize)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.SetBytes(int64(size))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
f2, err := sftp.Open("/dev/zero")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
|
||||||
|
for offset < size {
|
||||||
|
n, err := io.ReadFull(f2, buf)
|
||||||
|
offset += n
|
||||||
|
if err == io.ErrUnexpectedEOF && offset != size {
|
||||||
|
b.Fatalf("read too few bytes! want: %d, got: %d", size, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRead1k(b *testing.B) {
|
||||||
|
benchmarkRead(b, 1*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRead16k(b *testing.B) {
|
||||||
|
benchmarkRead(b, 16*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRead32k(b *testing.B) {
|
||||||
|
benchmarkRead(b, 32*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRead128k(b *testing.B) {
|
||||||
|
benchmarkRead(b, 128*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRead512k(b *testing.B) {
|
||||||
|
benchmarkRead(b, 512*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRead1MiB(b *testing.B) {
|
||||||
|
benchmarkRead(b, 1024*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRead4MiB(b *testing.B) {
|
||||||
|
benchmarkRead(b, 4*1024*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkWrite(b *testing.B, bufsize int) {
|
||||||
|
size := 10*1024*1024 + 123 // ~10MiB
|
||||||
|
|
||||||
|
// open sftp client
|
||||||
|
sftp, cmd := testClient(b, false)
|
||||||
|
defer cmd.Wait()
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
data := make([]byte, size)
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.SetBytes(int64(size))
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
f, err := ioutil.TempFile("", "sftptest")
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
f2, err := sftp.Create(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f2.Close()
|
||||||
|
|
||||||
|
for offset < size {
|
||||||
|
n, err := f2.Write(data[offset:min(len(data), offset+bufsize)])
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset+n < size && n != bufsize {
|
||||||
|
b.Fatalf("wrote too few bytes! want: %d, got: %d", size, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += n
|
||||||
|
}
|
||||||
|
|
||||||
|
f2.Close()
|
||||||
|
|
||||||
|
fi, err := os.Stat(f.Name())
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Size() != int64(size) {
|
||||||
|
b.Fatalf("wrong file size: want %d, got %d", size, fi.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Remove(f.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWrite1k(b *testing.B) {
|
||||||
|
benchmarkWrite(b, 1*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWrite16k(b *testing.B) {
|
||||||
|
benchmarkWrite(b, 16*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWrite32k(b *testing.B) {
|
||||||
|
benchmarkWrite(b, 32*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWrite128k(b *testing.B) {
|
||||||
|
benchmarkWrite(b, 128*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWrite512k(b *testing.B) {
|
||||||
|
benchmarkWrite(b, 512*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWrite1MiB(b *testing.B) {
|
||||||
|
benchmarkWrite(b, 1024*1024)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWrite4MiB(b *testing.B) {
|
||||||
|
benchmarkWrite(b, 4*1024*1024)
|
||||||
|
}
|
75
Godeps/_workspace/src/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
75
Godeps/_workspace/src/github.com/pkg/sftp/client_test.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kr/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// assert that *Client implements fs.FileSystem
|
||||||
|
var _ fs.FileSystem = new(Client)
|
||||||
|
|
||||||
|
// assert that *File implements io.ReadWriteCloser
|
||||||
|
var _ io.ReadWriteCloser = new(File)
|
||||||
|
|
||||||
|
var ok = &StatusError{Code: ssh_FX_OK}
|
||||||
|
var eof = &StatusError{Code: ssh_FX_EOF}
|
||||||
|
var fail = &StatusError{Code: ssh_FX_FAILURE}
|
||||||
|
|
||||||
|
var eofOrErrTests = []struct {
|
||||||
|
err, want error
|
||||||
|
}{
|
||||||
|
{nil, nil},
|
||||||
|
{eof, io.EOF},
|
||||||
|
{ok, ok},
|
||||||
|
{io.EOF, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEofOrErr(t *testing.T) {
|
||||||
|
for _, tt := range eofOrErrTests {
|
||||||
|
got := eofOrErr(tt.err)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("eofOrErr(%#v): want: %#v, got: %#v", tt.err, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var okOrErrTests = []struct {
|
||||||
|
err, want error
|
||||||
|
}{
|
||||||
|
{nil, nil},
|
||||||
|
{eof, eof},
|
||||||
|
{ok, nil},
|
||||||
|
{io.EOF, io.EOF},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOkOrErr(t *testing.T) {
|
||||||
|
for _, tt := range okOrErrTests {
|
||||||
|
got := okOrErr(tt.err)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("okOrErr(%#v): want: %#v, got: %#v", tt.err, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var flagsTests = []struct {
|
||||||
|
flags int
|
||||||
|
want uint32
|
||||||
|
}{
|
||||||
|
{os.O_RDONLY, ssh_FXF_READ},
|
||||||
|
{os.O_WRONLY, ssh_FXF_WRITE},
|
||||||
|
{os.O_RDWR, ssh_FXF_READ | ssh_FXF_WRITE},
|
||||||
|
{os.O_RDWR | os.O_CREATE | os.O_TRUNC, ssh_FXF_READ | ssh_FXF_WRITE | ssh_FXF_CREAT | ssh_FXF_TRUNC},
|
||||||
|
{os.O_WRONLY | os.O_APPEND, ssh_FXF_WRITE | ssh_FXF_APPEND},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFlags(t *testing.T) {
|
||||||
|
for i, tt := range flagsTests {
|
||||||
|
got := flags(tt.flags)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("test %v: flags(%x): want: %x, got: %x", i, tt.flags, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
Godeps/_workspace/src/github.com/pkg/sftp/debug.go
generated
vendored
Normal file
9
Godeps/_workspace/src/github.com/pkg/sftp/debug.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build debug
|
||||||
|
|
||||||
|
package sftp
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
func debug(fmt string, args ...interface{}) {
|
||||||
|
log.Printf(fmt, args...)
|
||||||
|
}
|
91
Godeps/_workspace/src/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
91
Godeps/_workspace/src/github.com/pkg/sftp/example_test.go
generated
vendored
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package sftp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example(conn *ssh.Client) {
|
||||||
|
// open an SFTP session over an existing ssh connection.
|
||||||
|
sftp, err := sftp.NewClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer sftp.Close()
|
||||||
|
|
||||||
|
// walk a directory
|
||||||
|
w := sftp.Walk("/home/user")
|
||||||
|
for w.Step() {
|
||||||
|
if w.Err() != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Println(w.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
// leave your mark
|
||||||
|
f, err := sftp.Create("hello.txt")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := f.Write([]byte("Hello world!")); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check it's there
|
||||||
|
fi, err := sftp.Lstat("hello.txt")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println(fi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewClientPipe() {
|
||||||
|
// Connect to a remote host and request the sftp subsystem via the 'ssh'
|
||||||
|
// command. This assumes that passwordless login is correctly configured.
|
||||||
|
cmd := exec.Command("ssh", "example.com", "-s", "sftp")
|
||||||
|
|
||||||
|
// send errors from ssh to stderr
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
// get stdin and stdout
|
||||||
|
wr, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
rd, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// start the process
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer cmd.Wait()
|
||||||
|
|
||||||
|
// open the SFTP session
|
||||||
|
client, err := sftp.NewClientPipe(rd, wr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// read a directory
|
||||||
|
list, err := client.ReadDir("/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print contents
|
||||||
|
for _, item := range list {
|
||||||
|
fmt.Println(item.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
// close the connection
|
||||||
|
client.Close()
|
||||||
|
}
|
76
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
76
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-read-benchmark/main.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// buffered-read-benchmark benchmarks the peformance of reading
|
||||||
|
// from /dev/zero on the server to a []byte on the client via io.Copy.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||||
|
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||||
|
PORT = flag.Int("port", 22, "ssh server port")
|
||||||
|
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var auths []ssh.AuthMethod
|
||||||
|
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||||
|
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||||
|
|
||||||
|
}
|
||||||
|
if *PASS != "" {
|
||||||
|
auths = append(auths, ssh.Password(*PASS))
|
||||||
|
}
|
||||||
|
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
User: *USER,
|
||||||
|
Auth: auths,
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||||
|
conn, err := ssh.Dial("tcp", addr, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
c, err := sftp.NewClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
r, err := c.Open("/dev/zero")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
const size = 1e9
|
||||||
|
|
||||||
|
log.Printf("reading %v bytes", size)
|
||||||
|
t1 := time.Now()
|
||||||
|
n, err := io.ReadFull(r, make([]byte, size))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != size {
|
||||||
|
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||||
|
}
|
||||||
|
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||||
|
}
|
82
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
82
Godeps/_workspace/src/github.com/pkg/sftp/examples/buffered-write-benchmark/main.go
generated
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// buffered-write-benchmark benchmarks the peformance of writing
|
||||||
|
// a single large []byte on the client to /dev/null on the server via io.Copy.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||||
|
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||||
|
PORT = flag.Int("port", 22, "ssh server port")
|
||||||
|
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var auths []ssh.AuthMethod
|
||||||
|
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||||
|
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||||
|
|
||||||
|
}
|
||||||
|
if *PASS != "" {
|
||||||
|
auths = append(auths, ssh.Password(*PASS))
|
||||||
|
}
|
||||||
|
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
User: *USER,
|
||||||
|
Auth: auths,
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||||
|
conn, err := ssh.Dial("tcp", addr, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
c, err := sftp.NewClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
f, err := os.Open("/dev/zero")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const size = 1e9
|
||||||
|
|
||||||
|
log.Printf("writing %v bytes", size)
|
||||||
|
t1 := time.Now()
|
||||||
|
n, err := w.Write(make([]byte, size))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != size {
|
||||||
|
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||||
|
}
|
||||||
|
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||||
|
}
|
147
Godeps/_workspace/src/github.com/pkg/sftp/examples/gsftp/main.go
generated
vendored
Normal file
147
Godeps/_workspace/src/github.com/pkg/sftp/examples/gsftp/main.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// gsftp implements a simple sftp client.
|
||||||
|
//
|
||||||
|
// gsftp understands the following commands:
|
||||||
|
//
|
||||||
|
// List a directory (and its subdirectories)
|
||||||
|
// gsftp ls DIR
|
||||||
|
//
|
||||||
|
// Fetch a remote file
|
||||||
|
// gsftp fetch FILE
|
||||||
|
//
|
||||||
|
// Put the contents of stdin to a remote file
|
||||||
|
// cat LOCALFILE | gsftp put REMOTEFILE
|
||||||
|
//
|
||||||
|
// Print the details of a remote file
|
||||||
|
// gsftp stat FILE
|
||||||
|
//
|
||||||
|
// Remove a remote file
|
||||||
|
// gsftp rm FILE
|
||||||
|
//
|
||||||
|
// Rename a file
|
||||||
|
// gsftp mv OLD NEW
|
||||||
|
//
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||||
|
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||||
|
PORT = flag.Int("port", 22, "ssh server port")
|
||||||
|
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
if len(flag.Args()) < 1 {
|
||||||
|
log.Fatal("subcommand required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var auths []ssh.AuthMethod
|
||||||
|
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||||
|
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||||
|
|
||||||
|
}
|
||||||
|
if *PASS != "" {
|
||||||
|
auths = append(auths, ssh.Password(*PASS))
|
||||||
|
}
|
||||||
|
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
User: *USER,
|
||||||
|
Auth: auths,
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||||
|
conn, err := ssh.Dial("tcp", addr, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client, err := sftp.NewClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
switch cmd := flag.Args()[0]; cmd {
|
||||||
|
case "ls":
|
||||||
|
if len(flag.Args()) < 2 {
|
||||||
|
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||||
|
}
|
||||||
|
walker := client.Walk(flag.Args()[1])
|
||||||
|
for walker.Step() {
|
||||||
|
if err := walker.Err(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Println(walker.Path())
|
||||||
|
}
|
||||||
|
case "fetch":
|
||||||
|
if len(flag.Args()) < 2 {
|
||||||
|
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||||
|
}
|
||||||
|
f, err := client.Open(flag.Args()[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := io.Copy(os.Stdout, f); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "put":
|
||||||
|
if len(flag.Args()) < 2 {
|
||||||
|
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||||
|
}
|
||||||
|
f, err := client.Create(flag.Args()[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
if _, err := io.Copy(f, os.Stdin); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
case "stat":
|
||||||
|
if len(flag.Args()) < 2 {
|
||||||
|
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||||
|
}
|
||||||
|
f, err := client.Open(flag.Args()[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
fi, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to stat file: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%s %d %v\n", fi.Name(), fi.Size(), fi.Mode())
|
||||||
|
case "rm":
|
||||||
|
if len(flag.Args()) < 2 {
|
||||||
|
log.Fatalf("%s %s: remote path required", cmd, os.Args[0])
|
||||||
|
}
|
||||||
|
if err := client.Remove(flag.Args()[1]); err != nil {
|
||||||
|
log.Fatalf("unable to remove file: %v", err)
|
||||||
|
}
|
||||||
|
case "mv":
|
||||||
|
if len(flag.Args()) < 3 {
|
||||||
|
log.Fatalf("%s %s: old and new name required", cmd, os.Args[0])
|
||||||
|
}
|
||||||
|
if err := client.Rename(flag.Args()[1], flag.Args()[2]); err != nil {
|
||||||
|
log.Fatalf("unable to rename file: %v", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Fatalf("unknown subcommand: %v", cmd)
|
||||||
|
}
|
||||||
|
}
|
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-read-benchmark/main.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// streaming-read-benchmark benchmarks the peformance of reading
|
||||||
|
// from /dev/zero on the server to /dev/null on the client via io.Copy.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||||
|
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||||
|
PORT = flag.Int("port", 22, "ssh server port")
|
||||||
|
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var auths []ssh.AuthMethod
|
||||||
|
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||||
|
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||||
|
|
||||||
|
}
|
||||||
|
if *PASS != "" {
|
||||||
|
auths = append(auths, ssh.Password(*PASS))
|
||||||
|
}
|
||||||
|
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
User: *USER,
|
||||||
|
Auth: auths,
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||||
|
conn, err := ssh.Dial("tcp", addr, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
c, err := sftp.NewClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
r, err := c.Open("/dev/zero")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
w, err := os.OpenFile("/dev/null", syscall.O_WRONLY, 0600)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
const size int64 = 1e9
|
||||||
|
|
||||||
|
log.Printf("reading %v bytes", size)
|
||||||
|
t1 := time.Now()
|
||||||
|
n, err := io.Copy(w, io.LimitReader(r, size))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != size {
|
||||||
|
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||||
|
}
|
||||||
|
log.Printf("read %v bytes in %s", size, time.Since(t1))
|
||||||
|
}
|
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
83
Godeps/_workspace/src/github.com/pkg/sftp/examples/streaming-write-benchmark/main.go
generated
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// streaming-write-benchmark benchmarks the peformance of writing
|
||||||
|
// from /dev/zero on the client to /dev/null on the server via io.Copy.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
|
||||||
|
"github.com/pkg/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
USER = flag.String("user", os.Getenv("USER"), "ssh username")
|
||||||
|
HOST = flag.String("host", "localhost", "ssh server hostname")
|
||||||
|
PORT = flag.Int("port", 22, "ssh server port")
|
||||||
|
PASS = flag.String("pass", os.Getenv("SOCKSIE_SSH_PASSWORD"), "ssh password")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.Parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var auths []ssh.AuthMethod
|
||||||
|
if aconn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")); err == nil {
|
||||||
|
auths = append(auths, ssh.PublicKeysCallback(agent.NewClient(aconn).Signers))
|
||||||
|
|
||||||
|
}
|
||||||
|
if *PASS != "" {
|
||||||
|
auths = append(auths, ssh.Password(*PASS))
|
||||||
|
}
|
||||||
|
|
||||||
|
config := ssh.ClientConfig{
|
||||||
|
User: *USER,
|
||||||
|
Auth: auths,
|
||||||
|
}
|
||||||
|
addr := fmt.Sprintf("%s:%d", *HOST, *PORT)
|
||||||
|
conn, err := ssh.Dial("tcp", addr, &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to connect to [%s]: %v", addr, err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
c, err := sftp.NewClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("unable to start sftp subsytem: %v", err)
|
||||||
|
}
|
||||||
|
defer c.Close()
|
||||||
|
|
||||||
|
w, err := c.OpenFile("/dev/null", syscall.O_WRONLY)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
f, err := os.Open("/dev/zero")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
const size int64 = 1e9
|
||||||
|
|
||||||
|
log.Printf("writing %v bytes", size)
|
||||||
|
t1 := time.Now()
|
||||||
|
n, err := io.Copy(w, io.LimitReader(f, size))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != size {
|
||||||
|
log.Fatalf("copy: expected %v bytes, got %d", size, n)
|
||||||
|
}
|
||||||
|
log.Printf("wrote %v bytes in %s", size, time.Since(t1))
|
||||||
|
}
|
331
Godeps/_workspace/src/github.com/pkg/sftp/packet.go
generated
vendored
Normal file
331
Godeps/_workspace/src/github.com/pkg/sftp/packet.go
generated
vendored
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func marshalUint32(b []byte, v uint32) []byte {
|
||||||
|
return append(b, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalUint64(b []byte, v uint64) []byte {
|
||||||
|
return marshalUint32(marshalUint32(b, uint32(v>>32)), uint32(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalString(b []byte, v string) []byte {
|
||||||
|
return append(marshalUint32(b, uint32(len(v))), v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshal(b []byte, v interface{}) []byte {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case uint8:
|
||||||
|
return append(b, v)
|
||||||
|
case uint32:
|
||||||
|
return marshalUint32(b, v)
|
||||||
|
case uint64:
|
||||||
|
return marshalUint64(b, v)
|
||||||
|
case string:
|
||||||
|
return marshalString(b, v)
|
||||||
|
default:
|
||||||
|
switch d := reflect.ValueOf(v); d.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
for i, n := 0, d.NumField(); i < n; i++ {
|
||||||
|
b = append(marshal(b, d.Field(i).Interface()))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
case reflect.Slice:
|
||||||
|
for i, n := 0, d.Len(); i < n; i++ {
|
||||||
|
b = append(marshal(b, d.Index(i).Interface()))
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("marshal(%#v): cannot handle type %T", v, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalUint32(b []byte) (uint32, []byte) {
|
||||||
|
v := uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
|
||||||
|
return v, b[4:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalUint64(b []byte) (uint64, []byte) {
|
||||||
|
h, b := unmarshalUint32(b)
|
||||||
|
l, b := unmarshalUint32(b)
|
||||||
|
return uint64(h)<<32 | uint64(l), b
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalString(b []byte) (string, []byte) {
|
||||||
|
n, b := unmarshalUint32(b)
|
||||||
|
return string(b[:n]), b[n:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendPacket marshals p according to RFC 4234.
|
||||||
|
|
||||||
|
func sendPacket(w io.Writer, m encoding.BinaryMarshaler) error {
|
||||||
|
bb, err := m.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("marshal2(%#v): binary marshaller failed", err)
|
||||||
|
}
|
||||||
|
l := uint32(len(bb))
|
||||||
|
hdr := []byte{byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l)}
|
||||||
|
debug("send packet %T, len: %v", m, l)
|
||||||
|
_, err = w.Write(hdr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(bb)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func recvPacket(r io.Reader) (uint8, []byte, error) {
|
||||||
|
var b = []byte{0, 0, 0, 0}
|
||||||
|
if _, err := io.ReadFull(r, b); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
l, _ := unmarshalUint32(b)
|
||||||
|
b = make([]byte, l)
|
||||||
|
if _, err := io.ReadFull(r, b); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
return b[0], b[1:], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here starts the definition of packets along with their MarshalBinary
|
||||||
|
// implementations.
|
||||||
|
// Manually writing the marshalling logic wins us a lot of time and
|
||||||
|
// allocation.
|
||||||
|
|
||||||
|
type sshFxInitPacket struct {
|
||||||
|
Version uint32
|
||||||
|
Extensions []struct {
|
||||||
|
Name, Data string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxInitPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
l := 1 + 4 // byte + uint32
|
||||||
|
for _, e := range p.Extensions {
|
||||||
|
l += 4 + len(e.Name) + 4 + len(e.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, ssh_FXP_INIT)
|
||||||
|
b = marshalUint32(b, p.Version)
|
||||||
|
for _, e := range p.Extensions {
|
||||||
|
b = marshalString(b, e.Name)
|
||||||
|
b = marshalString(b, e.Data)
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func marshalIdString(packetType byte, id uint32, str string) ([]byte, error) {
|
||||||
|
l := 1 + 4 + // type(byte) + uint32
|
||||||
|
4 + len(str)
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, packetType)
|
||||||
|
b = marshalUint32(b, id)
|
||||||
|
b = marshalString(b, str)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpReaddirPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Handle string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpReaddirPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_READDIR, p.Id, p.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpOpendirPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpOpendirPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_OPENDIR, p.Id, p.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpLstatPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpLstatPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_LSTAT, p.Id, p.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpFstatPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Handle string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpFstatPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_FSTAT, p.Id, p.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpClosePacket struct {
|
||||||
|
Id uint32
|
||||||
|
Handle string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpClosePacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_CLOSE, p.Id, p.Handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpRemovePacket struct {
|
||||||
|
Id uint32
|
||||||
|
Filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpRemovePacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_REMOVE, p.Id, p.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpRmdirPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpRmdirPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_RMDIR, p.Id, p.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpReadlinkPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
return marshalIdString(ssh_FXP_READLINK, p.Id, p.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpOpenPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Path string
|
||||||
|
Pflags uint32
|
||||||
|
Flags uint32 // ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
l := 1 + 4 +
|
||||||
|
4 + len(p.Path) +
|
||||||
|
4 + 4
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, ssh_FXP_OPEN)
|
||||||
|
b = marshalUint32(b, p.Id)
|
||||||
|
b = marshalString(b, p.Path)
|
||||||
|
b = marshalUint32(b, p.Pflags)
|
||||||
|
b = marshalUint32(b, p.Flags)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpReadPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Handle string
|
||||||
|
Offset uint64
|
||||||
|
Len uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
l := 1 + 4 + // type(byte) + uint32
|
||||||
|
4 + len(p.Handle) +
|
||||||
|
8 + 4 // uint64 + uint32
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, ssh_FXP_READ)
|
||||||
|
b = marshalUint32(b, p.Id)
|
||||||
|
b = marshalString(b, p.Handle)
|
||||||
|
b = marshalUint64(b, p.Offset)
|
||||||
|
b = marshalUint32(b, p.Len)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpRenamePacket struct {
|
||||||
|
Id uint32
|
||||||
|
Oldpath string
|
||||||
|
Newpath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
|
||||||
|
l := 1 + 4 + // type(byte) + uint32
|
||||||
|
4 + len(p.Oldpath) +
|
||||||
|
4 + len(p.Newpath)
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, ssh_FXP_RENAME)
|
||||||
|
b = marshalUint32(b, p.Id)
|
||||||
|
b = marshalString(b, p.Oldpath)
|
||||||
|
b = marshalString(b, p.Newpath)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpWritePacket struct {
|
||||||
|
Id uint32
|
||||||
|
Handle string
|
||||||
|
Offset uint64
|
||||||
|
Length uint32
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s sshFxpWritePacket) MarshalBinary() ([]byte, error) {
|
||||||
|
l := 1 + 4 + // type(byte) + uint32
|
||||||
|
4 + len(s.Handle) +
|
||||||
|
8 + 4 + // uint64 + uint32
|
||||||
|
len(s.Data)
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, ssh_FXP_WRITE)
|
||||||
|
b = marshalUint32(b, s.Id)
|
||||||
|
b = marshalString(b, s.Handle)
|
||||||
|
b = marshalUint64(b, s.Offset)
|
||||||
|
b = marshalUint32(b, s.Length)
|
||||||
|
b = append(b, s.Data...)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpMkdirPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Path string
|
||||||
|
Flags uint32 // ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
l := 1 + 4 + // type(byte) + uint32
|
||||||
|
4 + len(p.Path) +
|
||||||
|
4 // uint32
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, ssh_FXP_MKDIR)
|
||||||
|
b = marshalUint32(b, p.Id)
|
||||||
|
b = marshalString(b, p.Path)
|
||||||
|
b = marshalUint32(b, p.Flags)
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type sshFxpSetstatPacket struct {
|
||||||
|
Id uint32
|
||||||
|
Path string
|
||||||
|
Flags uint32
|
||||||
|
Attrs interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
|
||||||
|
l := 1 + 4 + // type(byte) + uint32
|
||||||
|
4 + len(p.Path) +
|
||||||
|
4 // uint32 + uint64
|
||||||
|
|
||||||
|
b := make([]byte, 0, l)
|
||||||
|
b = append(b, ssh_FXP_SETSTAT)
|
||||||
|
b = marshalUint32(b, p.Id)
|
||||||
|
b = marshalString(b, p.Path)
|
||||||
|
b = marshalUint32(b, p.Flags)
|
||||||
|
b = marshal(b, p.Attrs)
|
||||||
|
return b, nil
|
||||||
|
}
|
261
Godeps/_workspace/src/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
261
Godeps/_workspace/src/github.com/pkg/sftp/packet_test.go
generated
vendored
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var marshalUint32Tests = []struct {
|
||||||
|
v uint32
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{1, []byte{0, 0, 0, 1}},
|
||||||
|
{256, []byte{0, 0, 1, 0}},
|
||||||
|
{^uint32(0), []byte{255, 255, 255, 255}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUint32(t *testing.T) {
|
||||||
|
for _, tt := range marshalUint32Tests {
|
||||||
|
got := marshalUint32(nil, tt.v)
|
||||||
|
if !bytes.Equal(tt.want, got) {
|
||||||
|
t.Errorf("marshalUint32(%d): want %v, got %v", tt.v, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalUint64Tests = []struct {
|
||||||
|
v uint64
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{1, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}},
|
||||||
|
{256, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0}},
|
||||||
|
{^uint64(0), []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
|
||||||
|
{1 << 32, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalUint64(t *testing.T) {
|
||||||
|
for _, tt := range marshalUint64Tests {
|
||||||
|
got := marshalUint64(nil, tt.v)
|
||||||
|
if !bytes.Equal(tt.want, got) {
|
||||||
|
t.Errorf("marshalUint64(%d): want %#v, got %#v", tt.v, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalStringTests = []struct {
|
||||||
|
v string
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{"", []byte{0, 0, 0, 0}},
|
||||||
|
{"/foo", []byte{0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalString(t *testing.T) {
|
||||||
|
for _, tt := range marshalStringTests {
|
||||||
|
got := marshalString(nil, tt.v)
|
||||||
|
if !bytes.Equal(tt.want, got) {
|
||||||
|
t.Errorf("marshalString(%q): want %#v, got %#v", tt.v, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalTests = []struct {
|
||||||
|
v interface{}
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{uint8(1), []byte{1}},
|
||||||
|
{byte(1), []byte{1}},
|
||||||
|
{uint32(1), []byte{0, 0, 0, 1}},
|
||||||
|
{uint64(1), []byte{0, 0, 0, 0, 0, 0, 0, 1}},
|
||||||
|
{"foo", []byte{0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f}},
|
||||||
|
{[]uint32{1, 2, 3, 4}, []byte{0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x4}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshal(t *testing.T) {
|
||||||
|
for _, tt := range marshalTests {
|
||||||
|
got := marshal(nil, tt.v)
|
||||||
|
if !bytes.Equal(tt.want, got) {
|
||||||
|
t.Errorf("marshal(%v): want %#v, got %#v", tt.v, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalUint32Tests = []struct {
|
||||||
|
b []byte
|
||||||
|
want uint32
|
||||||
|
rest []byte
|
||||||
|
}{
|
||||||
|
{[]byte{0, 0, 0, 0}, 0, nil},
|
||||||
|
{[]byte{0, 0, 1, 0}, 256, nil},
|
||||||
|
{[]byte{255, 0, 0, 255}, 4278190335, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalUint32(t *testing.T) {
|
||||||
|
for _, tt := range unmarshalUint32Tests {
|
||||||
|
got, rest := unmarshalUint32(tt.b)
|
||||||
|
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||||
|
t.Errorf("unmarshalUint32(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalUint64Tests = []struct {
|
||||||
|
b []byte
|
||||||
|
want uint64
|
||||||
|
rest []byte
|
||||||
|
}{
|
||||||
|
{[]byte{0, 0, 0, 0, 0, 0, 0, 0}, 0, nil},
|
||||||
|
{[]byte{0, 0, 0, 0, 0, 0, 1, 0}, 256, nil},
|
||||||
|
{[]byte{255, 0, 0, 0, 0, 0, 0, 255}, 18374686479671623935, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalUint64(t *testing.T) {
|
||||||
|
for _, tt := range unmarshalUint64Tests {
|
||||||
|
got, rest := unmarshalUint64(tt.b)
|
||||||
|
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||||
|
t.Errorf("unmarshalUint64(%v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalStringTests = []struct {
|
||||||
|
b []byte
|
||||||
|
want string
|
||||||
|
rest []byte
|
||||||
|
}{
|
||||||
|
{marshalString(nil, ""), "", nil},
|
||||||
|
{marshalString(nil, "blah"), "blah", nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalString(t *testing.T) {
|
||||||
|
for _, tt := range unmarshalStringTests {
|
||||||
|
got, rest := unmarshalString(tt.b)
|
||||||
|
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||||
|
t.Errorf("unmarshalUint64(%v): want %q, %#v, got %q, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendPacketTests = []struct {
|
||||||
|
p encoding.BinaryMarshaler
|
||||||
|
want []byte
|
||||||
|
}{
|
||||||
|
{sshFxInitPacket{
|
||||||
|
Version: 3,
|
||||||
|
Extensions: []struct{ Name, Data string }{
|
||||||
|
{"posix-rename@openssh.com", "1"},
|
||||||
|
},
|
||||||
|
}, []byte{0x0, 0x0, 0x0, 0x26, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||||
|
|
||||||
|
{sshFxpOpenPacket{
|
||||||
|
Id: 1,
|
||||||
|
Path: "/foo",
|
||||||
|
Pflags: flags(os.O_RDONLY),
|
||||||
|
}, []byte{0x0, 0x0, 0x0, 0x15, 0x3, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
|
||||||
|
|
||||||
|
{sshFxpWritePacket{
|
||||||
|
Id: 124,
|
||||||
|
Handle: "foo",
|
||||||
|
Offset: 13,
|
||||||
|
Length: uint32(len([]byte("bar"))),
|
||||||
|
Data: []byte("bar"),
|
||||||
|
}, []byte{0x0, 0x0, 0x0, 0x1b, 0x6, 0x0, 0x0, 0x0, 0x7c, 0x0, 0x0, 0x0, 0x3, 0x66, 0x6f, 0x6f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0, 0x3, 0x62, 0x61, 0x72}},
|
||||||
|
|
||||||
|
{sshFxpSetstatPacket{
|
||||||
|
Id: 31,
|
||||||
|
Path: "/bar",
|
||||||
|
Flags: flags(os.O_WRONLY),
|
||||||
|
Attrs: struct {
|
||||||
|
Uid uint32
|
||||||
|
Gid uint32
|
||||||
|
}{1000, 100},
|
||||||
|
}, []byte{0x0, 0x0, 0x0, 0x19, 0x9, 0x0, 0x0, 0x0, 0x1f, 0x0, 0x0, 0x0, 0x4, 0x2f, 0x62, 0x61, 0x72, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x3, 0xe8, 0x0, 0x0, 0x0, 0x64}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSendPacket(t *testing.T) {
|
||||||
|
for _, tt := range sendPacketTests {
|
||||||
|
var w bytes.Buffer
|
||||||
|
sendPacket(&w, tt.p)
|
||||||
|
if got := w.Bytes(); !bytes.Equal(tt.want, got) {
|
||||||
|
t.Errorf("sendPacket(%v): want %#v, got %#v", tt.p, tt.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sp(p encoding.BinaryMarshaler) []byte {
|
||||||
|
var w bytes.Buffer
|
||||||
|
sendPacket(&w, p)
|
||||||
|
return w.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
var recvPacketTests = []struct {
|
||||||
|
b []byte
|
||||||
|
want uint8
|
||||||
|
rest []byte
|
||||||
|
}{
|
||||||
|
{sp(sshFxInitPacket{
|
||||||
|
Version: 3,
|
||||||
|
Extensions: []struct{ Name, Data string }{
|
||||||
|
{"posix-rename@openssh.com", "1"},
|
||||||
|
},
|
||||||
|
}), ssh_FXP_INIT, []byte{0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x18, 0x70, 0x6f, 0x73, 0x69, 0x78, 0x2d, 0x72, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x0, 0x0, 0x0, 0x1, 0x31}},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecvPacket(t *testing.T) {
|
||||||
|
for _, tt := range recvPacketTests {
|
||||||
|
r := bytes.NewReader(tt.b)
|
||||||
|
got, rest, _ := recvPacket(r)
|
||||||
|
if got != tt.want || !bytes.Equal(rest, tt.rest) {
|
||||||
|
t.Errorf("recvPacket(%#v): want %v, %#v, got %v, %#v", tt.b, tt.want, tt.rest, got, rest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalInit(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sp(sshFxInitPacket{
|
||||||
|
Version: 3,
|
||||||
|
Extensions: []struct{ Name, Data string }{
|
||||||
|
{"posix-rename@openssh.com", "1"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalOpen(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sp(sshFxpOpenPacket{
|
||||||
|
Id: 1,
|
||||||
|
Path: "/home/test/some/random/path",
|
||||||
|
Pflags: flags(os.O_RDONLY),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalWriteWorstCase(b *testing.B) {
|
||||||
|
data := make([]byte, 32*1024)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sp(sshFxpWritePacket{
|
||||||
|
Id: 1,
|
||||||
|
Handle: "someopaquehandle",
|
||||||
|
Offset: 0,
|
||||||
|
Length: uint32(len(data)),
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarshalWrite1k(b *testing.B) {
|
||||||
|
data := make([]byte, 1024)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sp(sshFxpWritePacket{
|
||||||
|
Id: 1,
|
||||||
|
Handle: "someopaquehandle",
|
||||||
|
Offset: 0,
|
||||||
|
Length: uint32(len(data)),
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
5
Godeps/_workspace/src/github.com/pkg/sftp/release.go
generated
vendored
Normal file
5
Godeps/_workspace/src/github.com/pkg/sftp/release.go
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// +build !debug
|
||||||
|
|
||||||
|
package sftp
|
||||||
|
|
||||||
|
func debug(fmt string, args ...interface{}) {}
|
187
Godeps/_workspace/src/github.com/pkg/sftp/sftp.go
generated
vendored
Normal file
187
Godeps/_workspace/src/github.com/pkg/sftp/sftp.go
generated
vendored
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// Package sftp implements the SSH File Transfer Protocol as described in
|
||||||
|
// https://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
|
||||||
|
package sftp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ssh_FXP_INIT = 1
|
||||||
|
ssh_FXP_VERSION = 2
|
||||||
|
ssh_FXP_OPEN = 3
|
||||||
|
ssh_FXP_CLOSE = 4
|
||||||
|
ssh_FXP_READ = 5
|
||||||
|
ssh_FXP_WRITE = 6
|
||||||
|
ssh_FXP_LSTAT = 7
|
||||||
|
ssh_FXP_FSTAT = 8
|
||||||
|
ssh_FXP_SETSTAT = 9
|
||||||
|
ssh_FXP_FSETSTAT = 10
|
||||||
|
ssh_FXP_OPENDIR = 11
|
||||||
|
ssh_FXP_READDIR = 12
|
||||||
|
ssh_FXP_REMOVE = 13
|
||||||
|
ssh_FXP_MKDIR = 14
|
||||||
|
ssh_FXP_RMDIR = 15
|
||||||
|
ssh_FXP_REALPATH = 16
|
||||||
|
ssh_FXP_STAT = 17
|
||||||
|
ssh_FXP_RENAME = 18
|
||||||
|
ssh_FXP_READLINK = 19
|
||||||
|
ssh_FXP_SYMLINK = 20
|
||||||
|
ssh_FXP_STATUS = 101
|
||||||
|
ssh_FXP_HANDLE = 102
|
||||||
|
ssh_FXP_DATA = 103
|
||||||
|
ssh_FXP_NAME = 104
|
||||||
|
ssh_FXP_ATTRS = 105
|
||||||
|
ssh_FXP_EXTENDED = 200
|
||||||
|
ssh_FXP_EXTENDED_REPLY = 201
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ssh_FX_OK = 0
|
||||||
|
ssh_FX_EOF = 1
|
||||||
|
ssh_FX_NO_SUCH_FILE = 2
|
||||||
|
ssh_FX_PERMISSION_DENIED = 3
|
||||||
|
ssh_FX_FAILURE = 4
|
||||||
|
ssh_FX_BAD_MESSAGE = 5
|
||||||
|
ssh_FX_NO_CONNECTION = 6
|
||||||
|
ssh_FX_CONNECTION_LOST = 7
|
||||||
|
ssh_FX_OP_UNSUPPORTED = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ssh_FXF_READ = 0x00000001
|
||||||
|
ssh_FXF_WRITE = 0x00000002
|
||||||
|
ssh_FXF_APPEND = 0x00000004
|
||||||
|
ssh_FXF_CREAT = 0x00000008
|
||||||
|
ssh_FXF_TRUNC = 0x00000010
|
||||||
|
ssh_FXF_EXCL = 0x00000020
|
||||||
|
)
|
||||||
|
|
||||||
|
type fxp uint8
|
||||||
|
|
||||||
|
func (f fxp) String() string {
|
||||||
|
switch f {
|
||||||
|
case ssh_FXP_INIT:
|
||||||
|
return "SSH_FXP_INIT"
|
||||||
|
case ssh_FXP_VERSION:
|
||||||
|
return "SSH_FXP_VERSION"
|
||||||
|
case ssh_FXP_OPEN:
|
||||||
|
return "SSH_FXP_OPEN"
|
||||||
|
case ssh_FXP_CLOSE:
|
||||||
|
return "SSH_FXP_CLOSE"
|
||||||
|
case ssh_FXP_READ:
|
||||||
|
return "SSH_FXP_READ"
|
||||||
|
case ssh_FXP_WRITE:
|
||||||
|
return "SSH_FXP_WRITE"
|
||||||
|
case ssh_FXP_LSTAT:
|
||||||
|
return "SSH_FXP_LSTAT"
|
||||||
|
case ssh_FXP_FSTAT:
|
||||||
|
return "SSH_FXP_FSTAT"
|
||||||
|
case ssh_FXP_SETSTAT:
|
||||||
|
return "SSH_FXP_SETSTAT"
|
||||||
|
case ssh_FXP_FSETSTAT:
|
||||||
|
return "SSH_FXP_FSETSTAT"
|
||||||
|
case ssh_FXP_OPENDIR:
|
||||||
|
return "SSH_FXP_OPENDIR"
|
||||||
|
case ssh_FXP_READDIR:
|
||||||
|
return "SSH_FXP_READDIR"
|
||||||
|
case ssh_FXP_REMOVE:
|
||||||
|
return "SSH_FXP_REMOVE"
|
||||||
|
case ssh_FXP_MKDIR:
|
||||||
|
return "SSH_FXP_MKDIR"
|
||||||
|
case ssh_FXP_RMDIR:
|
||||||
|
return "SSH_FXP_RMDIR"
|
||||||
|
case ssh_FXP_REALPATH:
|
||||||
|
return "SSH_FXP_REALPATH"
|
||||||
|
case ssh_FXP_STAT:
|
||||||
|
return "SSH_FXP_STAT"
|
||||||
|
case ssh_FXP_RENAME:
|
||||||
|
return "SSH_FXP_RENAME"
|
||||||
|
case ssh_FXP_READLINK:
|
||||||
|
return "SSH_FXP_READLINK"
|
||||||
|
case ssh_FXP_SYMLINK:
|
||||||
|
return "SSH_FXP_SYMLINK"
|
||||||
|
case ssh_FXP_STATUS:
|
||||||
|
return "SSH_FXP_STATUS"
|
||||||
|
case ssh_FXP_HANDLE:
|
||||||
|
return "SSH_FXP_HANDLE"
|
||||||
|
case ssh_FXP_DATA:
|
||||||
|
return "SSH_FXP_DATA"
|
||||||
|
case ssh_FXP_NAME:
|
||||||
|
return "SSH_FXP_NAME"
|
||||||
|
case ssh_FXP_ATTRS:
|
||||||
|
return "SSH_FXP_ATTRS"
|
||||||
|
case ssh_FXP_EXTENDED:
|
||||||
|
return "SSH_FXP_EXTENDED"
|
||||||
|
case ssh_FXP_EXTENDED_REPLY:
|
||||||
|
return "SSH_FXP_EXTENDED_REPLY"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fx uint8
|
||||||
|
|
||||||
|
func (f fx) String() string {
|
||||||
|
switch f {
|
||||||
|
case ssh_FX_OK:
|
||||||
|
return "SSH_FX_OK"
|
||||||
|
case ssh_FX_EOF:
|
||||||
|
return "SSH_FX_EOF"
|
||||||
|
case ssh_FX_NO_SUCH_FILE:
|
||||||
|
return "SSH_FX_NO_SUCH_FILE"
|
||||||
|
case ssh_FX_PERMISSION_DENIED:
|
||||||
|
return "SSH_FX_PERMISSION_DENIED"
|
||||||
|
case ssh_FX_FAILURE:
|
||||||
|
return "SSH_FX_FAILURE"
|
||||||
|
case ssh_FX_BAD_MESSAGE:
|
||||||
|
return "SSH_FX_BAD_MESSAGE"
|
||||||
|
case ssh_FX_NO_CONNECTION:
|
||||||
|
return "SSH_FX_NO_CONNECTION"
|
||||||
|
case ssh_FX_CONNECTION_LOST:
|
||||||
|
return "SSH_FX_CONNECTION_LOST"
|
||||||
|
case ssh_FX_OP_UNSUPPORTED:
|
||||||
|
return "SSH_FX_OP_UNSUPPORTED"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexpectedPacketErr struct {
|
||||||
|
want, got uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *unexpectedPacketErr) Error() string {
|
||||||
|
return fmt.Sprintf("sftp: unexpected packet: want %v, got %v", fxp(u.want), fxp(u.got))
|
||||||
|
}
|
||||||
|
|
||||||
|
func unimplementedPacketErr(u uint8) error {
|
||||||
|
return fmt.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexpectedIdErr struct{ want, got uint32 }
|
||||||
|
|
||||||
|
func (u *unexpectedIdErr) Error() string {
|
||||||
|
return fmt.Sprintf("sftp: unexpected id: want %v, got %v", u.want, u.got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unimplementedSeekWhence(whence int) error {
|
||||||
|
return fmt.Errorf("sftp: unimplemented seek whence %v", whence)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unexpectedCount(want, got uint32) error {
|
||||||
|
return fmt.Errorf("sftp: unexpected count: want %v, got %v", want, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unexpectedVersionErr struct{ want, got uint32 }
|
||||||
|
|
||||||
|
func (u *unexpectedVersionErr) Error() string {
|
||||||
|
return fmt.Sprintf("sftp: unexpected server version: want %v, got %v", u.want, u.got)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusError struct {
|
||||||
|
Code uint32
|
||||||
|
msg, lang string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StatusError) Error() string { return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) }
|
1
Godeps/_workspace/src/github.com/pkg/sftp/wercker.yml
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/pkg/sftp/wercker.yml
generated
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
box: wercker/golang
|
77
Godeps/_workspace/src/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
77
Godeps/_workspace/src/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||||
|
2898 / PKCS #5 v2.0.
|
||||||
|
|
||||||
|
A key derivation function is useful when encrypting data based on a password
|
||||||
|
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||||
|
a secure encryption key based on the password.
|
||||||
|
|
||||||
|
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||||
|
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||||
|
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||||
|
choose, you can pass the `New` functions from the different SHA packages to
|
||||||
|
pbkdf2.Key.
|
||||||
|
*/
|
||||||
|
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key derives a key from the password, salt and iteration count, returning a
|
||||||
|
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||||
|
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||||
|
// the supplied hash function.
|
||||||
|
//
|
||||||
|
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||||
|
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||||
|
// doing:
|
||||||
|
//
|
||||||
|
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||||
|
//
|
||||||
|
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||||
|
// RFC.
|
||||||
|
//
|
||||||
|
// Using a higher iteration count will increase the cost of an exhaustive
|
||||||
|
// search but will also make derivation proportionally slower.
|
||||||
|
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(h, password)
|
||||||
|
hashLen := prf.Size()
|
||||||
|
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
dk := make([]byte, 0, numBlocks*hashLen)
|
||||||
|
U := make([]byte, hashLen)
|
||||||
|
for block := 1; block <= numBlocks; block++ {
|
||||||
|
// N.B.: || means concatenation, ^ means XOR
|
||||||
|
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||||
|
// U_1 = PRF(password, salt || uint(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
buf[0] = byte(block >> 24)
|
||||||
|
buf[1] = byte(block >> 16)
|
||||||
|
buf[2] = byte(block >> 8)
|
||||||
|
buf[3] = byte(block)
|
||||||
|
prf.Write(buf[:4])
|
||||||
|
dk = prf.Sum(dk)
|
||||||
|
T := dk[len(dk)-hashLen:]
|
||||||
|
copy(U, T)
|
||||||
|
|
||||||
|
// U_n = PRF(password, U_(n-1))
|
||||||
|
for n := 2; n <= iter; n++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(U)
|
||||||
|
U = U[:0]
|
||||||
|
U = prf.Sum(U)
|
||||||
|
for x := range U {
|
||||||
|
T[x] ^= U[x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dk[:keyLen]
|
||||||
|
}
|
157
Godeps/_workspace/src/golang.org/x/crypto/pbkdf2/pbkdf2_test.go
generated
vendored
Normal file
157
Godeps/_workspace/src/golang.org/x/crypto/pbkdf2/pbkdf2_test.go
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package pbkdf2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"hash"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testVector struct {
|
||||||
|
password string
|
||||||
|
salt string
|
||||||
|
iter int
|
||||||
|
output []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test vectors from RFC 6070, http://tools.ietf.org/html/rfc6070
|
||||||
|
var sha1TestVectors = []testVector{
|
||||||
|
{
|
||||||
|
"password",
|
||||||
|
"salt",
|
||||||
|
1,
|
||||||
|
[]byte{
|
||||||
|
0x0c, 0x60, 0xc8, 0x0f, 0x96, 0x1f, 0x0e, 0x71,
|
||||||
|
0xf3, 0xa9, 0xb5, 0x24, 0xaf, 0x60, 0x12, 0x06,
|
||||||
|
0x2f, 0xe0, 0x37, 0xa6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"password",
|
||||||
|
"salt",
|
||||||
|
2,
|
||||||
|
[]byte{
|
||||||
|
0xea, 0x6c, 0x01, 0x4d, 0xc7, 0x2d, 0x6f, 0x8c,
|
||||||
|
0xcd, 0x1e, 0xd9, 0x2a, 0xce, 0x1d, 0x41, 0xf0,
|
||||||
|
0xd8, 0xde, 0x89, 0x57,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"password",
|
||||||
|
"salt",
|
||||||
|
4096,
|
||||||
|
[]byte{
|
||||||
|
0x4b, 0x00, 0x79, 0x01, 0xb7, 0x65, 0x48, 0x9a,
|
||||||
|
0xbe, 0xad, 0x49, 0xd9, 0x26, 0xf7, 0x21, 0xd0,
|
||||||
|
0x65, 0xa4, 0x29, 0xc1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// // This one takes too long
|
||||||
|
// {
|
||||||
|
// "password",
|
||||||
|
// "salt",
|
||||||
|
// 16777216,
|
||||||
|
// []byte{
|
||||||
|
// 0xee, 0xfe, 0x3d, 0x61, 0xcd, 0x4d, 0xa4, 0xe4,
|
||||||
|
// 0xe9, 0x94, 0x5b, 0x3d, 0x6b, 0xa2, 0x15, 0x8c,
|
||||||
|
// 0x26, 0x34, 0xe9, 0x84,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
"passwordPASSWORDpassword",
|
||||||
|
"saltSALTsaltSALTsaltSALTsaltSALTsalt",
|
||||||
|
4096,
|
||||||
|
[]byte{
|
||||||
|
0x3d, 0x2e, 0xec, 0x4f, 0xe4, 0x1c, 0x84, 0x9b,
|
||||||
|
0x80, 0xc8, 0xd8, 0x36, 0x62, 0xc0, 0xe4, 0x4a,
|
||||||
|
0x8b, 0x29, 0x1a, 0x96, 0x4c, 0xf2, 0xf0, 0x70,
|
||||||
|
0x38,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pass\000word",
|
||||||
|
"sa\000lt",
|
||||||
|
4096,
|
||||||
|
[]byte{
|
||||||
|
0x56, 0xfa, 0x6a, 0xa7, 0x55, 0x48, 0x09, 0x9d,
|
||||||
|
0xcc, 0x37, 0xd7, 0xf0, 0x34, 0x25, 0xe0, 0xc3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test vectors from
|
||||||
|
// http://stackoverflow.com/questions/5130513/pbkdf2-hmac-sha2-test-vectors
|
||||||
|
var sha256TestVectors = []testVector{
|
||||||
|
{
|
||||||
|
"password",
|
||||||
|
"salt",
|
||||||
|
1,
|
||||||
|
[]byte{
|
||||||
|
0x12, 0x0f, 0xb6, 0xcf, 0xfc, 0xf8, 0xb3, 0x2c,
|
||||||
|
0x43, 0xe7, 0x22, 0x52, 0x56, 0xc4, 0xf8, 0x37,
|
||||||
|
0xa8, 0x65, 0x48, 0xc9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"password",
|
||||||
|
"salt",
|
||||||
|
2,
|
||||||
|
[]byte{
|
||||||
|
0xae, 0x4d, 0x0c, 0x95, 0xaf, 0x6b, 0x46, 0xd3,
|
||||||
|
0x2d, 0x0a, 0xdf, 0xf9, 0x28, 0xf0, 0x6d, 0xd0,
|
||||||
|
0x2a, 0x30, 0x3f, 0x8e,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"password",
|
||||||
|
"salt",
|
||||||
|
4096,
|
||||||
|
[]byte{
|
||||||
|
0xc5, 0xe4, 0x78, 0xd5, 0x92, 0x88, 0xc8, 0x41,
|
||||||
|
0xaa, 0x53, 0x0d, 0xb6, 0x84, 0x5c, 0x4c, 0x8d,
|
||||||
|
0x96, 0x28, 0x93, 0xa0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"passwordPASSWORDpassword",
|
||||||
|
"saltSALTsaltSALTsaltSALTsaltSALTsalt",
|
||||||
|
4096,
|
||||||
|
[]byte{
|
||||||
|
0x34, 0x8c, 0x89, 0xdb, 0xcb, 0xd3, 0x2b, 0x2f,
|
||||||
|
0x32, 0xd8, 0x14, 0xb8, 0x11, 0x6e, 0x84, 0xcf,
|
||||||
|
0x2b, 0x17, 0x34, 0x7e, 0xbc, 0x18, 0x00, 0x18,
|
||||||
|
0x1c,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pass\000word",
|
||||||
|
"sa\000lt",
|
||||||
|
4096,
|
||||||
|
[]byte{
|
||||||
|
0x89, 0xb6, 0x9d, 0x05, 0x16, 0xf8, 0x29, 0x89,
|
||||||
|
0x3c, 0x69, 0x62, 0x26, 0x65, 0x0a, 0x86, 0x87,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func testHash(t *testing.T, h func() hash.Hash, hashName string, vectors []testVector) {
|
||||||
|
for i, v := range vectors {
|
||||||
|
o := Key([]byte(v.password), []byte(v.salt), v.iter, len(v.output), h)
|
||||||
|
if !bytes.Equal(o, v.output) {
|
||||||
|
t.Errorf("%s %d: expected %x, got %x", hashName, i, v.output, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithHMACSHA1(t *testing.T) {
|
||||||
|
testHash(t, sha1.New, "SHA1", sha1TestVectors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithHMACSHA256(t *testing.T) {
|
||||||
|
testHash(t, sha256.New, "SHA256", sha256TestVectors)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user