diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7240ba0..9226af6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,8 @@ jobs: test: strategy: matrix: - go-version: [1.13.x, 1.14.x] - platform: [ubuntu-latest] - runs-on: ${{ matrix.platform }} + go-version: [1.23.x, 1.24.x] + runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@master @@ -16,11 +15,8 @@ jobs: with: path: './src/github.com/github-release/github-release' # staticcheck needs this for GOPATH - - run: echo "::set-env name=GOPATH::$GITHUB_WORKSPACE" - - run: echo "::set-env name=PATH::$GITHUB_WORKSPACE/bin:$PATH" - - name: Download dependencies - run: go get -t -v ./... - working-directory: './src/github.com/github-release/github-release' + - run: echo "GOPATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV + - run: echo "PATH=$GITHUB_WORKSPACE/bin:$PATH" >> $GITHUB_ENV - name: Run tests - run: make test + run: make lint test working-directory: './src/github.com/github-release/github-release' diff --git a/.gitignore b/.gitignore index a266021..d0e4a2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,7 @@ -github-release -go-app -bin/ - -# 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 +/github-release +/go-app *.exe + +/bin +/var/cache diff --git a/Makefile b/Makefile index e8414b7..87f6565 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,6 @@ -LAST_TAG := $(shell git describe --abbrev=0 --tags) +SHELL=/bin/bash -o pipefail + +LAST_TAG ?= $(shell git describe --abbrev=0 --tags) USER := github-release EXECUTABLE := github-release @@ -21,7 +23,7 @@ all: $(EXECUTABLE) # the executable used to perform the upload, dogfooding and all... bin/tmp/$(EXECUTABLE): - go build -o "$@" + go build -v -o "$@" # arm bin/linux/arm/5/$(EXECUTABLE): @@ -60,20 +62,20 @@ ifndef GITHUB_TOKEN @echo "Please set GITHUB_TOKEN in the environment to perform a release" exit 1 endif - $(MAKE) bin/tmp/$(EXECUTABLE) $(COMPRESSED_EXECUTABLE_TARGETS) - git push && git push --tags - git log --format=%B $(LAST_TAG) -1 | \ - bin/tmp/$(EXECUTABLE) release -u $(USER) -r $(EXECUTABLE) \ - -t $(LAST_TAG) -n $(LAST_TAG) -d - || true - $(foreach FILE,$(COMPRESSED_EXECUTABLES),$(UPLOAD_CMD);) - -# install and/or update all dependencies, run this from the project directory -# go get -u ./... -# go test -i ./ -dep: - go list -f '{{join .Deps "\n"}}' | xargs go list -e -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | xargs go get -u - -$(EXECUTABLE): dep + docker run --rm --volume $(PWD)/var/cache:/root/.cache/go-build \ + --env GITHUB_TOKEN=$(GITHUB_TOKEN) \ + --env DEBUG_HTTP_TRAFFIC=true \ + --volume "$(PWD)":/app \ + --workdir /app \ + golang:latest \ + ./release \ + "$(MAKE) bin/tmp/$(EXECUTABLE) $(COMPRESSED_EXECUTABLE_TARGETS) && \ + git log --format=%B $(LAST_TAG) -1 | \ + bin/tmp/$(EXECUTABLE) release -u $(USER) -r $(EXECUTABLE) \ + -t $(LAST_TAG) -n $(LAST_TAG) -d - || true && \ + $(foreach FILE,$(COMPRESSED_EXECUTABLES),$(UPLOAD_CMD);)" + +$(EXECUTABLE): go build -o "$@" install: @@ -84,7 +86,10 @@ clean: rm $(EXECUTABLE) || true rm -rf bin/ +lint: + go vet ./... + test: go test ./... -.PHONY: clean release dep install +.PHONY: clean release install diff --git a/README.md b/README.md index 123544c..a9eba6a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ dogfooding, check the makefile! If you have Go installed, you can just do: ```sh -go get github.com/github-release/github-release +go install github.com/github-release/github-release ``` This will automatically download, compile and install the app. @@ -101,6 +101,13 @@ $ github-release delete \ --tag v0.1.0 ``` +Errata +====== + +The `release` command does not have an `--auth-user` flag because in practice, +Github ignores the `--auth-user` flag when validating releases. The only thing +that matters is passing a token that has permission to create the release. + GitHub Enterprise Support ========================= You can point to a different GitHub API endpoint via the environment variable ```GITHUB_API```: diff --git a/cmd.go b/cmd.go index e39a191..506ab28 100644 --- a/cmd.go +++ b/cmd.go @@ -317,11 +317,16 @@ func releasecmd(opt Options) error { repo := nvls(cmdopt.Repo, EnvRepo) token := nvls(cmdopt.Token, EnvToken) tag := cmdopt.Tag - name := nvls(cmdopt.Name, tag) - desc := nvls(cmdopt.Desc, tag) + name := cmdopt.Name + desc := cmdopt.Desc target := nvls(cmdopt.Target) draft := cmdopt.Draft prerelease := cmdopt.Prerelease + generateReleaseNotes := cmdopt.GenerateReleaseNotes + if !generateReleaseNotes { + name = nvls(name, tag) + desc = nvls(desc, tag) + } vprintln("releasing...") @@ -339,12 +344,13 @@ func releasecmd(opt Options) error { } params := ReleaseCreate{ - TagName: tag, - TargetCommitish: target, - Name: name, - Body: desc, - Draft: draft, - Prerelease: prerelease, + TagName: tag, + TargetCommitish: target, + Name: name, + Body: desc, + Draft: draft, + Prerelease: prerelease, + GenerateReleaseNotes: generateReleaseNotes, } /* encode params as json */ @@ -354,10 +360,17 @@ func releasecmd(opt Options) error { } reader := bytes.NewReader(payload) - URL := nvls(EnvApiEndpoint, github.DefaultBaseURL) + fmt.Sprintf("/repos/%s/%s/releases", user, repo) - resp, err := github.DoAuthRequest("POST", URL, "application/json", token, nil, reader) + // NB: Github appears to ignore the user here - the only thing that seems to + // matter is that the token is valid. + client := github.NewClient(user, token, nil) + client.SetBaseURL(EnvApiEndpoint) + req, err := client.NewRequest("POST", fmt.Sprintf("/repos/%s/%s/releases", user, repo), reader) if err != nil { - return fmt.Errorf("while submitting %v, %v", string(payload), err) + return fmt.Errorf("while submitting %v: %w", string(payload), err) + } + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("while submitting %v: %w", string(payload), err) } defer resp.Body.Close() diff --git a/github-release.go b/github-release.go index 28be5a1..5303cb8 100644 --- a/github-release.go +++ b/github-release.go @@ -38,15 +38,16 @@ type Options struct { Replace bool `goptions:"-R, --replace, description='Replace asset with same name if it already exists (WARNING: not atomic, failure to upload will remove the original asset too)'"` } `goptions:"upload"` Release struct { - Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"` - User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"` - Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"` - Tag string `goptions:"-t, --tag, obligatory, description='Git tag to create a release from'"` - Name string `goptions:"-n, --name, description='Name of the release (defaults to tag)'"` - Desc string `goptions:"-d, --description, description='Release description, use - for reading a description from stdin (defaults to tag)'"` - Target string `goptions:"-c, --target, description='Commit SHA or branch to create release of (defaults to the repository default branch)'"` - Draft bool `goptions:"--draft, description='The release is a draft'"` - Prerelease bool `goptions:"-p, --pre-release, description='The release is a pre-release'"` + Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"` + User string `goptions:"-u, --user, description='Github repo user or organisation (required if $GITHUB_USER not set)'"` + Repo string `goptions:"-r, --repo, description='Github repo (required if $GITHUB_REPO not set)'"` + Tag string `goptions:"-t, --tag, obligatory, description='Git tag to create a release from'"` + Name string `goptions:"-n, --name, description='Name of the release (defaults to tag)'"` + Desc string `goptions:"-d, --description, description='Release description, use - for reading a description from stdin (defaults to tag)'"` + Target string `goptions:"-c, --target, description='Commit SHA or branch to create release of (defaults to the repository default branch)'"` + Draft bool `goptions:"--draft, description='The release is a draft'"` + Prerelease bool `goptions:"-p, --pre-release, description='The release is a pre-release'"` + GenerateReleaseNotes bool `goptions:"-g, --generate-release-notes, description='Generate name and description if not given'"` } `goptions:"release"` Edit struct { Token string `goptions:"-s, --security-token, description='Github token (required if $GITHUB_TOKEN not set)'"` diff --git a/github/github.go b/github/github.go index ee0240c..0b2392a 100644 --- a/github/github.go +++ b/github/github.go @@ -11,7 +11,7 @@ import ( "os" "reflect" - "github.com/kevinburke/rest" + "github.com/kevinburke/rest/restclient" "github.com/tomnomnom/linkheader" ) @@ -42,14 +42,14 @@ func DoAuthRequest(method, url, mime, token string, headers map[string]string, b // API, such as authorization tokens. Methods called on Client will supply // these options when calling the API. type Client struct { - client *rest.Client + client *restclient.Client } // NewClient creates a new Client for use with the Github API. -func NewClient(username, token string, client *rest.Client) Client { +func NewClient(username, token string, client *restclient.Client) Client { c := Client{} if client == nil { - c.client = rest.NewClient(username, token, DefaultBaseURL) + c.client = restclient.New(username, token, DefaultBaseURL) } else { c.client = client } @@ -153,12 +153,12 @@ var defaultHttpClient *http.Client func init() { defaultHttpClient = &http.Client{ - Transport: rest.DefaultTransport, + Transport: restclient.DefaultTransport, } } // Caller is responsible for reading and closing the response body. -func (c Client) do(r *http.Request) (*http.Response, error) { +func (c Client) Do(r *http.Request) (*http.Response, error) { // Pulled this out of client.go:Do because we need to read the response // headers. var res *http.Response @@ -176,7 +176,7 @@ func (c Client) do(r *http.Request) (*http.Response, error) { if c.client.ErrorParser != nil { return nil, c.client.ErrorParser(res) } - return nil, rest.DefaultErrorParser(res) + return nil, restclient.DefaultErrorParser(res) } return res, nil } @@ -217,7 +217,7 @@ func (c Client) getPaginated(uri string) (io.ReadCloser, error) { if err != nil { return nil, err } - resp, err := c.do(req) + resp, err := c.Do(req) if err != nil { return nil, err } @@ -251,7 +251,7 @@ func (c Client) getPaginated(uri string) (io.ReadCloser, error) { w.CloseWithError(err) return } - resp, err := c.do(req) + resp, err := c.Do(req) if err != nil { w.CloseWithError(err) return @@ -283,7 +283,7 @@ func (c Client) getPaginated(uri string) (io.ReadCloser, error) { for resp := range responses { if resp.StatusCode != http.StatusOK { resp.Body.Close() - w.CloseWithError(fmt.Errorf("expected '200 OK' but received '%v' (url: %s)", resp.Status, resp.Request.URL)) + w.CloseWithError(fmt.Errorf("expected '200 OK' but received '%v' (%s to %s)", resp.Status, resp.Request.Method, resp.Request.URL.Path)) return } _, err := io.Copy(w, resp.Body) diff --git a/github/version.go b/github/version.go index 010f403..7c86c5e 100644 --- a/github/version.go +++ b/github/version.go @@ -1,3 +1,3 @@ package github -const VERSION = "0.8.0" +const VERSION = "0.11.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..27d2c9a --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/github-release/github-release + +go 1.23.0 + +require ( + github.com/dustin/go-humanize v1.0.1 + github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f + github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 + github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..874fb47 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f h1:y+inhBsY0ewgXFXCXlxodNxkXdYeU9YuneCYQEnRkmw= +github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f/go.mod h1:3cBF15uOiTj025Ll5QHLw317EB+e06+AEwyt7oHUubI= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= +github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= +github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 h1:txplJASvd6b/hrE0s/Ixfpp2cuwH9IO9oZBAN9iYa4A= +github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2/go.mod h1:DGCIhurYgnLz8J9ga1fMV/fbLDyUvTyrWXVWUIyJon4= diff --git a/release b/release new file mode 100755 index 0000000..4a1f30e --- /dev/null +++ b/release @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -euo pipefail + +main() { + apt-get update && apt-get install -y zip bzip2 + git branch --set-upstream-to=origin/master release + set -x + exec /bin/bash -c "$@" +} + +main "$@" diff --git a/releases.go b/releases.go index 83d4f02..05177a3 100644 --- a/releases.go +++ b/releases.go @@ -59,12 +59,13 @@ func (r *Release) String() string { } type ReleaseCreate struct { - TagName string `json:"tag_name"` - TargetCommitish string `json:"target_commitish,omitempty"` - Name string `json:"name"` - Body string `json:"body"` - Draft bool `json:"draft"` - Prerelease bool `json:"prerelease"` + TagName string `json:"tag_name"` + TargetCommitish string `json:"target_commitish,omitempty"` + Name string `json:"name"` + Body string `json:"body"` + Draft bool `json:"draft"` + Prerelease bool `json:"prerelease"` + GenerateReleaseNotes bool `json:"generate_release_notes"` } func Releases(user, repo, authUser, token string) ([]Release, error) { diff --git a/vendor/github.com/dustin/go-humanize/.travis.yml b/vendor/github.com/dustin/go-humanize/.travis.yml new file mode 100644 index 0000000..ac12e48 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/.travis.yml @@ -0,0 +1,21 @@ +sudo: false +language: go +go_import_path: github.com/dustin/go-humanize +go: + - 1.13.x + - 1.14.x + - 1.15.x + - 1.16.x + - stable + - master +matrix: + allow_failures: + - go: master + fast_finish: true +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). +script: + - diff -u <(echo -n) <(gofmt -d -s .) + - go vet . + - go install -v -race ./... + - go test -v -race ./... diff --git a/vendor/github.com/dustin/go-humanize/LICENSE b/vendor/github.com/dustin/go-humanize/LICENSE new file mode 100644 index 0000000..8d9a94a --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2005-2008 Dustin Sallings + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/vendor/github.com/dustin/go-humanize/README.markdown b/vendor/github.com/dustin/go-humanize/README.markdown new file mode 100644 index 0000000..7d0b16b --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/README.markdown @@ -0,0 +1,124 @@ +# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize) + +Just a few functions for helping humanize times and sizes. + +`go get` it as `github.com/dustin/go-humanize`, import it as +`"github.com/dustin/go-humanize"`, use it as `humanize`. + +See [godoc](https://pkg.go.dev/github.com/dustin/go-humanize) for +complete documentation. + +## Sizes + +This lets you take numbers like `82854982` and convert them to useful +strings like, `83 MB` or `79 MiB` (whichever you prefer). + +Example: + +```go +fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB. +``` + +## Times + +This lets you take a `time.Time` and spit it out in relative terms. +For example, `12 seconds ago` or `3 days from now`. + +Example: + +```go +fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago. +``` + +Thanks to Kyle Lemons for the time implementation from an IRC +conversation one day. It's pretty neat. + +## Ordinals + +From a [mailing list discussion][odisc] where a user wanted to be able +to label ordinals. + + 0 -> 0th + 1 -> 1st + 2 -> 2nd + 3 -> 3rd + 4 -> 4th + [...] + +Example: + +```go +fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend. +``` + +## Commas + +Want to shove commas into numbers? Be my guest. + + 0 -> 0 + 100 -> 100 + 1000 -> 1,000 + 1000000000 -> 1,000,000,000 + -100000 -> -100,000 + +Example: + +```go +fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491. +``` + +## Ftoa + +Nicer float64 formatter that removes trailing zeros. + +```go +fmt.Printf("%f", 2.24) // 2.240000 +fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 +fmt.Printf("%f", 2.0) // 2.000000 +fmt.Printf("%s", humanize.Ftoa(2.0)) // 2 +``` + +## SI notation + +Format numbers with [SI notation][sinotation]. + +Example: + +```go +humanize.SI(0.00000000223, "M") // 2.23 nM +``` + +## English-specific functions + +The following functions are in the `humanize/english` subpackage. + +### Plurals + +Simple English pluralization + +```go +english.PluralWord(1, "object", "") // object +english.PluralWord(42, "object", "") // objects +english.PluralWord(2, "bus", "") // buses +english.PluralWord(99, "locus", "loci") // loci + +english.Plural(1, "object", "") // 1 object +english.Plural(42, "object", "") // 42 objects +english.Plural(2, "bus", "") // 2 buses +english.Plural(99, "locus", "loci") // 99 loci +``` + +### Word series + +Format comma-separated words lists with conjuctions: + +```go +english.WordSeries([]string{"foo"}, "and") // foo +english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar +english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz + +english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz +``` + +[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion +[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix diff --git a/vendor/github.com/dustin/go-humanize/big.go b/vendor/github.com/dustin/go-humanize/big.go new file mode 100644 index 0000000..f49dc33 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/big.go @@ -0,0 +1,31 @@ +package humanize + +import ( + "math/big" +) + +// order of magnitude (to a max order) +func oomm(n, b *big.Int, maxmag int) (float64, int) { + mag := 0 + m := &big.Int{} + for n.Cmp(b) >= 0 { + n.DivMod(n, b, m) + mag++ + if mag == maxmag && maxmag >= 0 { + break + } + } + return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag +} + +// total order of magnitude +// (same as above, but with no upper limit) +func oom(n, b *big.Int) (float64, int) { + mag := 0 + m := &big.Int{} + for n.Cmp(b) >= 0 { + n.DivMod(n, b, m) + mag++ + } + return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag +} diff --git a/vendor/github.com/dustin/go-humanize/bigbytes.go b/vendor/github.com/dustin/go-humanize/bigbytes.go new file mode 100644 index 0000000..3b015fd --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/bigbytes.go @@ -0,0 +1,189 @@ +package humanize + +import ( + "fmt" + "math/big" + "strings" + "unicode" +) + +var ( + bigIECExp = big.NewInt(1024) + + // BigByte is one byte in bit.Ints + BigByte = big.NewInt(1) + // BigKiByte is 1,024 bytes in bit.Ints + BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp) + // BigMiByte is 1,024 k bytes in bit.Ints + BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp) + // BigGiByte is 1,024 m bytes in bit.Ints + BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp) + // BigTiByte is 1,024 g bytes in bit.Ints + BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp) + // BigPiByte is 1,024 t bytes in bit.Ints + BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp) + // BigEiByte is 1,024 p bytes in bit.Ints + BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp) + // BigZiByte is 1,024 e bytes in bit.Ints + BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp) + // BigYiByte is 1,024 z bytes in bit.Ints + BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp) + // BigRiByte is 1,024 y bytes in bit.Ints + BigRiByte = (&big.Int{}).Mul(BigYiByte, bigIECExp) + // BigQiByte is 1,024 r bytes in bit.Ints + BigQiByte = (&big.Int{}).Mul(BigRiByte, bigIECExp) +) + +var ( + bigSIExp = big.NewInt(1000) + + // BigSIByte is one SI byte in big.Ints + BigSIByte = big.NewInt(1) + // BigKByte is 1,000 SI bytes in big.Ints + BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp) + // BigMByte is 1,000 SI k bytes in big.Ints + BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp) + // BigGByte is 1,000 SI m bytes in big.Ints + BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp) + // BigTByte is 1,000 SI g bytes in big.Ints + BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp) + // BigPByte is 1,000 SI t bytes in big.Ints + BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp) + // BigEByte is 1,000 SI p bytes in big.Ints + BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp) + // BigZByte is 1,000 SI e bytes in big.Ints + BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp) + // BigYByte is 1,000 SI z bytes in big.Ints + BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp) + // BigRByte is 1,000 SI y bytes in big.Ints + BigRByte = (&big.Int{}).Mul(BigYByte, bigSIExp) + // BigQByte is 1,000 SI r bytes in big.Ints + BigQByte = (&big.Int{}).Mul(BigRByte, bigSIExp) +) + +var bigBytesSizeTable = map[string]*big.Int{ + "b": BigByte, + "kib": BigKiByte, + "kb": BigKByte, + "mib": BigMiByte, + "mb": BigMByte, + "gib": BigGiByte, + "gb": BigGByte, + "tib": BigTiByte, + "tb": BigTByte, + "pib": BigPiByte, + "pb": BigPByte, + "eib": BigEiByte, + "eb": BigEByte, + "zib": BigZiByte, + "zb": BigZByte, + "yib": BigYiByte, + "yb": BigYByte, + "rib": BigRiByte, + "rb": BigRByte, + "qib": BigQiByte, + "qb": BigQByte, + // Without suffix + "": BigByte, + "ki": BigKiByte, + "k": BigKByte, + "mi": BigMiByte, + "m": BigMByte, + "gi": BigGiByte, + "g": BigGByte, + "ti": BigTiByte, + "t": BigTByte, + "pi": BigPiByte, + "p": BigPByte, + "ei": BigEiByte, + "e": BigEByte, + "z": BigZByte, + "zi": BigZiByte, + "y": BigYByte, + "yi": BigYiByte, + "r": BigRByte, + "ri": BigRiByte, + "q": BigQByte, + "qi": BigQiByte, +} + +var ten = big.NewInt(10) + +func humanateBigBytes(s, base *big.Int, sizes []string) string { + if s.Cmp(ten) < 0 { + return fmt.Sprintf("%d B", s) + } + c := (&big.Int{}).Set(s) + val, mag := oomm(c, base, len(sizes)-1) + suffix := sizes[mag] + f := "%.0f %s" + if val < 10 { + f = "%.1f %s" + } + + return fmt.Sprintf(f, val, suffix) + +} + +// BigBytes produces a human readable representation of an SI size. +// +// See also: ParseBigBytes. +// +// BigBytes(82854982) -> 83 MB +func BigBytes(s *big.Int) string { + sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"} + return humanateBigBytes(s, bigSIExp, sizes) +} + +// BigIBytes produces a human readable representation of an IEC size. +// +// See also: ParseBigBytes. +// +// BigIBytes(82854982) -> 79 MiB +func BigIBytes(s *big.Int) string { + sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"} + return humanateBigBytes(s, bigIECExp, sizes) +} + +// ParseBigBytes parses a string representation of bytes into the number +// of bytes it represents. +// +// See also: BigBytes, BigIBytes. +// +// ParseBigBytes("42 MB") -> 42000000, nil +// ParseBigBytes("42 mib") -> 44040192, nil +func ParseBigBytes(s string) (*big.Int, error) { + lastDigit := 0 + hasComma := false + for _, r := range s { + if !(unicode.IsDigit(r) || r == '.' || r == ',') { + break + } + if r == ',' { + hasComma = true + } + lastDigit++ + } + + num := s[:lastDigit] + if hasComma { + num = strings.Replace(num, ",", "", -1) + } + + val := &big.Rat{} + _, err := fmt.Sscanf(num, "%f", val) + if err != nil { + return nil, err + } + + extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) + if m, ok := bigBytesSizeTable[extra]; ok { + mv := (&big.Rat{}).SetInt(m) + val.Mul(val, mv) + rv := &big.Int{} + rv.Div(val.Num(), val.Denom()) + return rv, nil + } + + return nil, fmt.Errorf("unhandled size name: %v", extra) +} diff --git a/vendor/github.com/dustin/go-humanize/bytes.go b/vendor/github.com/dustin/go-humanize/bytes.go new file mode 100644 index 0000000..0b498f4 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/bytes.go @@ -0,0 +1,143 @@ +package humanize + +import ( + "fmt" + "math" + "strconv" + "strings" + "unicode" +) + +// IEC Sizes. +// kibis of bits +const ( + Byte = 1 << (iota * 10) + KiByte + MiByte + GiByte + TiByte + PiByte + EiByte +) + +// SI Sizes. +const ( + IByte = 1 + KByte = IByte * 1000 + MByte = KByte * 1000 + GByte = MByte * 1000 + TByte = GByte * 1000 + PByte = TByte * 1000 + EByte = PByte * 1000 +) + +var bytesSizeTable = map[string]uint64{ + "b": Byte, + "kib": KiByte, + "kb": KByte, + "mib": MiByte, + "mb": MByte, + "gib": GiByte, + "gb": GByte, + "tib": TiByte, + "tb": TByte, + "pib": PiByte, + "pb": PByte, + "eib": EiByte, + "eb": EByte, + // Without suffix + "": Byte, + "ki": KiByte, + "k": KByte, + "mi": MiByte, + "m": MByte, + "gi": GiByte, + "g": GByte, + "ti": TiByte, + "t": TByte, + "pi": PiByte, + "p": PByte, + "ei": EiByte, + "e": EByte, +} + +func logn(n, b float64) float64 { + return math.Log(n) / math.Log(b) +} + +func humanateBytes(s uint64, base float64, sizes []string) string { + if s < 10 { + return fmt.Sprintf("%d B", s) + } + e := math.Floor(logn(float64(s), base)) + suffix := sizes[int(e)] + val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 + f := "%.0f %s" + if val < 10 { + f = "%.1f %s" + } + + return fmt.Sprintf(f, val, suffix) +} + +// Bytes produces a human readable representation of an SI size. +// +// See also: ParseBytes. +// +// Bytes(82854982) -> 83 MB +func Bytes(s uint64) string { + sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} + return humanateBytes(s, 1000, sizes) +} + +// IBytes produces a human readable representation of an IEC size. +// +// See also: ParseBytes. +// +// IBytes(82854982) -> 79 MiB +func IBytes(s uint64) string { + sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} + return humanateBytes(s, 1024, sizes) +} + +// ParseBytes parses a string representation of bytes into the number +// of bytes it represents. +// +// See Also: Bytes, IBytes. +// +// ParseBytes("42 MB") -> 42000000, nil +// ParseBytes("42 mib") -> 44040192, nil +func ParseBytes(s string) (uint64, error) { + lastDigit := 0 + hasComma := false + for _, r := range s { + if !(unicode.IsDigit(r) || r == '.' || r == ',') { + break + } + if r == ',' { + hasComma = true + } + lastDigit++ + } + + num := s[:lastDigit] + if hasComma { + num = strings.Replace(num, ",", "", -1) + } + + f, err := strconv.ParseFloat(num, 64) + if err != nil { + return 0, err + } + + extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) + if m, ok := bytesSizeTable[extra]; ok { + f *= float64(m) + if f >= math.MaxUint64 { + return 0, fmt.Errorf("too large: %v", s) + } + return uint64(f), nil + } + + return 0, fmt.Errorf("unhandled size name: %v", extra) +} diff --git a/vendor/github.com/dustin/go-humanize/comma.go b/vendor/github.com/dustin/go-humanize/comma.go new file mode 100644 index 0000000..520ae3e --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/comma.go @@ -0,0 +1,116 @@ +package humanize + +import ( + "bytes" + "math" + "math/big" + "strconv" + "strings" +) + +// Comma produces a string form of the given number in base 10 with +// commas after every three orders of magnitude. +// +// e.g. Comma(834142) -> 834,142 +func Comma(v int64) string { + sign := "" + + // Min int64 can't be negated to a usable value, so it has to be special cased. + if v == math.MinInt64 { + return "-9,223,372,036,854,775,808" + } + + if v < 0 { + sign = "-" + v = 0 - v + } + + parts := []string{"", "", "", "", "", "", ""} + j := len(parts) - 1 + + for v > 999 { + parts[j] = strconv.FormatInt(v%1000, 10) + switch len(parts[j]) { + case 2: + parts[j] = "0" + parts[j] + case 1: + parts[j] = "00" + parts[j] + } + v = v / 1000 + j-- + } + parts[j] = strconv.Itoa(int(v)) + return sign + strings.Join(parts[j:], ",") +} + +// Commaf produces a string form of the given number in base 10 with +// commas after every three orders of magnitude. +// +// e.g. Commaf(834142.32) -> 834,142.32 +func Commaf(v float64) string { + buf := &bytes.Buffer{} + if v < 0 { + buf.Write([]byte{'-'}) + v = 0 - v + } + + comma := []byte{','} + + parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") + pos := 0 + if len(parts[0])%3 != 0 { + pos += len(parts[0]) % 3 + buf.WriteString(parts[0][:pos]) + buf.Write(comma) + } + for ; pos < len(parts[0]); pos += 3 { + buf.WriteString(parts[0][pos : pos+3]) + buf.Write(comma) + } + buf.Truncate(buf.Len() - 1) + + if len(parts) > 1 { + buf.Write([]byte{'.'}) + buf.WriteString(parts[1]) + } + return buf.String() +} + +// CommafWithDigits works like the Commaf but limits the resulting +// string to the given number of decimal places. +// +// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3 +func CommafWithDigits(f float64, decimals int) string { + return stripTrailingDigits(Commaf(f), decimals) +} + +// BigComma produces a string form of the given big.Int in base 10 +// with commas after every three orders of magnitude. +func BigComma(b *big.Int) string { + sign := "" + if b.Sign() < 0 { + sign = "-" + b.Abs(b) + } + + athousand := big.NewInt(1000) + c := (&big.Int{}).Set(b) + _, m := oom(c, athousand) + parts := make([]string, m+1) + j := len(parts) - 1 + + mod := &big.Int{} + for b.Cmp(athousand) >= 0 { + b.DivMod(b, athousand, mod) + parts[j] = strconv.FormatInt(mod.Int64(), 10) + switch len(parts[j]) { + case 2: + parts[j] = "0" + parts[j] + case 1: + parts[j] = "00" + parts[j] + } + j-- + } + parts[j] = strconv.Itoa(int(b.Int64())) + return sign + strings.Join(parts[j:], ",") +} diff --git a/vendor/github.com/dustin/go-humanize/commaf.go b/vendor/github.com/dustin/go-humanize/commaf.go new file mode 100644 index 0000000..2bc83a0 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/commaf.go @@ -0,0 +1,41 @@ +//go:build go1.6 +// +build go1.6 + +package humanize + +import ( + "bytes" + "math/big" + "strings" +) + +// BigCommaf produces a string form of the given big.Float in base 10 +// with commas after every three orders of magnitude. +func BigCommaf(v *big.Float) string { + buf := &bytes.Buffer{} + if v.Sign() < 0 { + buf.Write([]byte{'-'}) + v.Abs(v) + } + + comma := []byte{','} + + parts := strings.Split(v.Text('f', -1), ".") + pos := 0 + if len(parts[0])%3 != 0 { + pos += len(parts[0]) % 3 + buf.WriteString(parts[0][:pos]) + buf.Write(comma) + } + for ; pos < len(parts[0]); pos += 3 { + buf.WriteString(parts[0][pos : pos+3]) + buf.Write(comma) + } + buf.Truncate(buf.Len() - 1) + + if len(parts) > 1 { + buf.Write([]byte{'.'}) + buf.WriteString(parts[1]) + } + return buf.String() +} diff --git a/vendor/github.com/dustin/go-humanize/ftoa.go b/vendor/github.com/dustin/go-humanize/ftoa.go new file mode 100644 index 0000000..bce923f --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/ftoa.go @@ -0,0 +1,49 @@ +package humanize + +import ( + "strconv" + "strings" +) + +func stripTrailingZeros(s string) string { + if !strings.ContainsRune(s, '.') { + return s + } + offset := len(s) - 1 + for offset > 0 { + if s[offset] == '.' { + offset-- + break + } + if s[offset] != '0' { + break + } + offset-- + } + return s[:offset+1] +} + +func stripTrailingDigits(s string, digits int) string { + if i := strings.Index(s, "."); i >= 0 { + if digits <= 0 { + return s[:i] + } + i++ + if i+digits >= len(s) { + return s + } + return s[:i+digits] + } + return s +} + +// Ftoa converts a float to a string with no trailing zeros. +func Ftoa(num float64) string { + return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64)) +} + +// FtoaWithDigits converts a float to a string but limits the resulting string +// to the given number of decimal places, and no trailing zeros. +func FtoaWithDigits(num float64, digits int) string { + return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits)) +} diff --git a/vendor/github.com/dustin/go-humanize/humanize.go b/vendor/github.com/dustin/go-humanize/humanize.go new file mode 100644 index 0000000..a2c2da3 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/humanize.go @@ -0,0 +1,8 @@ +/* +Package humanize converts boring ugly numbers to human-friendly strings and back. + +Durations can be turned into strings such as "3 days ago", numbers +representing sizes like 82854982 into useful strings like, "83 MB" or +"79 MiB" (whichever you prefer). +*/ +package humanize diff --git a/vendor/github.com/dustin/go-humanize/number.go b/vendor/github.com/dustin/go-humanize/number.go new file mode 100644 index 0000000..6470d0d --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/number.go @@ -0,0 +1,192 @@ +package humanize + +/* +Slightly adapted from the source to fit go-humanize. + +Author: https://github.com/gorhill +Source: https://gist.github.com/gorhill/5285193 + +*/ + +import ( + "math" + "strconv" +) + +var ( + renderFloatPrecisionMultipliers = [...]float64{ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + } + + renderFloatPrecisionRounders = [...]float64{ + 0.5, + 0.05, + 0.005, + 0.0005, + 0.00005, + 0.000005, + 0.0000005, + 0.00000005, + 0.000000005, + 0.0000000005, + } +) + +// FormatFloat produces a formatted number as string based on the following user-specified criteria: +// * thousands separator +// * decimal separator +// * decimal precision +// +// Usage: s := RenderFloat(format, n) +// The format parameter tells how to render the number n. +// +// See examples: http://play.golang.org/p/LXc1Ddm1lJ +// +// Examples of format strings, given n = 12345.6789: +// "#,###.##" => "12,345.67" +// "#,###." => "12,345" +// "#,###" => "12345,678" +// "#\u202F###,##" => "12 345,68" +// "#.###,###### => 12.345,678900 +// "" (aka default format) => 12,345.67 +// +// The highest precision allowed is 9 digits after the decimal symbol. +// There is also a version for integer number, FormatInteger(), +// which is convenient for calls within template. +func FormatFloat(format string, n float64) string { + // Special cases: + // NaN = "NaN" + // +Inf = "+Infinity" + // -Inf = "-Infinity" + if math.IsNaN(n) { + return "NaN" + } + if n > math.MaxFloat64 { + return "Infinity" + } + if n < (0.0 - math.MaxFloat64) { + return "-Infinity" + } + + // default format + precision := 2 + decimalStr := "." + thousandStr := "," + positiveStr := "" + negativeStr := "-" + + if len(format) > 0 { + format := []rune(format) + + // If there is an explicit format directive, + // then default values are these: + precision = 9 + thousandStr = "" + + // collect indices of meaningful formatting directives + formatIndx := []int{} + for i, char := range format { + if char != '#' && char != '0' { + formatIndx = append(formatIndx, i) + } + } + + if len(formatIndx) > 0 { + // Directive at index 0: + // Must be a '+' + // Raise an error if not the case + // index: 0123456789 + // +0.000,000 + // +000,000.0 + // +0000.00 + // +0000 + if formatIndx[0] == 0 { + if format[formatIndx[0]] != '+' { + panic("RenderFloat(): invalid positive sign directive") + } + positiveStr = "+" + formatIndx = formatIndx[1:] + } + + // Two directives: + // First is thousands separator + // Raise an error if not followed by 3-digit + // 0123456789 + // 0.000,000 + // 000,000.00 + if len(formatIndx) == 2 { + if (formatIndx[1] - formatIndx[0]) != 4 { + panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") + } + thousandStr = string(format[formatIndx[0]]) + formatIndx = formatIndx[1:] + } + + // One directive: + // Directive is decimal separator + // The number of digit-specifier following the separator indicates wanted precision + // 0123456789 + // 0.00 + // 000,0000 + if len(formatIndx) == 1 { + decimalStr = string(format[formatIndx[0]]) + precision = len(format) - formatIndx[0] - 1 + } + } + } + + // generate sign part + var signStr string + if n >= 0.000000001 { + signStr = positiveStr + } else if n <= -0.000000001 { + signStr = negativeStr + n = -n + } else { + signStr = "" + n = 0.0 + } + + // split number into integer and fractional parts + intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) + + // generate integer part string + intStr := strconv.FormatInt(int64(intf), 10) + + // add thousand separator if required + if len(thousandStr) > 0 { + for i := len(intStr); i > 3; { + i -= 3 + intStr = intStr[:i] + thousandStr + intStr[i:] + } + } + + // no fractional part, we can leave now + if precision == 0 { + return signStr + intStr + } + + // generate fractional part + fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) + // may need padding + if len(fracStr) < precision { + fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr + } + + return signStr + intStr + decimalStr + fracStr +} + +// FormatInteger produces a formatted number as string. +// See FormatFloat. +func FormatInteger(format string, n int) string { + return FormatFloat(format, float64(n)) +} diff --git a/vendor/github.com/dustin/go-humanize/ordinals.go b/vendor/github.com/dustin/go-humanize/ordinals.go new file mode 100644 index 0000000..43d88a8 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/ordinals.go @@ -0,0 +1,25 @@ +package humanize + +import "strconv" + +// Ordinal gives you the input number in a rank/ordinal format. +// +// Ordinal(3) -> 3rd +func Ordinal(x int) string { + suffix := "th" + switch x % 10 { + case 1: + if x%100 != 11 { + suffix = "st" + } + case 2: + if x%100 != 12 { + suffix = "nd" + } + case 3: + if x%100 != 13 { + suffix = "rd" + } + } + return strconv.Itoa(x) + suffix +} diff --git a/vendor/github.com/dustin/go-humanize/si.go b/vendor/github.com/dustin/go-humanize/si.go new file mode 100644 index 0000000..8b85019 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/si.go @@ -0,0 +1,127 @@ +package humanize + +import ( + "errors" + "math" + "regexp" + "strconv" +) + +var siPrefixTable = map[float64]string{ + -30: "q", // quecto + -27: "r", // ronto + -24: "y", // yocto + -21: "z", // zepto + -18: "a", // atto + -15: "f", // femto + -12: "p", // pico + -9: "n", // nano + -6: "µ", // micro + -3: "m", // milli + 0: "", + 3: "k", // kilo + 6: "M", // mega + 9: "G", // giga + 12: "T", // tera + 15: "P", // peta + 18: "E", // exa + 21: "Z", // zetta + 24: "Y", // yotta + 27: "R", // ronna + 30: "Q", // quetta +} + +var revSIPrefixTable = revfmap(siPrefixTable) + +// revfmap reverses the map and precomputes the power multiplier +func revfmap(in map[float64]string) map[string]float64 { + rv := map[string]float64{} + for k, v := range in { + rv[v] = math.Pow(10, k) + } + return rv +} + +var riParseRegex *regexp.Regexp + +func init() { + ri := `^([\-0-9.]+)\s?([` + for _, v := range siPrefixTable { + ri += v + } + ri += `]?)(.*)` + + riParseRegex = regexp.MustCompile(ri) +} + +// ComputeSI finds the most appropriate SI prefix for the given number +// and returns the prefix along with the value adjusted to be within +// that prefix. +// +// See also: SI, ParseSI. +// +// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p") +func ComputeSI(input float64) (float64, string) { + if input == 0 { + return 0, "" + } + mag := math.Abs(input) + exponent := math.Floor(logn(mag, 10)) + exponent = math.Floor(exponent/3) * 3 + + value := mag / math.Pow(10, exponent) + + // Handle special case where value is exactly 1000.0 + // Should return 1 M instead of 1000 k + if value == 1000.0 { + exponent += 3 + value = mag / math.Pow(10, exponent) + } + + value = math.Copysign(value, input) + + prefix := siPrefixTable[exponent] + return value, prefix +} + +// SI returns a string with default formatting. +// +// SI uses Ftoa to format float value, removing trailing zeros. +// +// See also: ComputeSI, ParseSI. +// +// e.g. SI(1000000, "B") -> 1 MB +// e.g. SI(2.2345e-12, "F") -> 2.2345 pF +func SI(input float64, unit string) string { + value, prefix := ComputeSI(input) + return Ftoa(value) + " " + prefix + unit +} + +// SIWithDigits works like SI but limits the resulting string to the +// given number of decimal places. +// +// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB +// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF +func SIWithDigits(input float64, decimals int, unit string) string { + value, prefix := ComputeSI(input) + return FtoaWithDigits(value, decimals) + " " + prefix + unit +} + +var errInvalid = errors.New("invalid input") + +// ParseSI parses an SI string back into the number and unit. +// +// See also: SI, ComputeSI. +// +// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) +func ParseSI(input string) (float64, string, error) { + found := riParseRegex.FindStringSubmatch(input) + if len(found) != 4 { + return 0, "", errInvalid + } + mag := revSIPrefixTable[found[2]] + unit := found[3] + + base, err := strconv.ParseFloat(found[1], 64) + return base * mag, unit, err +} diff --git a/vendor/github.com/dustin/go-humanize/times.go b/vendor/github.com/dustin/go-humanize/times.go new file mode 100644 index 0000000..dd3fbf5 --- /dev/null +++ b/vendor/github.com/dustin/go-humanize/times.go @@ -0,0 +1,117 @@ +package humanize + +import ( + "fmt" + "math" + "sort" + "time" +) + +// Seconds-based time units +const ( + Day = 24 * time.Hour + Week = 7 * Day + Month = 30 * Day + Year = 12 * Month + LongTime = 37 * Year +) + +// Time formats a time into a relative string. +// +// Time(someT) -> "3 weeks ago" +func Time(then time.Time) string { + return RelTime(then, time.Now(), "ago", "from now") +} + +// A RelTimeMagnitude struct contains a relative time point at which +// the relative format of time will switch to a new format string. A +// slice of these in ascending order by their "D" field is passed to +// CustomRelTime to format durations. +// +// The Format field is a string that may contain a "%s" which will be +// replaced with the appropriate signed label (e.g. "ago" or "from +// now") and a "%d" that will be replaced by the quantity. +// +// The DivBy field is the amount of time the time difference must be +// divided by in order to display correctly. +// +// e.g. if D is 2*time.Minute and you want to display "%d minutes %s" +// DivBy should be time.Minute so whatever the duration is will be +// expressed in minutes. +type RelTimeMagnitude struct { + D time.Duration + Format string + DivBy time.Duration +} + +var defaultMagnitudes = []RelTimeMagnitude{ + {time.Second, "now", time.Second}, + {2 * time.Second, "1 second %s", 1}, + {time.Minute, "%d seconds %s", time.Second}, + {2 * time.Minute, "1 minute %s", 1}, + {time.Hour, "%d minutes %s", time.Minute}, + {2 * time.Hour, "1 hour %s", 1}, + {Day, "%d hours %s", time.Hour}, + {2 * Day, "1 day %s", 1}, + {Week, "%d days %s", Day}, + {2 * Week, "1 week %s", 1}, + {Month, "%d weeks %s", Week}, + {2 * Month, "1 month %s", 1}, + {Year, "%d months %s", Month}, + {18 * Month, "1 year %s", 1}, + {2 * Year, "2 years %s", 1}, + {LongTime, "%d years %s", Year}, + {math.MaxInt64, "a long while %s", 1}, +} + +// RelTime formats a time into a relative string. +// +// It takes two times and two labels. In addition to the generic time +// delta string (e.g. 5 minutes), the labels are used applied so that +// the label corresponding to the smaller time is applied. +// +// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" +func RelTime(a, b time.Time, albl, blbl string) string { + return CustomRelTime(a, b, albl, blbl, defaultMagnitudes) +} + +// CustomRelTime formats a time into a relative string. +// +// It takes two times two labels and a table of relative time formats. +// In addition to the generic time delta string (e.g. 5 minutes), the +// labels are used applied so that the label corresponding to the +// smaller time is applied. +func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string { + lbl := albl + diff := b.Sub(a) + + if a.After(b) { + lbl = blbl + diff = a.Sub(b) + } + + n := sort.Search(len(magnitudes), func(i int) bool { + return magnitudes[i].D > diff + }) + + if n >= len(magnitudes) { + n = len(magnitudes) - 1 + } + mag := magnitudes[n] + args := []interface{}{} + escaped := false + for _, ch := range mag.Format { + if escaped { + switch ch { + case 's': + args = append(args, lbl) + case 'd': + args = append(args, diff/mag.DivBy) + } + escaped = false + } else { + escaped = ch == '%' + } + } + return fmt.Sprintf(mag.Format, args...) +} diff --git a/vendor/github.com/kevinburke/rest/LICENSE b/vendor/github.com/kevinburke/rest/LICENSE new file mode 100644 index 0000000..b1d4899 --- /dev/null +++ b/vendor/github.com/kevinburke/rest/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2016 Shyp, Inc. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/kevinburke/rest/restclient/client.go b/vendor/github.com/kevinburke/rest/restclient/client.go new file mode 100644 index 0000000..ca88014 --- /dev/null +++ b/vendor/github.com/kevinburke/rest/restclient/client.go @@ -0,0 +1,234 @@ +package restclient + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "runtime" + "strings" + "sync/atomic" + "time" + + "github.com/kevinburke/rest/resterror" +) + +type UploadType string + +// JSON specifies you'd like to upload JSON data. +var JSON UploadType = "application/json" + +// FormURLEncoded specifies you'd like to upload form-urlencoded data. +var FormURLEncoded UploadType = "application/x-www-form-urlencoded" + +const Version = "2.12.0" + +var ua string + +func init() { + gv := strings.Replace(runtime.Version(), "go", "", 1) + ua = fmt.Sprintf("rest-client/%s (https://github.com/kevinburke/rest) go/%s (%s/%s)", + Version, gv, runtime.GOOS, runtime.GOARCH) +} + +// Client is a generic Rest client for making HTTP requests. +type Client struct { + // Username for use in HTTP Basic Auth + ID string + // HTTP Client to use for making requests + Client *http.Client + // The base URL for all requests to this API, for example, + // "https://fax.twilio.com/v1" + Base string + // Set UploadType to JSON or FormURLEncoded to control how data is sent to + // the server. Defaults to FormURLEncoded. + UploadType UploadType + // ErrorParser is invoked when the client gets a 400-or-higher status code + // from the server. Defaults to rest.DefaultErrorParser. + ErrorParser func(*http.Response) error + + useBearerAuth bool + + // Password for use in HTTP Basic Auth, or single token in Bearer auth + token atomic.Value +} + +func (c *Client) Token() string { + l := c.token.Load() + s, _ := l.(string) + return s +} + +// New returns a new Client with HTTP Basic Auth with the given user and +// password. Base is the scheme+domain to hit for all requests. +func New(user, pass, base string) *Client { + + c := &Client{ + ID: user, + Client: defaultHttpClient, + Base: base, + UploadType: JSON, + ErrorParser: DefaultErrorParser, + } + c.token.Store(pass) + return c +} + +// NewBearerClient returns a new Client configured to use Bearer authentication. +func NewBearerClient(token, base string) *Client { + c := &Client{ + ID: "", + Client: defaultHttpClient, + Base: base, + UploadType: JSON, + ErrorParser: DefaultErrorParser, + useBearerAuth: true, + } + c.token.Store(token) + return c +} + +func (c *Client) UpdateToken(newToken string) { + c.token.Store(newToken) +} + +var defaultDialer = &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, +} + +// DialSocket configures c to use the provided socket and http.Transport to +// dial a Unix socket instead of a TCP port. +// +// If transport is nil, the settings from DefaultTransport are used. +func (c *Client) DialSocket(socket string, transport *http.Transport) { + dialSock := func(ctx context.Context, proto, addr string) (conn net.Conn, err error) { + return defaultDialer.DialContext(ctx, "unix", socket) + } + if transport == nil { + ht := http.DefaultTransport.(*http.Transport) + transport = &http.Transport{ + Proxy: ht.Proxy, + MaxIdleConns: ht.MaxIdleConns, + IdleConnTimeout: ht.IdleConnTimeout, + TLSHandshakeTimeout: ht.TLSHandshakeTimeout, + ExpectContinueTimeout: ht.ExpectContinueTimeout, + DialContext: dialSock, + } + } + if c.Client == nil { + // need to copy this so we don't modify the default client + c.Client = &http.Client{ + Timeout: defaultHttpClient.Timeout, + } + } + switch tp := c.Client.Transport.(type) { + // TODO both of these cases clobbber the existing transport which isn't + // ideal. + case nil, *Transport: + c.Client.Transport = &Transport{ + RoundTripper: transport, + Debug: DefaultTransport.Debug, + Output: DefaultTransport.Output, + } + case *http.Transport: + c.Client.Transport = transport + default: + panic(fmt.Sprintf("could not set DialSocket on unknown transport: %#v", tp)) + } +} + +func (c *Client) NewRequestWithContext(ctx context.Context, method, path string, body io.Reader) (*http.Request, error) { + if c == nil { + panic("cannot call NewRequestWithContext on nil *Client") + } + // see for example https://github.com/meterup/github-release/issues/1 - if + // the path contains the full URL including the base, strip it out + path = strings.TrimPrefix(path, c.Base) + req, err := http.NewRequestWithContext(ctx, method, c.Base+path, body) + if err != nil { + return nil, err + } + token := c.Token() + switch { + case c.useBearerAuth && token != "": + req.Header.Add("Authorization", "Bearer "+token) + case !c.useBearerAuth && (c.ID != "" || token != ""): + req.SetBasicAuth(c.ID, token) + } + req.Header.Add("User-Agent", ua) + req.Header.Add("Accept", "application/json") + req.Header.Add("Accept-Charset", "utf-8") + if method == "POST" || method == "PUT" { + uploadType := c.UploadType + if uploadType == "" { + uploadType = JSON + } + req.Header.Add("Content-Type", string(uploadType)+"; charset=utf-8") + } + return req, nil +} + +// NewRequest creates a new Request and sets basic auth based on the client's +// authentication information. +func (c *Client) NewRequest(method, path string, body io.Reader) (*http.Request, error) { + return c.NewRequestWithContext(context.Background(), method, path, body) +} + +// Do performs the HTTP request. If the HTTP response is in the 2xx range, +// Unmarshal the response body into v. If the response status code is 400 or +// above, attempt to Unmarshal the response into an Error. Otherwise return +// a generic http error. +func (c *Client) Do(r *http.Request, v interface{}) error { + var res *http.Response + var err error + if c.Client == nil { + res, err = defaultHttpClient.Do(r) + } else { + res, err = c.Client.Do(r) + } + if err != nil { + return err + } + defer res.Body.Close() + if res.StatusCode >= 400 { + if c.ErrorParser != nil { + return c.ErrorParser(res) + } + return DefaultErrorParser(res) + } + + resBody, err := io.ReadAll(res.Body) + if err != nil { + return err + } + if v == nil || res.StatusCode == http.StatusNoContent { + return nil + } else { + return json.Unmarshal(resBody, v) + } +} + +// DefaultErrorParser attempts to parse the response body as a rest.Error. If +// it cannot do so, return an error containing the entire response body. +func DefaultErrorParser(resp *http.Response) error { + resBody, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + defer resp.Body.Close() + rerr := new(resterror.Error) + err = json.Unmarshal(resBody, rerr) + if err != nil { + return fmt.Errorf("invalid response body: %s", string(resBody)) + } + if rerr.Title == "" { + return fmt.Errorf("invalid response body: %s", string(resBody)) + } else { + rerr.Status = resp.StatusCode + return rerr + } +} diff --git a/vendor/github.com/kevinburke/rest/restclient/transport.go b/vendor/github.com/kevinburke/rest/restclient/transport.go new file mode 100644 index 0000000..dd7b894 --- /dev/null +++ b/vendor/github.com/kevinburke/rest/restclient/transport.go @@ -0,0 +1,84 @@ +package restclient + +import ( + "bytes" + "io" + "net/http" + "net/http/httputil" + "os" +) + +// DefaultTransport is like http.DefaultTransport, but prints the contents of +// HTTP requests to os.Stderr if the DEBUG_HTTP_TRAFFIC environment variable is +// set to true. +var DefaultTransport *Transport +var defaultHttpClient *http.Client + +func init() { + DefaultTransport = &Transport{ + RoundTripper: http.DefaultTransport, + Debug: os.Getenv("DEBUG_HTTP_TRAFFIC") == "true", + Output: os.Stderr, + } + defaultHttpClient = &http.Client{ + Transport: DefaultTransport, + } +} + +// Transport implements HTTP round trips, but adds hooks for debugging the HTTP +// request. +type Transport struct { + // The underlying RoundTripper. + RoundTripper http.RoundTripper + // Whether to write the HTTP request and response contents to Output. + Debug bool + // If Debug is true, write the HTTP request and response contents here. If + // Output is nil, os.Stderr will be used. + Output io.Writer +} + +func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) { + if t == nil { + panic("nil Transport") + } + var w io.ReadWriter = nil + if t.Debug { + w = new(bytes.Buffer) + bits, err := httputil.DumpRequestOut(req, true) + if err != nil { + return nil, err + } + if len(bits) > 0 && bits[len(bits)-1] != '\n' { + bits = append(bits, '\n') + } + w.Write(bits) + } + var res *http.Response + var err error + if t.RoundTripper == nil { + res, err = http.DefaultTransport.RoundTrip(req) + } else { + res, err = t.RoundTripper.RoundTrip(req) + } + if err != nil { + return res, err + } + if t.Debug { + bits, err := httputil.DumpResponse(res, true) + if err != nil { + return res, err + } + if len(bits) > 0 && bits[len(bits)-1] != '\n' { + bits = append(bits, '\n') + } + w.Write(bits) + if t.Output == nil { + t.Output = os.Stderr + } + _, err = io.Copy(t.Output, w) + if err != nil { + return res, err + } + } + return res, err +} diff --git a/vendor/github.com/kevinburke/rest/resterror/resterror.go b/vendor/github.com/kevinburke/rest/resterror/resterror.go new file mode 100644 index 0000000..a41a778 --- /dev/null +++ b/vendor/github.com/kevinburke/rest/resterror/resterror.go @@ -0,0 +1,38 @@ +package resterror + +import "fmt" + +// Error implements the HTTP Problem spec laid out here: +// https://tools.ietf.org/html/rfc7807 +type Error struct { + // The main error message. Should be short enough to fit in a phone's + // alert box. Do not end this message with a period. + Title string `json:"title"` + + // Id of this error message ("forbidden", "invalid_parameter", etc) + ID string `json:"id"` + + // More information about what went wrong. + Detail string `json:"detail,omitempty"` + + // Path to the object that's in error. + Instance string `json:"instance,omitempty"` + + // Link to more information about the error (Zendesk, API docs, etc). + Type string `json:"type,omitempty"` + + // HTTP status code of the error. + Status int `json:"status,omitempty"` +} + +func (e *Error) Error() string { + return e.Title +} + +func (e *Error) String() string { + if e.Detail != "" { + return fmt.Sprintf("rest: %s. %s", e.Title, e.Detail) + } else { + return fmt.Sprintf("rest: %s", e.Title) + } +} diff --git a/vendor/github.com/tomnomnom/linkheader/.gitignore b/vendor/github.com/tomnomnom/linkheader/.gitignore new file mode 100644 index 0000000..0a00dde --- /dev/null +++ b/vendor/github.com/tomnomnom/linkheader/.gitignore @@ -0,0 +1,2 @@ +cpu.out +linkheader.test diff --git a/vendor/github.com/tomnomnom/linkheader/.travis.yml b/vendor/github.com/tomnomnom/linkheader/.travis.yml new file mode 100644 index 0000000..cfda086 --- /dev/null +++ b/vendor/github.com/tomnomnom/linkheader/.travis.yml @@ -0,0 +1,6 @@ +language: go + +go: + - 1.6 + - 1.7 + - tip diff --git a/vendor/github.com/tomnomnom/linkheader/CONTRIBUTING.mkd b/vendor/github.com/tomnomnom/linkheader/CONTRIBUTING.mkd new file mode 100644 index 0000000..0339bec --- /dev/null +++ b/vendor/github.com/tomnomnom/linkheader/CONTRIBUTING.mkd @@ -0,0 +1,10 @@ +# Contributing + +* Raise an issue if appropriate +* Fork the repo +* Bootstrap the dev dependencies (run `./script/bootstrap`) +* Make your changes +* Use [gofmt](https://golang.org/cmd/gofmt/) +* Make sure the tests pass (run `./script/test`) +* Make sure the linters pass (run `./script/lint`) +* Issue a pull request diff --git a/vendor/github.com/tomnomnom/linkheader/LICENSE b/vendor/github.com/tomnomnom/linkheader/LICENSE new file mode 100644 index 0000000..55192df --- /dev/null +++ b/vendor/github.com/tomnomnom/linkheader/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Tom Hudson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/tomnomnom/linkheader/README.mkd b/vendor/github.com/tomnomnom/linkheader/README.mkd new file mode 100644 index 0000000..2a949ca --- /dev/null +++ b/vendor/github.com/tomnomnom/linkheader/README.mkd @@ -0,0 +1,35 @@ +# Golang Link Header Parser + +Library for parsing HTTP Link headers. Requires Go 1.6 or higher. + +Docs can be found on [the GoDoc page](https://godoc.org/github.com/tomnomnom/linkheader). + +[![Build Status](https://travis-ci.org/tomnomnom/linkheader.svg)](https://travis-ci.org/tomnomnom/linkheader) + +## Basic Example + +```go +package main + +import ( + "fmt" + + "github.com/tomnomnom/linkheader" +) + +func main() { + header := "; rel=\"next\"," + + "; rel=\"last\"" + links := linkheader.Parse(header) + + for _, link := range links { + fmt.Printf("URL: %s; Rel: %s\n", link.URL, link.Rel) + } +} + +// Output: +// URL: https://api.github.com/user/58276/repos?page=2; Rel: next +// URL: https://api.github.com/user/58276/repos?page=2; Rel: last +``` + + diff --git a/vendor/github.com/tomnomnom/linkheader/main.go b/vendor/github.com/tomnomnom/linkheader/main.go new file mode 100644 index 0000000..6b81321 --- /dev/null +++ b/vendor/github.com/tomnomnom/linkheader/main.go @@ -0,0 +1,151 @@ +// Package linkheader provides functions for parsing HTTP Link headers +package linkheader + +import ( + "fmt" + "strings" +) + +// A Link is a single URL and related parameters +type Link struct { + URL string + Rel string + Params map[string]string +} + +// HasParam returns if a Link has a particular parameter or not +func (l Link) HasParam(key string) bool { + for p := range l.Params { + if p == key { + return true + } + } + return false +} + +// Param returns the value of a parameter if it exists +func (l Link) Param(key string) string { + for k, v := range l.Params { + if key == k { + return v + } + } + return "" +} + +// String returns the string representation of a link +func (l Link) String() string { + + p := make([]string, 0, len(l.Params)) + for k, v := range l.Params { + p = append(p, fmt.Sprintf("%s=\"%s\"", k, v)) + } + if l.Rel != "" { + p = append(p, fmt.Sprintf("%s=\"%s\"", "rel", l.Rel)) + } + return fmt.Sprintf("<%s>; %s", l.URL, strings.Join(p, "; ")) +} + +// Links is a slice of Link structs +type Links []Link + +// FilterByRel filters a group of Links by the provided Rel attribute +func (l Links) FilterByRel(r string) Links { + links := make(Links, 0) + for _, link := range l { + if link.Rel == r { + links = append(links, link) + } + } + return links +} + +// String returns the string representation of multiple Links +// for use in HTTP responses etc +func (l Links) String() string { + if l == nil { + return fmt.Sprint(nil) + } + + var strs []string + for _, link := range l { + strs = append(strs, link.String()) + } + return strings.Join(strs, ", ") +} + +// Parse parses a raw Link header in the form: +// ; rel="foo", ; rel="bar"; wat="dis" +// returning a slice of Link structs +func Parse(raw string) Links { + var links Links + + // One chunk: ; rel="foo" + for _, chunk := range strings.Split(raw, ",") { + + link := Link{URL: "", Rel: "", Params: make(map[string]string)} + + // Figure out what each piece of the chunk is + for _, piece := range strings.Split(chunk, ";") { + + piece = strings.Trim(piece, " ") + if piece == "" { + continue + } + + // URL + if piece[0] == '<' && piece[len(piece)-1] == '>' { + link.URL = strings.Trim(piece, "<>") + continue + } + + // Params + key, val := parseParam(piece) + if key == "" { + continue + } + + // Special case for rel + if strings.ToLower(key) == "rel" { + link.Rel = val + } else { + link.Params[key] = val + } + } + + if link.URL != "" { + links = append(links, link) + } + } + + return links +} + +// ParseMultiple is like Parse, but accepts a slice of headers +// rather than just one header string +func ParseMultiple(headers []string) Links { + links := make(Links, 0) + for _, header := range headers { + links = append(links, Parse(header)...) + } + return links +} + +// parseParam takes a raw param in the form key="val" and +// returns the key and value as seperate strings +func parseParam(raw string) (key, val string) { + + parts := strings.SplitN(raw, "=", 2) + if len(parts) == 1 { + return parts[0], "" + } + if len(parts) != 2 { + return "", "" + } + + key = parts[0] + val = strings.Trim(parts[1], "\"") + + return key, val + +} diff --git a/vendor/github.com/voxelbrain/goptions/.gitignore b/vendor/github.com/voxelbrain/goptions/.gitignore new file mode 100644 index 0000000..bf9dfdc --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/.gitignore @@ -0,0 +1,2 @@ +*.swp +.DS_Store diff --git a/vendor/github.com/voxelbrain/goptions/CHANGELOG.md b/vendor/github.com/voxelbrain/goptions/CHANGELOG.md new file mode 100644 index 0000000..32ec7ca --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/CHANGELOG.md @@ -0,0 +1,96 @@ +# Changelog +## 2.5.6 +### Bug fixes + +* Unexported fields are now ignored + +### Minor changes + +* Examples for Verbs and Remainder in documentation + +## 2.5.4 +### Bugfixes + +* Fix typo in documentation + +## 2.5.3 +### Bugfixes + +* Remove placeholders from LICENSE +* Add CONTROBUTORS + +## 2.5.2 +### Bugfixes + +* Bring `examples/readme_example.go` and `README.md` up to date +* Rewrite formatter + +## 2.5.1 +### Bugfixes + +* Make arrays of `goptions.Marshaler` work + +## 2.5.0 +### New features + +* Add support for `int32` and `int64` +* Add support for `float32` and `float64` + +### Bugfixes + +* Fix a bug where the name of a unknown type would not be properly + printed +* Fix checks whether to use `os.Stdin` or `os.Stdout` when "-" is given for a + `*os.File` +* Fix an test example where the output to `os.Stderr` is apparently + not evaluated anymore. + +## 2.4.1 +### Bugfixes + +* Code was not compilable due to temporary [maintainer](http://github.com/surma) idiocy + (Thanks [akrennmair](http://github.com/akrennmair)) + +## 2.4.0 +### New features + +* Gave `goptions.FlagSet` a `ParseAndFail()` method + +## 2.3.0 +### New features + +* Add support for `time.Duration` + +## 2.2.0 +### New features + +* Add support for `*net.TCPAddr` +* Add support for `*net/url.URL` + +### Bugfixes + +* Fix behaviour of `[]bool` fields + +## 2.1.0 +### New features + +* `goptions.Verbs` is of type `string` and will have selected verb name as value + after parsing. + +## 2.0.0 +### Breaking changes + +* Disallow multiple flag names for one member +* Remove `accumulate` option in favor of generic array support + +### New features + +* Add convenience function `ParseAndFail` to make common usage of the library + a one-liner (see `readme_example.go`) +* Add a `Marshaler` interface to enable thrid-party types +* Add support for slices (and thereby for mutiple flag definitions) + +### Minor changes + +* Refactoring to get more flexibility +* Make a flag's default value accessible in the template context diff --git a/vendor/github.com/voxelbrain/goptions/CONTRIBUTORS.md b/vendor/github.com/voxelbrain/goptions/CONTRIBUTORS.md new file mode 100644 index 0000000..65d757b --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/CONTRIBUTORS.md @@ -0,0 +1,7 @@ +Contributors +============ + +These people have contributed to goptions's design and implementation: + + * Andreas Krennmair + * GDG Berlin Golang diff --git a/vendor/github.com/voxelbrain/goptions/LICENSE.txt b/vendor/github.com/voxelbrain/goptions/LICENSE.txt new file mode 100644 index 0000000..44f1148 --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/LICENSE.txt @@ -0,0 +1,24 @@ +Copyright (c) 2012-2013, voxelbrain UG, Germany +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the voxelbrain UG 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 voxelbrain UG 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. diff --git a/vendor/github.com/voxelbrain/goptions/README.md b/vendor/github.com/voxelbrain/goptions/README.md new file mode 100644 index 0000000..0f31aef --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/README.md @@ -0,0 +1,114 @@ +`goptions` implements a flexible parser for command line options. + +Key targets were the support for both long and short flag versions, mutually +exclusive flags, and verbs. Flags and their corresponding variables are defined +by the tags in a (possibly anonymous) struct. + +![](https://circleci.com/gh/voxelbrain/goptions.png?circle-token=27cd98362d475cfa8c586565b659b2204733f25c) + +# Example + +```go +package main + +import ( + "github.com/voxelbrain/goptions" + "os" + "time" +) + +func main() { + options := struct { + Servers []string `goptions:"-s, --server, obligatory, description='Servers to connect to'"` + Password string `goptions:"-p, --password, description='Don\\'t prompt for password'"` + Timeout time.Duration `goptions:"-t, --timeout, description='Connection timeout in seconds'"` + Help goptions.Help `goptions:"-h, --help, description='Show this help'"` + + goptions.Verbs + Execute struct { + Command string `goptions:"--command, mutexgroup='input', description='Command to exectute', obligatory"` + Script *os.File `goptions:"--script, mutexgroup='input', description='Script to exectute', rdonly"` + } `goptions:"execute"` + Delete struct { + Path string `goptions:"-n, --name, obligatory, description='Name of the entity to be deleted'"` + Force bool `goptions:"-f, --force, description='Force removal'"` + } `goptions:"delete"` + }{ // Default values goes here + Timeout: 10 * time.Second, + } + goptions.ParseAndFail(&options) +} +``` + +``` +$ go run examples/readme_example.go --help +Usage: a.out [global options] [verb options] + +Global options: + -s, --server Servers to connect to (*) + -p, --password Don't prompt for password + -t, --timeout Connection timeout in seconds (default: 10s) + -h, --help Show this help + +Verbs: + delete: + -n, --name Name of the entity to be deleted (*) + -f, --force Force removal + execute: + --command Command to exectute (*) + --script Script to exectute +``` + +# Quick Reference + +## goptions + +Each field of your struct can be tagged with a `goptions` + +```go + FieldName type `goptions:"-S, --long, options..."` +``` + +Where the short options (`-S`) are declared with a single dash and +long options (`--long`) are declared with two dashes. Either or +both may be declared. + +After the short/long option names are one or more of the following: + +### Global Options + +* description='...' +* obligatory +* mutexgroup='GROUP_NAME' + +### os.File specific + +* create +* append +* rdonly +* wronly +* rdwr +* excl +* sync +* trunc +* perm=0777 + +## Supported Types + +* bool +* string +* float64 +* float32 +* int +* int64 +* int32 +* goptions.Help +* *os.File +* *net.TCPAddr +* *url.URL +* time.Duration + + + +--- +Version 2.5.11 diff --git a/vendor/github.com/voxelbrain/goptions/circle.yml b/vendor/github.com/voxelbrain/goptions/circle.yml new file mode 100644 index 0000000..b399d44 --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/circle.yml @@ -0,0 +1,9 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/golang + steps: + - checkout + - run: go get -d . + - run: go test diff --git a/vendor/github.com/voxelbrain/goptions/flag.go b/vendor/github.com/voxelbrain/goptions/flag.go new file mode 100644 index 0000000..0af05b4 --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/flag.go @@ -0,0 +1,90 @@ +package goptions + +import ( + "fmt" + "reflect" + "strings" +) + +// Flag represents a single flag of a FlagSet. +type Flag struct { + Short string + Long string + MutexGroups []string + Description string + Obligatory bool + WasSpecified bool + value reflect.Value + optionMeta map[string]interface{} + DefaultValue interface{} +} + +// Return the name of the flag preceding the right amount of dashes. +// The long name is preferred. If no name has been specified, "" +// will be returned. +func (f *Flag) Name() string { + if len(f.Long) > 0 { + return "--" + f.Long + } + if len(f.Short) > 0 { + return "-" + f.Short + } + return "" +} + +// NeedsExtraValue returns true if the flag expects a separate value. +func (f *Flag) NeedsExtraValue() bool { + // Explicit over implicit + if f.value.Type() == reflect.TypeOf(new([]bool)).Elem() || + f.value.Type() == reflect.TypeOf(new(bool)).Elem() { + return false + } + if _, ok := f.value.Interface().(Help); ok { + return false + } + return true +} + +// IsMulti returns true if the flag can be specified multiple times. +func (f *Flag) IsMulti() bool { + if f.value.Kind() == reflect.Slice { + return true + } + return false +} + +func isShort(arg string) bool { + return strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, "--") && len(arg) >= 2 +} + +func isLong(arg string) bool { + return strings.HasPrefix(arg, "--") && len(arg) >= 3 +} + +func (f *Flag) Handles(arg string) bool { + return (isShort(arg) && arg[1:2] == f.Short) || + (isLong(arg) && arg[2:] == f.Long) + +} + +func (f *Flag) Parse(args []string) ([]string, error) { + param, value := args[0], "" + if f.NeedsExtraValue() && + (len(args) < 2 || (isShort(param) && len(param) > 2)) { + return args, fmt.Errorf("Flag %s needs an argument", f.Name()) + } + if f.WasSpecified && !f.IsMulti() { + return args, fmt.Errorf("Flag %s can only be specified once", f.Name()) + } + if isShort(param) && len(param) > 2 { + // Short flag cluster + args[0] = "-" + param[2:] + } else if f.NeedsExtraValue() { + value = args[1] + args = args[2:] + } else { + args = args[1:] + } + f.WasSpecified = true + return args, f.setValue(value) +} diff --git a/vendor/github.com/voxelbrain/goptions/flagset.go b/vendor/github.com/voxelbrain/goptions/flagset.go new file mode 100644 index 0000000..cf31038 --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/flagset.go @@ -0,0 +1,240 @@ +package goptions + +import ( + "errors" + "fmt" + "io" + "os" + "reflect" + "strings" + "sync" +) + +// A FlagSet represents one set of flags which belong to one particular program. +// A FlagSet is also used to represent a subset of flags belonging to one verb. +type FlagSet struct { + // This HelpFunc will be called when PrintHelp() is called. + HelpFunc + // Name of the program. Might be used by HelpFunc. + Name string + helpFlag *Flag + remainderFlag *Flag + shortMap map[string]*Flag + longMap map[string]*Flag + verbFlag *Flag + // Global option flags + Flags []*Flag + // Verbs and corresponding FlagSets + Verbs map[string]*FlagSet + parent *FlagSet +} + +// NewFlagSet returns a new FlagSet containing all the flags which result from +// parsing the tags of the struct. Said struct as to be passed to the function +// as a pointer. +// If a tag line is erroneous, NewFlagSet() panics as this is considered a +// compile time error rather than a runtme error. +func NewFlagSet(name string, v interface{}) *FlagSet { + structValue := reflect.ValueOf(v) + if structValue.Kind() != reflect.Ptr { + panic("Value type is not a pointer to a struct") + } + structValue = structValue.Elem() + if structValue.Kind() != reflect.Struct { + panic("Value type is not a pointer to a struct") + } + return newFlagset(name, structValue, nil) +} + +// Internal version which skips type checking and takes the "parent"'s +// remainder flag as a parameter. +func newFlagset(name string, structValue reflect.Value, parent *FlagSet) *FlagSet { + var once sync.Once + r := &FlagSet{ + Name: name, + Flags: make([]*Flag, 0), + HelpFunc: DefaultHelpFunc, + parent: parent, + } + + if parent != nil && parent.remainderFlag != nil { + r.remainderFlag = parent.remainderFlag + } + + var i int + // Parse Option fields + for i = 0; i < structValue.Type().NumField(); i++ { + // Skip unexported fields + if StartsWithLowercase(structValue.Type().Field(i).Name) { + continue + } + + fieldValue := structValue.Field(i) + tag := structValue.Type().Field(i).Tag.Get("goptions") + flag, err := parseStructField(fieldValue, tag) + + if err != nil { + panic(fmt.Sprintf("Invalid struct field: %s", err)) + } + if fieldValue.Type().Name() == "Verbs" { + r.verbFlag = flag + break + } + if fieldValue.Type().Name() == "Help" { + r.helpFlag = flag + } + if fieldValue.Type().Name() == "Remainder" && r.remainderFlag == nil { + r.remainderFlag = flag + } + + if len(tag) != 0 { + r.Flags = append(r.Flags, flag) + } + } + + // Parse verb fields + for i++; i < structValue.Type().NumField(); i++ { + once.Do(func() { + r.Verbs = make(map[string]*FlagSet) + }) + fieldValue := structValue.Field(i) + tag := structValue.Type().Field(i).Tag.Get("goptions") + r.Verbs[tag] = newFlagset(tag, fieldValue, r) + } + r.createMaps() + return r +} + +var ( + ErrHelpRequest = errors.New("Request for Help") +) + +// Parse takes the command line arguments and sets the corresponding values +// in the FlagSet's struct. +func (fs *FlagSet) Parse(args []string) (err error) { + // Parse global flags + for len(args) > 0 { + if !((isLong(args[0]) && fs.hasLongFlag(args[0][2:])) || + (isShort(args[0]) && fs.hasShortFlag(args[0][1:2]))) { + break + } + f := fs.FlagByName(args[0]) + args, err = f.Parse(args) + if err != nil { + return + } + if f == fs.helpFlag && f.WasSpecified { + return ErrHelpRequest + } + } + + // Process verb + if len(args) > 0 { + if verb, ok := fs.Verbs[args[0]]; ok { + fs.verbFlag.value.Set(reflect.ValueOf(Verbs(args[0]))) + err := verb.Parse(args[1:]) + if err != nil { + return err + } + args = args[0:0] + } + } + + // Process remainder + if len(args) > 0 { + if fs.remainderFlag == nil { + return fmt.Errorf("Invalid trailing arguments: %v", args) + } + remainder := reflect.MakeSlice(fs.remainderFlag.value.Type(), len(args), len(args)) + reflect.Copy(remainder, reflect.ValueOf(args)) + fs.remainderFlag.value.Set(remainder) + } + + // Check for unset, obligatory, single Flags + for _, f := range fs.Flags { + if f.Obligatory && !f.WasSpecified && len(f.MutexGroups) == 0 { + return fmt.Errorf("%s must be specified", f.Name()) + } + } + + // Check for multiple set Flags in one mutex group + // Check also for unset, obligatory mutex groups + mgs := fs.MutexGroups() + for _, mg := range mgs { + if !mg.IsValid() { + return fmt.Errorf("Exactly one of %s must be specified", strings.Join(mg.Names(), ", ")) + } + } + return nil +} + +func (fs *FlagSet) createMaps() { + fs.longMap = make(map[string]*Flag) + fs.shortMap = make(map[string]*Flag) + for _, flag := range fs.Flags { + fs.longMap[flag.Long] = flag + fs.shortMap[flag.Short] = flag + } +} + +func (fs *FlagSet) hasLongFlag(fname string) bool { + _, ok := fs.longMap[fname] + return ok +} + +func (fs *FlagSet) hasShortFlag(fname string) bool { + _, ok := fs.shortMap[fname] + return ok +} + +func (fs *FlagSet) FlagByName(fname string) *Flag { + if isShort(fname) && fs.hasShortFlag(fname[1:2]) { + return fs.shortMap[fname[1:2]] + } else if isLong(fname) && fs.hasLongFlag(fname[2:]) { + return fs.longMap[fname[2:]] + } + return nil +} + +// MutexGroups returns a map of Flag lists which contain mutually +// exclusive flags. +func (fs *FlagSet) MutexGroups() map[string]MutexGroup { + r := make(map[string]MutexGroup) + for _, f := range fs.Flags { + for _, mg := range f.MutexGroups { + if len(mg) == 0 { + continue + } + if _, ok := r[mg]; !ok { + r[mg] = make(MutexGroup, 0) + } + r[mg] = append(r[mg], f) + } + } + return r +} + +// Prints the FlagSet's help to the given writer. +func (fs *FlagSet) PrintHelp(w io.Writer) { + fs.HelpFunc(w, fs) +} + +func (fs *FlagSet) ParseAndFail(w io.Writer, args []string) { + err := fs.Parse(args) + if err != nil { + errCode := 0 + if err != ErrHelpRequest { + errCode = 1 + fmt.Fprintf(w, "Error: %s\n", err) + } + fs.PrintHelp(w) + os.Exit(errCode) + } +} + +func StartsWithLowercase(s string) bool { + if len(s) <= 0 { + return false + } + return strings.ToLower(s)[0] == s[0] +} diff --git a/vendor/github.com/voxelbrain/goptions/goptions.go b/vendor/github.com/voxelbrain/goptions/goptions.go new file mode 100644 index 0000000..992f7ee --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/goptions.go @@ -0,0 +1,99 @@ +/* +package goptions implements a flexible parser for command line options. + +Key targets were the support for both long and short flag versions, mutually +exclusive flags, and verbs. Flags and their corresponding variables are defined +by the tags in a (possibly anonymous) struct. + + var options struct { + Name string `goptions:"-n, --name"` + Force bool `goptions:"-f, --force"` + Verbosity int `goptions:"-v, --verbose"` + } + +Short flags can be combined (e.g. `-nfv`). Long flags take their value after a +separating space. The equals notation (`--long-flag=value`) is NOT supported +right now. + +Every member of the struct which is supposed to catch a command line value +has to have a "goptions" tag. The contains the short and long flag names for this +member but can additionally specify any of these options below. + + obligatory - Flag must be specified. Otherwise an error will be returned + when Parse() is called. + description='...' - Set the description for this particular flag. Will be + used by the HelpFunc. + mutexgroup='...' - Add this flag to a MutexGroup. Only one flag of the + ones sharing a MutexGroup can be set. Otherwise an error + will be returned when Parse() is called. If one flag in a + MutexGroup is `obligatory` one flag of the group must be + specified. A flag can be in multiple MutexGroups at once. + +Depending on the type of the struct member, additional options might become available: + + Type: *os.File + The given string is interpreted as a path to a file. If the string is "-" + os.Stdin or os.Stdout will be used. os.Stdin will be returned, if the + `rdonly` flag was set. os.Stdout will be returned, if `wronly` was set. + Available options: + Any combination of create, append, rdonly, wronly, rdwr, + excl, sync, trunc and perm can be specified and correspond directly with + the combination of the homonymous flags in the os package. + + Type: *net.TCPAddr + The given string is interpreted as a tcp address. It is passed to + net.ResolvTCPAddr() with "tcp" as the network type identifier. + + Type: *net/url.URL + The given string is parsed by net/url.Parse() + + Type: time.Duration + The given string is parsed by time.ParseDuration() + +If a member is a slice type, multiple definitions of the flags are possible. For each +specification the underlying type will be used. + + var options struct { + Servers []string `goptions:"-s, --server, description='Servers to connect to'"` + }{} + +goptions also has support for verbs. Each verb accepts its own set of flags which +take exactly the same tag format as global options. For an usage example of verbs +see the PrintHelp() example. +*/ +package goptions + +import ( + "os" + "path/filepath" +) + +const ( + VERSION = "2.5.11" +) + +var ( + globalFlagSet *FlagSet +) + +// ParseAndFail is a convenience function to parse os.Args[1:] and print +// the help if an error occurs. This should cover 90% of this library's +// applications. +func ParseAndFail(v interface{}) { + globalFlagSet = NewFlagSet(filepath.Base(os.Args[0]), v) + globalFlagSet.ParseAndFail(os.Stderr, os.Args[1:]) +} + +// Parse parses the command-line flags from os.Args[1:]. +func Parse(v interface{}) error { + globalFlagSet = NewFlagSet(filepath.Base(os.Args[0]), v) + return globalFlagSet.Parse(os.Args[1:]) +} + +// PrintHelp renders the default help to os.Stderr. +func PrintHelp() { + if globalFlagSet == nil { + panic("Must call Parse() before PrintHelp()") + } + globalFlagSet.PrintHelp(os.Stderr) +} diff --git a/vendor/github.com/voxelbrain/goptions/helpfunc.go b/vendor/github.com/voxelbrain/goptions/helpfunc.go new file mode 100644 index 0000000..3b0165e --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/helpfunc.go @@ -0,0 +1,72 @@ +package goptions + +import ( + "io" + "sync" + "text/tabwriter" + "text/template" +) + +// HelpFunc is the signature of a function responsible for printing the help. +type HelpFunc func(w io.Writer, fs *FlagSet) + +// Generates a new HelpFunc taking a `text/template.Template`-formatted +// string as an argument. The resulting template will be executed with the FlagSet +// as its data. +func NewTemplatedHelpFunc(tpl string) HelpFunc { + var once sync.Once + var t *template.Template + return func(w io.Writer, fs *FlagSet) { + once.Do(func() { + t = template.Must(template.New("helpTemplate").Parse(tpl)) + }) + err := t.Execute(w, fs) + if err != nil { + panic(err) + } + } +} + +const ( + _DEFAULT_HELP = "\xffUsage: {{.Name}} [global options] {{with .Verbs}} [verb options]{{end}}\n" + + "\n" + + "Global options:\xff" + + "{{range .Flags}}" + + "\n\t" + + "\t{{with .Short}}" + "-{{.}}," + "{{end}}" + + "\t{{with .Long}}" + "--{{.}}" + "{{end}}" + + "\t{{.Description}}" + + "{{with .DefaultValue}}" + + " (default: {{.}})" + + "{{end}}" + + "{{if .Obligatory}}" + + " (*)" + + "{{end}}" + + "{{end}}" + + "\xff\n\n{{with .Verbs}}Verbs:\xff" + + "{{range .}}" + + "\xff\n {{.Name}}:\xff" + + "{{range .Flags}}" + + "\n\t" + + "\t{{with .Short}}" + "-{{.}}," + "{{end}}" + + "\t{{with .Long}}" + "--{{.}}" + "{{end}}" + + "\t{{.Description}}" + + "{{with .DefaultValue}}" + + " (default: {{.}})" + + "{{end}}" + + "{{if .Obligatory}}" + + " (*)" + + "{{end}}" + + "{{end}}" + + "{{end}}" + + "{{end}}" + + "\n" +) + +// DefaultHelpFunc is a HelpFunc which renders the default help template and pipes +// the output through a text/tabwriter.Writer before flushing it to the output. +func DefaultHelpFunc(w io.Writer, fs *FlagSet) { + tw := tabwriter.NewWriter(w, 4, 4, 1, ' ', tabwriter.StripEscape|tabwriter.DiscardEmptyColumns) + NewTemplatedHelpFunc(_DEFAULT_HELP)(tw, fs) + tw.Flush() +} diff --git a/vendor/github.com/voxelbrain/goptions/marshaler.go b/vendor/github.com/voxelbrain/goptions/marshaler.go new file mode 100644 index 0000000..b79cc60 --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/marshaler.go @@ -0,0 +1,5 @@ +package goptions + +type Marshaler interface { + MarshalGoption(s string) error +} diff --git a/vendor/github.com/voxelbrain/goptions/mutexgroup.go b/vendor/github.com/voxelbrain/goptions/mutexgroup.go new file mode 100644 index 0000000..14c2053 --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/mutexgroup.go @@ -0,0 +1,48 @@ +package goptions + +// A MutexGroup holds a set of flags which are mutually exclusive and cannot +// be specified at the same time. +type MutexGroup []*Flag + +// IsObligatory returns true if exactly one of the flags in the MutexGroup has +// to be specified +func (mg MutexGroup) IsObligatory() bool { + for _, flag := range mg { + if flag.Obligatory { + return true + } + } + return false +} + +func (mg MutexGroup) WasSpecified() bool { + for _, flag := range mg { + if flag.WasSpecified { + return true + } + } + return false +} + +// IsValid checks if the flags in the MutexGroup describe a valid state. +// I.e. At most one has been specified or – if it is an obligatory MutexGroup – +// exactly one has been specified. +func (mg MutexGroup) IsValid() bool { + c := 0 + for _, flag := range mg { + if flag.WasSpecified { + c++ + } + } + return c <= 1 && (!mg.IsObligatory() || c == 1) +} + +// Names is a convenience function to return the array of names of the flags +// in the MutexGroup. +func (mg MutexGroup) Names() []string { + r := make([]string, len(mg)) + for i, flag := range mg { + r[i] = flag.Name() + } + return r +} diff --git a/vendor/github.com/voxelbrain/goptions/options.go b/vendor/github.com/voxelbrain/goptions/options.go new file mode 100644 index 0000000..86ed6bc --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/options.go @@ -0,0 +1,136 @@ +package goptions + +import ( + "fmt" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +type optionFunc func(f *Flag, option, value string) error +type optionMap map[string]optionFunc + +var ( + typeOptionMap = map[reflect.Type]optionMap{ + // Global options + nil: optionMap{ + "description": description, + "obligatory": obligatory, + "mutexgroup": mutexgroup, + }, + reflect.TypeOf(new(time.Time)).Elem(): optionMap{ + "format": time_format, + }, + reflect.TypeOf(new(*os.File)).Elem(): optionMap{ + "create": initOptionMeta(file_create, "file_mode", 0), + "append": initOptionMeta(file_append, "file_mode", 0), + "rdonly": initOptionMeta(file_rdonly, "file_mode", 0), + "wronly": initOptionMeta(file_wronly, "file_mode", 0), + "rdwr": initOptionMeta(file_rdwr, "file_mode", 0), + "excl": initOptionMeta(file_excl, "file_mode", 0), + "sync": initOptionMeta(file_sync, "file_mode", 0), + "trunc": initOptionMeta(file_trunc, "file_mode", 0), + "perm": file_perm, + }, + } +) + +// Wraps another optionFunc and inits optionMeta[field] with value if it does +// not have one already. +func initOptionMeta(fn optionFunc, field string, init_value interface{}) optionFunc { + return func(f *Flag, option, value string) error { + if _, ok := f.optionMeta[field]; !ok { + f.optionMeta[field] = init_value + } + return fn(f, option, value) + } +} + +func description(f *Flag, option, value string) error { + f.Description = strings.Replace(value, `\`, ``, -1) + return nil +} + +func obligatory(f *Flag, option, value string) error { + f.Obligatory = true + return nil +} + +func mutexgroup(f *Flag, option, value string) error { + if len(value) <= 0 { + return fmt.Errorf("Mutexgroup option needs a value") + } + for _, group := range strings.Split(value, ",") { + f.MutexGroups = append(f.MutexGroups, group) + } + return nil +} + +func file_create(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_CREATE + return nil +} + +func file_append(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_APPEND + return nil +} + +func file_rdonly(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_RDONLY + return nil +} + +func file_wronly(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_WRONLY + return nil +} + +func file_rdwr(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_RDWR + return nil +} + +func file_excl(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_EXCL + return nil +} + +func file_sync(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_SYNC + return nil +} + +func file_trunc(f *Flag, option, value string) error { + f.optionMeta["file_mode"] = f.optionMeta["file_mode"].(int) | os.O_TRUNC + return nil +} + +func file_perm(f *Flag, option, value string) error { + perm, err := strconv.ParseInt(value, 8, 32) + if err != nil { + return err + } + f.optionMeta["file_perm"] = uint32(perm) + return nil +} + +func time_format(f *Flag, option, value string) error { + f.optionMeta["format"] = value + return nil +} + +func optionMapForType(t reflect.Type) optionMap { + g := typeOptionMap[nil] + m, _ := typeOptionMap[t] + r := make(optionMap) + for k, v := range g { + r[k] = v + } + for k, v := range m { + r[k] = v + } + return r +} diff --git a/vendor/github.com/voxelbrain/goptions/special_types.go b/vendor/github.com/voxelbrain/goptions/special_types.go new file mode 100644 index 0000000..dc0078f --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/special_types.go @@ -0,0 +1,14 @@ +package goptions + +// Help Defines the common help flag. It is handled separately as it will cause +// Parse() to return ErrHelpRequest. +type Help bool + +// Verbs marks the point in the struct where the verbs start. Its value will be +// the name of the selected verb. +type Verbs string + +// A remainder catches all excessive arguments. If both a verb and +// the containing options struct have a remainder field, only the latter one +// will be used. +type Remainder []string diff --git a/vendor/github.com/voxelbrain/goptions/tagparser.go b/vendor/github.com/voxelbrain/goptions/tagparser.go new file mode 100644 index 0000000..7a507e9 --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/tagparser.go @@ -0,0 +1,68 @@ +package goptions + +import ( + "fmt" + "reflect" + "regexp" + "strings" +) + +const ( + _LONG_FLAG_REGEXP = `--[[:word:]-]+` + _SHORT_FLAG_REGEXP = `-[[:alnum:]]` + _QUOTED_STRING_REGEXP = `'((?:\\'|[^\\'])+)'` + _OPTION_REGEXP = `([[:word:]-]+)(?:=` + _QUOTED_STRING_REGEXP + `)?` +) + +var ( + optionRegexp = regexp.MustCompile(`^(` + strings.Join([]string{_SHORT_FLAG_REGEXP, _LONG_FLAG_REGEXP, _OPTION_REGEXP}, "|") + `)(?:,|$)`) +) + +func parseStructField(fieldValue reflect.Value, tag string) (*Flag, error) { + f := &Flag{ + value: fieldValue, + DefaultValue: fieldValue.Interface(), + optionMeta: make(map[string]interface{}), + } + for { + tag = strings.TrimSpace(tag) + if len(tag) == 0 { + break + } + idx := optionRegexp.FindStringSubmatchIndex(tag) + if idx == nil { + return nil, fmt.Errorf("Could not find a valid flag definition at the beginning of \"%s\"", tag) + } + option := tag[idx[2]:idx[3]] + + if strings.HasPrefix(option, "--") { + if f.Long != "" { + return nil, fmt.Errorf("Multiple flags assigned to a member: %s", strings.Join([]string{"--" + f.Long, option}, ", ")) + } + f.Long = option[2:] + } else if strings.HasPrefix(option, "-") { + if f.Short != "" { + return nil, fmt.Errorf("Multiple flags assigned to a member: %s", strings.Join([]string{"-" + f.Short, option}, ", ")) + } + f.Short = option[1:] + } else { + option := tag[idx[4]:idx[5]] + value := "" + if idx[6] != -1 { + value = tag[idx[6]:idx[7]] + } + optionmap := optionMapForType(fieldValue.Type()) + opf, ok := optionmap[option] + if !ok { + return nil, fmt.Errorf("Unknown option %s", option) + } + err := opf(f, option, value) + if err != nil { + return nil, fmt.Errorf("Option %s invalid: %s", option, err) + } + } + // Keep remainder + tag = tag[idx[1]:] + } + return f, nil +} diff --git a/vendor/github.com/voxelbrain/goptions/valueparser.go b/vendor/github.com/voxelbrain/goptions/valueparser.go new file mode 100644 index 0000000..e8176fa --- /dev/null +++ b/vendor/github.com/voxelbrain/goptions/valueparser.go @@ -0,0 +1,163 @@ +package goptions + +import ( + "fmt" + "net" + "net/url" + "os" + "reflect" + "strconv" + "time" +) + +type valueParser func(f *Flag, val string) (reflect.Value, error) + +var ( + parserMap = map[reflect.Type]valueParser{ + reflect.TypeOf(new(bool)).Elem(): boolValueParser, + reflect.TypeOf(new(string)).Elem(): stringValueParser, + reflect.TypeOf(new(float64)).Elem(): float64ValueParser, + reflect.TypeOf(new(float32)).Elem(): float32ValueParser, + reflect.TypeOf(new(int)).Elem(): intValueParser, + reflect.TypeOf(new(int64)).Elem(): int64ValueParser, + reflect.TypeOf(new(int32)).Elem(): int32ValueParser, + reflect.TypeOf(new(Help)).Elem(): helpValueParser, + reflect.TypeOf(new(*os.File)).Elem(): fileValueParser, + reflect.TypeOf(new(*net.TCPAddr)).Elem(): tcpAddrValueParser, + reflect.TypeOf(new(*url.URL)).Elem(): urlValueParser, + reflect.TypeOf(new(time.Duration)).Elem(): durationValueParser, + reflect.TypeOf(new(time.Time)).Elem(): timeValueParser, + } +) + +func parseMarshalValue(value reflect.Value, s string) error { + newval := reflect.New(value.Type()).Elem() + if newval.Kind() == reflect.Ptr { + newptrval := reflect.New(value.Type().Elem()) + newval.Set(newptrval) + } + err := newval.Interface().(Marshaler).MarshalGoption(s) + value.Set(newval) + return err +} + +func (f *Flag) setValue(s string) (err error) { + defer func() { + if x := recover(); x != nil { + err = x.(error) + return + } + }() + if f.value.Type().Implements(reflect.TypeOf(new(Marshaler)).Elem()) { + return parseMarshalValue(f.value, s) + } + vtype := f.value.Type() + newval := reflect.New(vtype).Elem() + if f.value.Kind() == reflect.Slice { + vtype = f.value.Type().Elem() + if vtype.Implements(reflect.TypeOf(new(Marshaler)).Elem()) { + newval = reflect.New(vtype).Elem() + err := parseMarshalValue(newval, s) + f.value.Set(reflect.Append(f.value, newval)) + return err + } + } + if parser, ok := parserMap[vtype]; ok { + val, err := parser(f, s) + if err != nil { + return err + } + if f.value.Kind() == reflect.Slice { + f.value.Set(reflect.Append(f.value, val)) + } else { + f.value.Set(val) + } + return nil + } else { + return fmt.Errorf("Unsupported flag type: %s", f.value.Type()) + } + panic("Invalid execution path") +} + +func boolValueParser(f *Flag, val string) (reflect.Value, error) { + return reflect.ValueOf(true), nil +} + +func stringValueParser(f *Flag, val string) (reflect.Value, error) { + return reflect.ValueOf(val), nil +} + +func float64ValueParser(f *Flag, val string) (reflect.Value, error) { + floatval, err := strconv.ParseFloat(val, 64) + return reflect.ValueOf(float64(floatval)), err +} + +func float32ValueParser(f *Flag, val string) (reflect.Value, error) { + floatval, err := strconv.ParseFloat(val, 32) + return reflect.ValueOf(float32(floatval)), err +} + +func int64ValueParser(f *Flag, val string) (reflect.Value, error) { + intval, err := strconv.ParseInt(val, 10, 64) + return reflect.ValueOf(int64(intval)), err +} + +func int32ValueParser(f *Flag, val string) (reflect.Value, error) { + intval, err := strconv.ParseInt(val, 10, 32) + return reflect.ValueOf(int32(intval)), err +} + +func intValueParser(f *Flag, val string) (reflect.Value, error) { + intval, err := strconv.ParseInt(val, 10, 64) + return reflect.ValueOf(int(intval)), err +} + +func fileValueParser(f *Flag, val string) (reflect.Value, error) { + mode := 0 + if v, ok := f.optionMeta["file_mode"]; ok { + mode = v.(int) + } + if val == "-" { + if mode&1 == os.O_RDONLY { + return reflect.ValueOf(os.Stdin), nil + } else if mode&1 == os.O_WRONLY { + return reflect.ValueOf(os.Stdout), nil + } + } else { + perm := uint32(0644) + if v, ok := f.optionMeta["file_perm"].(uint32); ok { + perm = v + } + f, e := os.OpenFile(val, mode, os.FileMode(perm)) + return reflect.ValueOf(f), e + } + panic("Invalid execution path") +} + +func tcpAddrValueParser(f *Flag, val string) (reflect.Value, error) { + addr, err := net.ResolveTCPAddr("tcp", val) + return reflect.ValueOf(addr), err +} + +func urlValueParser(f *Flag, val string) (reflect.Value, error) { + url, err := url.Parse(val) + return reflect.ValueOf(url), err +} + +func durationValueParser(f *Flag, val string) (reflect.Value, error) { + d, err := time.ParseDuration(val) + return reflect.ValueOf(d), err +} + +func timeValueParser(f *Flag, val string) (reflect.Value, error) { + format := time.RFC3339 + if altFormat, ok := f.optionMeta["format"]; ok { + format = altFormat.(string) + } + d, err := time.Parse(format, val) + return reflect.ValueOf(d), err +} + +func helpValueParser(f *Flag, val string) (reflect.Value, error) { + return reflect.Value{}, ErrHelpRequest +} diff --git a/vendor/modules.txt b/vendor/modules.txt new file mode 100644 index 0000000..2ecbcc9 --- /dev/null +++ b/vendor/modules.txt @@ -0,0 +1,13 @@ +# github.com/dustin/go-humanize v1.0.1 +## explicit; go 1.16 +github.com/dustin/go-humanize +# github.com/kevinburke/rest v0.0.0-20250718180114-1a15e4f2364f +## explicit; go 1.23.0 +github.com/kevinburke/rest/restclient +github.com/kevinburke/rest/resterror +# github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 +## explicit +github.com/tomnomnom/linkheader +# github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 +## explicit +github.com/voxelbrain/goptions