From 129df9d7f20fcc713ab27aea1f7ee03fea437716 Mon Sep 17 00:00:00 2001 From: Andreas Schneider Date: Fri, 9 Oct 2020 18:35:44 +0200 Subject: [PATCH] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20update=20dependencies,=20?= =?UTF-8?q?=E2=9C=A8=20implement=20user=20store?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 73 +- example.yaml | 18 - go.mod | 19 +- go.sum | 71 + store.go | 258 +- store_test.go | 103 + vendor/github.com/alecthomas/kong/.gitignore | 1 + .../github.com/alecthomas/kong/.golangci.yml | 42 + .../kong/COPYING} | 14 +- vendor/github.com/alecthomas/kong/README.md | 536 ++++ vendor/github.com/alecthomas/kong/build.go | 211 ++ .../github.com/alecthomas/kong/callbacks.go | 76 + .../github.com/alecthomas/kong/camelcase.go | 90 + vendor/github.com/alecthomas/kong/context.go | 787 +++++ vendor/github.com/alecthomas/kong/defaults.go | 21 + vendor/github.com/alecthomas/kong/doc.go | 32 + vendor/github.com/alecthomas/kong/error.go | 12 + vendor/github.com/alecthomas/kong/global.go | 16 + vendor/github.com/alecthomas/kong/go.mod | 10 + vendor/github.com/alecthomas/kong/go.sum | 8 + .../github.com/alecthomas/kong/guesswidth.go | 9 + .../alecthomas/kong/guesswidth_unix.go | 41 + vendor/github.com/alecthomas/kong/help.go | 416 +++ vendor/github.com/alecthomas/kong/hooks.go | 19 + .../github.com/alecthomas/kong/interpolate.go | 39 + vendor/github.com/alecthomas/kong/kong.go | 368 +++ vendor/github.com/alecthomas/kong/kong.png | Bin 0 -> 68610 bytes vendor/github.com/alecthomas/kong/kong.sketch | Bin 0 -> 162506 bytes .../github.com/alecthomas/kong/levenshtein.go | 39 + vendor/github.com/alecthomas/kong/mapper.go | 727 +++++ vendor/github.com/alecthomas/kong/model.go | 433 +++ vendor/github.com/alecthomas/kong/options.go | 289 ++ vendor/github.com/alecthomas/kong/resolver.go | 49 + vendor/github.com/alecthomas/kong/scanner.go | 217 ++ vendor/github.com/alecthomas/kong/tag.go | 240 ++ vendor/github.com/alecthomas/kong/util.go | 35 + vendor/github.com/alecthomas/kong/visit.go | 58 + vendor/github.com/go-chi/chi/.gitignore | 3 - vendor/github.com/go-chi/chi/.travis.yml | 18 - vendor/github.com/go-chi/chi/CHANGELOG.md | 139 - vendor/github.com/go-chi/chi/CONTRIBUTING.md | 31 - vendor/github.com/go-chi/chi/LICENSE | 20 - vendor/github.com/go-chi/chi/README.md | 438 --- vendor/github.com/go-chi/chi/chain.go | 49 - vendor/github.com/go-chi/chi/chi.go | 134 - vendor/github.com/go-chi/chi/context.go | 161 - vendor/github.com/go-chi/chi/mux.go | 460 --- vendor/github.com/go-chi/chi/tree.go | 846 ------ vendor/github.com/go-yaml/yaml/.travis.yml | 12 - vendor/github.com/go-yaml/yaml/LICENSE | 201 -- vendor/github.com/go-yaml/yaml/NOTICE | 13 - vendor/github.com/go-yaml/yaml/README.md | 135 - vendor/github.com/go-yaml/yaml/apic.go | 739 ----- vendor/github.com/go-yaml/yaml/decode.go | 764 ----- vendor/github.com/go-yaml/yaml/emitterc.go | 1685 ---------- vendor/github.com/go-yaml/yaml/encode.go | 358 --- vendor/github.com/go-yaml/yaml/parserc.go | 1095 ------- vendor/github.com/go-yaml/yaml/readerc.go | 394 --- vendor/github.com/go-yaml/yaml/resolve.go | 245 -- vendor/github.com/go-yaml/yaml/scannerc.go | 2702 ----------------- vendor/github.com/go-yaml/yaml/sorter.go | 104 - vendor/github.com/go-yaml/yaml/writerc.go | 26 - vendor/github.com/go-yaml/yaml/yaml.go | 466 --- vendor/github.com/go-yaml/yaml/yamlh.go | 738 ----- .../github.com/go-yaml/yaml/yamlprivateh.go | 173 -- vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 15 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/README.md | 52 + vendor/github.com/pkg/errors/appveyor.yml | 32 + vendor/github.com/pkg/errors/errors.go | 282 ++ vendor/github.com/pkg/errors/stack.go | 147 + vendor/github.com/satori/go.uuid/.travis.yml | 23 + .../go-password => satori/go.uuid}/LICENSE | 2 +- vendor/github.com/satori/go.uuid/README.md | 65 + vendor/github.com/satori/go.uuid/codec.go | 206 ++ vendor/github.com/satori/go.uuid/generator.go | 239 ++ vendor/github.com/satori/go.uuid/sql.go | 78 + vendor/github.com/satori/go.uuid/uuid.go | 161 + .../go-password/password/generate.go | 242 -- vendor/github.com/tidwall/btree/btree.go | 10 + vendor/github.com/tidwall/buntdb/buntdb.go | 12 - vendor/github.com/tidwall/buntdb/go.mod | 12 + vendor/github.com/tidwall/buntdb/go.sum | 14 + vendor/github.com/tidwall/gjson/SYNTAX.md | 3 +- vendor/github.com/tidwall/gjson/gjson.go | 71 +- vendor/golang.org/x/crypto/blowfish/cipher.go | 8 + .../x/net/webdav/litmus_test_server.go | 94 - vendor/golang.org/x/net/webdav/webdav.go | 6 +- vendor/modules.txt | 34 +- 90 files changed, 6640 insertions(+), 12741 deletions(-) delete mode 100644 example.yaml create mode 100644 store_test.go create mode 100644 vendor/github.com/alecthomas/kong/.gitignore create mode 100644 vendor/github.com/alecthomas/kong/.golangci.yml rename vendor/github.com/{go-yaml/yaml/LICENSE.libyaml => alecthomas/kong/COPYING} (77%) create mode 100644 vendor/github.com/alecthomas/kong/README.md create mode 100644 vendor/github.com/alecthomas/kong/build.go create mode 100644 vendor/github.com/alecthomas/kong/callbacks.go create mode 100644 vendor/github.com/alecthomas/kong/camelcase.go create mode 100644 vendor/github.com/alecthomas/kong/context.go create mode 100644 vendor/github.com/alecthomas/kong/defaults.go create mode 100644 vendor/github.com/alecthomas/kong/doc.go create mode 100644 vendor/github.com/alecthomas/kong/error.go create mode 100644 vendor/github.com/alecthomas/kong/global.go create mode 100644 vendor/github.com/alecthomas/kong/go.mod create mode 100644 vendor/github.com/alecthomas/kong/go.sum create mode 100644 vendor/github.com/alecthomas/kong/guesswidth.go create mode 100644 vendor/github.com/alecthomas/kong/guesswidth_unix.go create mode 100644 vendor/github.com/alecthomas/kong/help.go create mode 100644 vendor/github.com/alecthomas/kong/hooks.go create mode 100644 vendor/github.com/alecthomas/kong/interpolate.go create mode 100644 vendor/github.com/alecthomas/kong/kong.go create mode 100644 vendor/github.com/alecthomas/kong/kong.png create mode 100644 vendor/github.com/alecthomas/kong/kong.sketch create mode 100644 vendor/github.com/alecthomas/kong/levenshtein.go create mode 100644 vendor/github.com/alecthomas/kong/mapper.go create mode 100644 vendor/github.com/alecthomas/kong/model.go create mode 100644 vendor/github.com/alecthomas/kong/options.go create mode 100644 vendor/github.com/alecthomas/kong/resolver.go create mode 100644 vendor/github.com/alecthomas/kong/scanner.go create mode 100644 vendor/github.com/alecthomas/kong/tag.go create mode 100644 vendor/github.com/alecthomas/kong/util.go create mode 100644 vendor/github.com/alecthomas/kong/visit.go delete mode 100644 vendor/github.com/go-chi/chi/.gitignore delete mode 100644 vendor/github.com/go-chi/chi/.travis.yml delete mode 100644 vendor/github.com/go-chi/chi/CHANGELOG.md delete mode 100644 vendor/github.com/go-chi/chi/CONTRIBUTING.md delete mode 100644 vendor/github.com/go-chi/chi/LICENSE delete mode 100644 vendor/github.com/go-chi/chi/README.md delete mode 100644 vendor/github.com/go-chi/chi/chain.go delete mode 100644 vendor/github.com/go-chi/chi/chi.go delete mode 100644 vendor/github.com/go-chi/chi/context.go delete mode 100644 vendor/github.com/go-chi/chi/mux.go delete mode 100644 vendor/github.com/go-chi/chi/tree.go delete mode 100644 vendor/github.com/go-yaml/yaml/.travis.yml delete mode 100644 vendor/github.com/go-yaml/yaml/LICENSE delete mode 100644 vendor/github.com/go-yaml/yaml/NOTICE delete mode 100644 vendor/github.com/go-yaml/yaml/README.md delete mode 100644 vendor/github.com/go-yaml/yaml/apic.go delete mode 100644 vendor/github.com/go-yaml/yaml/decode.go delete mode 100644 vendor/github.com/go-yaml/yaml/emitterc.go delete mode 100644 vendor/github.com/go-yaml/yaml/encode.go delete mode 100644 vendor/github.com/go-yaml/yaml/parserc.go delete mode 100644 vendor/github.com/go-yaml/yaml/readerc.go delete mode 100644 vendor/github.com/go-yaml/yaml/resolve.go delete mode 100644 vendor/github.com/go-yaml/yaml/scannerc.go delete mode 100644 vendor/github.com/go-yaml/yaml/sorter.go delete mode 100644 vendor/github.com/go-yaml/yaml/writerc.go delete mode 100644 vendor/github.com/go-yaml/yaml/yaml.go delete mode 100644 vendor/github.com/go-yaml/yaml/yamlh.go delete mode 100644 vendor/github.com/go-yaml/yaml/yamlprivateh.go create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/satori/go.uuid/.travis.yml rename vendor/github.com/{sethvargo/go-password => satori/go.uuid}/LICENSE (94%) create mode 100644 vendor/github.com/satori/go.uuid/README.md create mode 100644 vendor/github.com/satori/go.uuid/codec.go create mode 100644 vendor/github.com/satori/go.uuid/generator.go create mode 100644 vendor/github.com/satori/go.uuid/sql.go create mode 100644 vendor/github.com/satori/go.uuid/uuid.go delete mode 100644 vendor/github.com/sethvargo/go-password/password/generate.go create mode 100644 vendor/github.com/tidwall/buntdb/go.mod create mode 100644 vendor/github.com/tidwall/buntdb/go.sum delete mode 100644 vendor/golang.org/x/net/webdav/litmus_test_server.go diff --git a/config.go b/config.go index d1aa135..591caaa 100644 --- a/config.go +++ b/config.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019, Andreas Schneider +// Copyright (c) 2020, Andreas Schneider // All rights reserved. // // Redistribution and use in source and binary forms, with or without @@ -25,71 +25,10 @@ package main -import ( - "github.com/go-yaml/yaml" - "os" -) - -type Config struct { - ListenAddress string `yaml:"listenAddress"` - BaseDirectory string `yaml:"baseDirectory"` - Shares map[string]Share `yaml:"shares"` +type config struct { + ListenAddress string `name:"listen-address" default:":3000" help:"Address to listen on for HTTP requests."` + DataDirectory string `name:"data-dir" default:"data" help:"Directory to store all files in."` + Db string `name:"db" default:"ShareDAV.db" help:"Database file to use."` } -type Share struct { - Directory string `yaml:"directory"` - Users map[string]ShareUser `yaml:"users"` -} - -type ShareUser struct { - Role string `yaml:"role"` - Logins map[string]string `yaml:"logins"` -} - -func LoadConfig(filename string) Config { - c := Config{} - f, err := os.Open(filename) - if err != nil { - panic(err) - } - defer f.Close() - if err := yaml.NewDecoder(f).Decode(&c); err != nil { - panic(err) - } - - return c -} - -func (c *Config) ValidateDAVUser(username, password string) (valid bool, directory string) { - /*parts := strings.SplitN(username, "@", 2) - if len(parts) != 2 { - return false, "" - } - - l, ok := c.Logins[parts[0]] - if !ok { - return false, "" - } - - cred, ok := l.Credentials[parts[1]] - if !ok { - return false, "" - } - - share, ok := c.Shares[cred.Share] - if !ok { - log.Printf("Invalid share for user %s: %s\n", username, cred.Share) - return false, "" - } - - err := bcrypt.CompareHashAndPassword([]byte(cred.Password), []byte(password)) - if err == bcrypt.ErrMismatchedHashAndPassword { - return false, "" - } else if err != nil { - log.Printf("Cannot validate password for user %s: %s\n", username, err.Error()) - return false, "" - } else { - return true, share.Directory - }*/ - return false, "" -} +var Config config diff --git a/example.yaml b/example.yaml deleted file mode 100644 index 00f3057..0000000 --- a/example.yaml +++ /dev/null @@ -1,18 +0,0 @@ -listenAddress: :3000 -baseDirectory: data -shares: - test1: - directory: share1 - users: - User1: - role: admin - logins: - dev1: $2a$10$5AuehKad7TxDqW2HXdYaZ.ipFajhl7ULyTR3DLCquTA3B/dxHujIq #test - test2: - directory: share2 - users: - User1: - role: reader -users: - User1: - diff --git a/go.mod b/go.mod index 02853f9..726f260 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,19 @@ module ShareDAV +go 1.15 + require ( - github.com/go-chi/chi v4.0.2+incompatible - github.com/go-yaml/yaml v2.1.0+incompatible + github.com/alecthomas/kong v0.2.11 + github.com/go-chi/chi v4.0.2+incompatible // indirect + github.com/go-yaml/yaml v2.1.0+incompatible // indirect github.com/kr/pretty v0.1.0 // indirect - github.com/sethvargo/go-password v0.1.2 - github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0 // indirect - github.com/tidwall/buntdb v1.1.0 - github.com/tidwall/gjson v1.3.2 // indirect + github.com/satori/go.uuid v1.2.0 + github.com/sethvargo/go-password v0.1.2 // indirect + github.com/tidwall/buntdb v1.1.2 github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect - golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 - golang.org/x/net v0.0.0-20181114220301-adae6a3d119a - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 + golang.org/x/net v0.0.0-20190620200207-3b0461eec859 gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index 679597b..ccff5ee 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,78 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= +github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/kong v0.2.11 h1:RKeJXXWfg9N47RYfMm0+igkxBCTF4bzbneAxaqid0c4= +github.com/alecthomas/kong v0.2.11/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= +github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k= +github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= +github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sethvargo/go-password v0.1.2 h1:fhBF4thiPVKEZ7R6+CX46GWJiPyCyXshbeqZ7lqEeYo= github.com/sethvargo/go-password v0.1.2/go.mod h1:qKHfdSjT26DpHQWHWWR5+X4BI45jT31dg6j4RI2TEb0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0 h1:QnyrPZZvPmR0AtJCxxfCtI1qN+fYpKTKJ/5opWmZ34k= github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E= +github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8= github.com/tidwall/buntdb v1.1.0 h1:H6LzK59KiNjf1nHVPFrYj4Qnl8d8YLBsYamdL8N+Bao= github.com/tidwall/buntdb v1.1.0/go.mod h1:Y39xhcDW10WlyYXeLgGftXVbjtM0QP+/kpz8xl9cbzE= +github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo= +github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI= github.com/tidwall/gjson v1.3.2 h1:+7p3qQFaH3fOMXAJSrdZwGKcOO/lYdGS0HqGhPqDdTI= github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw= +github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE= github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= @@ -25,12 +83,25 @@ github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2K github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao= github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE= github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/store.go b/store.go index b419912..b0bbac1 100644 --- a/store.go +++ b/store.go @@ -1,106 +1,166 @@ +// Copyright (c) 2020, Andreas Schneider +// 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 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 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. + package main import ( "errors" "fmt" + "strings" + + uuid "github.com/satori/go.uuid" "github.com/tidwall/buntdb" "golang.org/x/crypto/bcrypt" - "strings" - "time" ) type UserStore interface { - AddUser(username string, role GlobalRole, password string) (err error) + AddUser(user User) (err error) GetUser(username string) (user User, err error) - GetLogin(username string, password string) (appName string, err error) - SetLogin(username string, appName string, password string) (err error) - RemoveLogin(username string, appName string) (err error) + GetUsers() ([]User, error) + Update(user User) error RemoveUser(username string) (err error) } type ShareStore interface { - } +var _ UserStore = &DBStore{} + type GlobalRole string + const ( - GlobalRoleUser GlobalRole = "user" - GlobalRoleManager GlobalRole = "manager" + GlobalRoleUser GlobalRole = "user" GlobalRoleAdmin GlobalRole = "admin" ) type ShareRole string + const ( ShareRoleReader ShareRole = "reader" ShareRoleWriter ShareRole = "writer" - ShareRoleAdmin ShareRole = "admin" + ShareRoleAdmin ShareRole = "admin" ) type User struct { Username string - Role GlobalRole - Apps []App -} - -type App struct { - Name string - CreatedAt time.Time + Password string + Role GlobalRole } type Share struct { + UUID uuid.UUID Directory string - Users []ShareUser + Users []ShareUser } type ShareUser struct { Username string - Role ShareRole + Role ShareRole + Logins []Login } +type Login struct { + Loginname string + Password string +} + +const usersPrefix = "users:" + type DBStore struct { db *buntdb.DB } +func (u User) key() string { + return usersPrefix + u.Username +} + +func (u User) prefix() string { + return fmt.Sprintf("user:%s", u.Username) +} + +func (u User) roleKey() string { + return u.prefix() + ":role" +} + +func (u User) passwordKey() string { + return u.prefix() + ":password" +} + func NewDBStore(filename string) (*DBStore, error) { db, err := buntdb.Open(filename) if err != nil { return nil, err } - if err := db.CreateIndex("login", "user:*:login*", buntdb.IndexBinary); err != nil && err != buntdb.ErrIndexExists { - return nil, errors.New("cannot create login index: " + err.Error()) - } return &DBStore{db}, nil } +func (store *DBStore) Close() error { + return store.db.Close() +} + var ErrExists = errors.New("key already exists") var ErrUserNotFound = errors.New("user not found") var ErrInvalidUsername = errors.New("invalid username") -func (store *DBStore) AddUser(username string, role GlobalRole, password string) (err error) { - if strings.Contains(username, ":") { +func (store *DBStore) setUserValues(tx *buntdb.Tx, user User) (exists bool, err error) { + pwString, err := buildPassword(user.Username, user.Password) + if err != nil { + return false, fmt.Errorf("cannot hash password: %w", err) + } + + if _, replaced, err := tx.Set(user.roleKey(), string(user.Role), nil); err != nil { + return false, err + } else if replaced { + exists = true + } + if _, replaced, err := tx.Set(user.passwordKey(), pwString, nil); err != nil { + return false, err + } else if replaced { + exists = true + } + + return exists, nil +} + +func (store *DBStore) AddUser(user User) (err error) { + if strings.Contains(user.Username, ":") { return ErrInvalidUsername } - pwString, err := buildPassword(username, password) - if err != nil { - return err - } if err = store.db.Update(func(tx *buntdb.Tx) error { - if _, replaced, err := tx.Set(fmt.Sprintf("user:%s:role", username), string(role), nil); err != nil { + if _, exists, err := tx.Set(user.key(), "", nil); err != nil { return err - } else if replaced { + } else if exists { return ErrExists } - if _, replaced, err := tx.Set(fmt.Sprintf("user:%s:login", username), pwString, nil); err != nil { - return err - } else if replaced { - return ErrExists - } - // just in case ... - if _, err := tx.Delete(fmt.Sprintf("user:%s:login:*", username)); err != nil { - return err - } + if exists, err := store.setUserValues(tx, user); err != nil { + return err + } else if exists { + return ErrExists + } return nil }); err != nil { return err @@ -114,33 +174,20 @@ func (store *DBStore) GetUser(username string) (user User, err error) { } user.Username = username if err := store.db.View(func(tx *buntdb.Tx) error { - if val, err := tx.Get(fmt.Sprintf("user:%s:role", username)); err != nil && err != buntdb.ErrNotFound { + if val, err := tx.Get(user.roleKey()); err != nil && err != buntdb.ErrNotFound { return err + } else if err == buntdb.ErrNotFound { + return ErrUserNotFound } else { user.Role = GlobalRole(val) } - - loginPrefix := fmt.Sprintf("user:%s:login:", username) - if err := tx.AscendKeys(loginPrefix + "*", func(key, value string) bool { - app := App{Name: strings.TrimPrefix(key, loginPrefix)} - creationTimeValue, err := tx.Get(fmt.Sprintf("user:%s:app:%s:created_at", username, app.Name)) - if err != nil { - // invalid app ... move along - return true - } - creationTime, err := time.Parse(time.RFC3339Nano, creationTimeValue) - if err != nil { - // invalid creation time ... invalid app ... move along. - return true - } - app.CreatedAt = creationTime - user.Apps = append(user.Apps, app) - - return true - }); err != nil { + if val, err := tx.Get(user.passwordKey()); err != nil && err != buntdb.ErrNotFound { return err + } else if err == buntdb.ErrNotFound { + return ErrUserNotFound + } else { + user.Password = val } - return nil }); err != nil { return user, err @@ -148,56 +195,39 @@ func (store *DBStore) GetUser(username string) (user User, err error) { return user, nil } -func (store *DBStore) GetLogin(username string, password string) (appName string, err error) { - panic("implement me") -} - -func (store *DBStore) SetLogin(username string, appName string, password string) (err error) { - if strings.Contains(username, ":") { - return ErrInvalidUsername - } - - pwString, err := buildPassword(username, password) - if err != nil { - return err - } - - return store.db.Update(func(tx *buntdb.Tx) error { - mainLoginKey := fmt.Sprintf("user:%s:login", username) - // check if we actually know the user. the current login is a good indicator - if _, err := tx.Get(mainLoginKey); err != nil { - if err == buntdb.ErrNotFound { - return ErrUserNotFound - } else { - return err - } +func (store *DBStore) GetUsers() (users []User, err error) { + err = store.db.View(func(tx *buntdb.Tx) error { + if err := tx.AscendKeys(usersPrefix+"*", func(key, value string) bool { + var user User + user.Username = strings.TrimPrefix(key, usersPrefix) + users = append(users, user) + return true + }); err != nil { + return err } - saneAppName := strings.TrimSpace(appName) - - // if no appname is given, we apparently have to update the password of the whole account. - if saneAppName == "" { - if _, _, err := tx.Set(mainLoginKey, pwString, nil); err != nil { + for i := range users { + if roleString, err := tx.Get(users[i].roleKey()); err != nil { return err - } - } else { - if _, replaced, err := tx.Set(fmt.Sprintf("%s:%s", mainLoginKey, saneAppName), pwString, nil); err != nil { - return err - } else if !replaced { - // o,h so this is a new key. set the timestamp - creationTime := time.Now().Format(time.RFC3339Nano) - if _, _, err := tx.Set(fmt.Sprintf("user:%s:app:%s:created_at", username, saneAppName), creationTime, nil); err != nil { - return err - } + } else { + users[i].Role = GlobalRole(roleString) } } return nil }) + + return users, err } -func (store *DBStore) RemoveLogin(username string, appName string) (err error) { - panic("implement me") +func (store *DBStore) Update(user User) error { + if strings.Contains(user.Username, ":") { + return ErrInvalidUsername + } + return store.db.Update(func(tx *buntdb.Tx) error { + _, err := store.setUserValues(tx, user) + return err + }) } func (store *DBStore) RemoveUser(username string) (err error) { @@ -205,9 +235,29 @@ func (store *DBStore) RemoveUser(username string) (err error) { return ErrInvalidUsername } return store.db.Update(func(tx *buntdb.Tx) error { - // TODO won't work ... collect keys first, then delete them one by one - _, err := tx.Delete(fmt.Sprintf("user:%s:*", username)) - return err + user := User{Username: username} + + // Delete the main key first. This is a good indicator if the user generally exists. + if _, err := tx.Delete(user.key()); err == buntdb.ErrNotFound { + return ErrUserNotFound + } else if err != nil { + return err + } + + // Now get all attributes and delete them as well. One by one. + var keys []string + if err := tx.AscendKeys(user.prefix()+":*", func(key, value string) bool { + keys = append(keys, key) + return true + }); err != nil { + return fmt.Errorf("cannot iterate keys: %w", err) + } + for _, key := range keys { + if _, err := tx.Delete(key); err != nil { + return fmt.Errorf("cannot remove key: %w", err) + } + } + return nil }) } @@ -217,4 +267,4 @@ func buildPassword(username string, password string) (string, error) { return "", err } return fmt.Sprintf("%s:%s", username, hash), nil -} \ No newline at end of file +} diff --git a/store_test.go b/store_test.go new file mode 100644 index 0000000..0d42e2f --- /dev/null +++ b/store_test.go @@ -0,0 +1,103 @@ +package main + +import ( + "testing" + + "github.com/tidwall/buntdb" +) + +func TestStoreUserHandling(t *testing.T) { + store, err := NewDBStore(":memory:") + if err != nil { + t.Fatalf("cannot create store: %v", err) + } + defer store.Close() + + t.Run("store should be empty initially", func(t *testing.T) { + users, err := store.GetUsers() + if err != nil { + t.Errorf("no error should have been returned: %v", err) + } + if len(users) != 0 { + t.Errorf("there should be no users") + } + }) + + t.Run("user that doesn't exist should return error", func(t *testing.T) { + _, err := store.GetUser("someuser") + if err != ErrUserNotFound { + t.Errorf("unexpected error: %v", err) + } + }) + + t.Run("adding users should work", func(t *testing.T) { + if err := store.AddUser(User{"myuser", "mypass", GlobalRoleUser}); err != nil { + t.Errorf("cannot add user: %v", err) + } + + t.Run("retrieving that single user should work", func(t *testing.T) { + user, err := store.GetUser("myuser") + if err != nil { + t.Errorf("cannot retrieve user: %v", err) + } + if user.Username != "myuser" { + t.Errorf("retrieved user contains unexpected username") + } + if user.Role != GlobalRoleUser { + t.Errorf("retrieved user contains unexpected role") + } + }) + + t.Run("retrieving multiple users should work", func(t *testing.T) { + users, err := store.GetUsers() + if err != nil { + t.Errorf("cannot retrieve user list: %v", err) + } + if len(users) != 1 { + t.Errorf("there should be only one user") + } + if users[0].Username != "myuser" { + t.Errorf("retrieved user contains unexpected username") + } + if users[0].Role != GlobalRoleUser { + t.Errorf("retrieved user contains unexpected role") + } + }) + + t.Run("deleting that user should work", func(t *testing.T) { + err := store.RemoveUser("myuser") + if err != nil { + t.Errorf("cannot delete user: %v", err) + } + + t.Run("user should no longer be found", func(t *testing.T) { + _, err := store.GetUser("myuser") + if err != ErrUserNotFound { + t.Errorf("unexpected error: %v", err) + } + }) + + t.Run("user should no longer be listed", func(t *testing.T) { + users, err := store.GetUsers() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if len(users) != 0 { + t.Errorf("there should be no users") + } + }) + }) + }) + + t.Run("database should be empty now", func(t *testing.T) { + // checks that we properly deleted all keys + if err := store.db.View(func(tx *buntdb.Tx) error { + return tx.Ascend("", func(key, value string) bool { + t.Errorf("there should be no keys left") + return false + }) + }); err != nil { + t.Errorf("iterating keys failed: %v", err) + } + }) +} diff --git a/vendor/github.com/alecthomas/kong/.gitignore b/vendor/github.com/alecthomas/kong/.gitignore new file mode 100644 index 0000000..ba077a4 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/.gitignore @@ -0,0 +1 @@ +bin diff --git a/vendor/github.com/alecthomas/kong/.golangci.yml b/vendor/github.com/alecthomas/kong/.golangci.yml new file mode 100644 index 0000000..af55257 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/.golangci.yml @@ -0,0 +1,42 @@ +run: + tests: true + +output: + print-issued-lines: false + +linters: + enable-all: true + disable: + - maligned + - lll + - gochecknoglobals + - wsl + - funlen + - gocognit + - gomnd + - goprintffuncname + +linters-settings: + govet: + check-shadowing: true + dupl: + threshold: 100 + goconst: + min-len: 5 + min-occurrences: 3 + gocyclo: + min-complexity: 20 + +issues: + max-per-linter: 0 + max-same: 0 + exclude-use-default: false + exclude: + - '^(G104|G204):' + # Very commonly not checked. + - 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked' + - 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON) should have comment or be unexported' + - 'composite literal uses unkeyed fields' + - 'bad syntax for struct tag key' + - 'bad syntax for struct tag pair' + - 'result .* \(error\) is always nil' diff --git a/vendor/github.com/go-yaml/yaml/LICENSE.libyaml b/vendor/github.com/alecthomas/kong/COPYING similarity index 77% rename from vendor/github.com/go-yaml/yaml/LICENSE.libyaml rename to vendor/github.com/alecthomas/kong/COPYING index 8da58fb..22707ac 100644 --- a/vendor/github.com/go-yaml/yaml/LICENSE.libyaml +++ b/vendor/github.com/alecthomas/kong/COPYING @@ -1,16 +1,4 @@ -The following files were ported to Go from C files of libyaml, and thus -are still covered by their original copyright and license: - - apic.go - emitterc.go - parserc.go - readerc.go - scannerc.go - writerc.go - yamlh.go - yamlprivateh.go - -Copyright (c) 2006 Kirill Simonov +Copyright (C) 2018 Alec Thomas Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/vendor/github.com/alecthomas/kong/README.md b/vendor/github.com/alecthomas/kong/README.md new file mode 100644 index 0000000..eaad200 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/README.md @@ -0,0 +1,536 @@ + +

+ +# Kong is a command-line parser for Go +[![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3) + +[TOC levels=2-3 numbered]: # "#### Table of Contents" + +#### Table of Contents +1. [Introduction](#introduction) +1. [Help](#help) +1. [Command handling](#command-handling) + 1. [Switch on the command string](#switch-on-the-command-string) + 1. [Attach a `Run(...) error` method to each command](#attach-a-run-error-method-to-each-command) +1. [Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) +1. [Flags](#flags) +1. [Commands and sub-commands](#commands-and-sub-commands) +1. [Branching positional arguments](#branching-positional-arguments) +1. [Terminating positional arguments](#terminating-positional-arguments) +1. [Slices](#slices) +1. [Maps](#maps) +1. [Custom named decoders](#custom-named-decoders) +1. [Custom decoders (mappers)](#custom-decoders-mappers) +1. [Supported tags](#supported-tags) +1. [Variable interpolation](#variable-interpolation) +1. [Modifying Kong's behaviour](#modifying-kongs-behaviour) + 1. [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description) + 1. [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files) + 1. [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources) + 1. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values) + 1. [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help) + 1. [`Bind(...)` - bind values for callback hooks and Run() methods](#bind---bind-values-for-callback-hooks-and-run-methods) + 1. [Other options](#other-options) + + +## Introduction + +Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible. + +To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is mapped onto the struct. + +For example, the following command-line: + + shell rm [-f] [-r] ... + shell ls [ ...] + +Can be represented by the following command-line structure: + +```go +package main + +import "github.com/alecthomas/kong" + +var CLI struct { + Rm struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `arg name:"path" help:"Paths to remove." type:"path"` + } `cmd help:"Remove files."` + + Ls struct { + Paths []string `arg optional name:"path" help:"Paths to list." type:"path"` + } `cmd help:"List paths."` +} + +func main() { + ctx := kong.Parse(&CLI) + switch ctx.Command() { + case "rm ": + case "ls": + default: + panic(ctx.Command()) + } +} +``` + +## Help + +Help is automatically generated. With no other arguments provided, help will display a full summary of all available commands. + +eg. + + $ shell --help + usage: shell + + A shell-like example app. + + Flags: + --help Show context-sensitive help. + --debug Debug mode. + + Commands: + rm ... + Remove files. + + ls [ ...] + List paths. + +If a command is provided, the help will show full detail on the command including all available flags. + +eg. + + $ shell --help rm + usage: shell rm ... + + Remove files. + + Arguments: + ... Paths to remove. + + Flags: + --debug Debug mode. + + -f, --force Force removal. + -r, --recursive Recursively remove files. + +For flags with associated environment variables, the variable `${env}` can be +interpolated into the help string. In the absence of this variable in the help, + + +## Command handling + +There are two ways to handle commands in Kong. + +### Switch on the command string + +When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example: + +There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/main.go). + +eg. + +```go +package main + +import "github.com/alecthomas/kong" + +var CLI struct { + Rm struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `arg name:"path" help:"Paths to remove." type:"path"` + } `cmd help:"Remove files."` + + Ls struct { + Paths []string `arg optional name:"path" help:"Paths to list." type:"path"` + } `cmd help:"List paths."` +} + +func main() { + ctx := kong.Parse(&CLI) + switch ctx.Command() { + case "rm ": + case "ls": + default: + panic(ctx.Command()) + } +} +``` + +This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile. + +### Attach a `Run(...) error` method to each command + +A more robust approach is to break each command out into their own structs: + +1. Break leaf commands out into separate structs. +2. Attach a `Run(...) error` method to all leaf commands. +3. Call `kong.Kong.Parse()` to obtain a `kong.Context`. +4. Call `kong.Context.Run(bindings...)` to call the selected parsed command. + +Once a command node is selected by Kong it will search from that node back to the root. Each +encountered command node with a `Run(...) error` will be called in reverse order. This allows +sub-trees to be re-used fairly conveniently. + +In addition to values bound with the `kong.Bind(...)` option, any values +passed through to `kong.Context.Run(...)` are also bindable to the target's +`Run()` arguments. + +Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`. + +There's a full example emulating part of the Docker CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker). + +eg. + +```go +type Context struct { + Debug bool +} + +type RmCmd struct { + Force bool `help:"Force removal."` + Recursive bool `help:"Recursively remove files."` + + Paths []string `arg name:"path" help:"Paths to remove." type:"path"` +} + +func (r *RmCmd) Run(ctx *Context) error { + fmt.Println("rm", r.Paths) + return nil +} + +type LsCmd struct { + Paths []string `arg optional name:"path" help:"Paths to list." type:"path"` +} + +func (l *LsCmd) Run(ctx *Context) error { + fmt.Println("ls", l.Paths) + return nil +} + +var cli struct { + Debug bool `help:"Enable debug mode."` + + Rm RmCmd `cmd help:"Remove files."` + Ls LsCmd `cmd help:"List paths."` +} + +func main() { + ctx := kong.Parse(&cli) + // Call the Run() method of the selected parsed command. + err := ctx.Run(&Context{Debug: cli.Debug}) + ctx.FatalIfErrorf(err) +} + +``` + +## Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option + +If a node in the grammar has a `BeforeResolve(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those methods will be called before validation/assignment and after validation/assignment, respectively. + +The `--help` flag is implemented with a `BeforeApply` hook. + +Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`. + +eg. + +```go +// A flag with a hook that, if triggered, will set the debug loggers output to stdout. +type debugFlag bool + +func (d debugFlag) BeforeApply(logger *log.Logger) error { + logger.SetOutput(os.Stdout) + return nil +} + +var cli struct { + Debug debugFlag `help:"Enable debug logging."` +} + +func main() { + // Debug logger going to discard. + logger := log.New(ioutil.Discard, "", log.LstdFlags) + + ctx := kong.Parse(&cli, kong.Bind(logger)) + + // ... +} +``` + +## Flags + +Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure *not* tagged with `cmd` or `arg` will be a flag. Flags are optional by default. + +eg. The command-line `app [--flag="foo"]` can be represented by the following. + +```go +type CLI struct { + Flag string +} +``` + +## Commands and sub-commands + +Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands. + +eg. The following struct represents the CLI structure `command [--flag="str"] sub-command`. + +```go +type CLI struct { + Command struct { + Flag string + + SubCommand struct { + } `cmd` + } `cmd` +} +``` + +If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments. + +## Branching positional arguments + +In addition to sub-commands, structs can also be configured as branching positional arguments. + +This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure: + + app rename to + +Can be represented with the following: + +```go +var CLI struct { + Rename struct { + Name struct { + Name string `arg` // <-- NOTE: identical name to enclosing struct field. + To struct { + Name struct { + Name string `arg` + } `arg` + } `cmd` + } `arg` + } `cmd` +} +``` + +This looks a little verbose in this contrived example, but typically this will not be the case. + +## Terminating positional arguments + +If a [mapped type](#mapper---customising-how-the-command-line-is-mapped-to-go-values) is tagged with `arg` it will be treated as the final positional values to be parsed on the command line. + +If a positional argument is a slice, all remaining arguments will be appended to that slice. + +## Slices + +Slice values are treated specially. First the input is split on the `sep:""` tag (defaults to `,`), then each element is parsed by the slice element type and appended to the slice. If the same value is encountered multiple times, elements continue to be appended. + +To represent the following command-line: + + cmd ls ... + +You would use the following: + +```go +var CLI struct { + Ls struct { + Files []string `arg type:"existingfile"` + } `cmd` +} +``` + +## Maps + +Maps are similar to slices except that only one key/value pair can be assigned per value, and the `sep` tag denotes the assignment character and defaults to `=`. + +To represent the following command-line: + + cmd config set = = ... + +You would use the following: + +```go +var CLI struct { + Config struct { + Set struct { + Config map[string]float64 `arg type:"file:"` + } `cmd` + } `cmd` +} +``` + +For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`. + +## Custom named decoders + +Kong includes a number of builtin custom type mappers. These can be used by +specifying the tag `type:""`. They are registered with the option +function `NamedMapper(name, mapper)`. + +| Name | Description +|-------------------|--------------------------------------------------- +| `path` | A path. ~ expansion is applied. +| `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin. +| `existingdir` | An existing directory. ~ expansion is applied. +| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`. + + +Slices and maps treat type tags specially. For slices, the `type:""` tag +specifies the element type. For maps, the tag has the format +`tag:"[]:[]"` where either may be omitted. + + +## Custom decoders (mappers) + +Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces +for decoding values. + +For more fine-grained control, if a field implements the +[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue) +interface it will be used to decode arguments into the field. + +## Supported tags + +Tags can be in two forms: + +1. Standard Go syntax, eg. `kong:"required,name='foo'"`. +2. Bare tags, eg. `required name:"foo"` + +Both can coexist with standard Tag parsing. + +Tag | Description +-----------------------| ------------------------------------------- +`cmd` | If present, struct is a command. +`arg` | If present, field is an argument. +`env:"X"` | Specify envar to use for default value. +`name:"X"` | Long name, for overriding field name. +`help:"X"` | Help text. +`type:"X"` | Specify [named types](#custom-named-decoders) to use. +`placeholder:"X"` | Placeholder text. +`default:"X"` | Default value. +`default:"1"` | On a command, make it the default. +`short:"X"` | Short name, if flag. +`required` | If present, flag/arg is required. +`optional` | If present, flag/arg is optional. +`hidden` | If present, command or flag is hidden. +`format:"X"` | Format for parsing input, if supported. +`sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting. +`mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting. +`enum:"X,Y,..."` | Set of valid values allowed for this flag. +`group:"X"` | Logical group for a flag or command. +`xor:"X"` | Exclusive OR group for flags. Only one flag in the group can be used which is restricted within the same command. +`prefix:"X"` | Prefix for all sub-flags. +`set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur. +`embed` | If present, this field's children will be embedded in the parent. Useful for composition. +`-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct. + +## Variable interpolation + +Kong supports limited variable interpolation into help strings, enum lists and +default values. + +Variables are in the form: + + ${} + ${=} + +Variables are set with the `Vars{"key": "value", ...}` option. Undefined +variable references in the grammar without a default will result in an error at +construction time. + +Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that +node and all children. This is useful for composition by allowing the same struct to be reused. + +When interpolating into flag or argument help strings, some extra variables +are defined from the value itself: + + ${default} + ${enum} + +eg. + +```go +type cli struct { + Config string `type:"path" default:"${config_file}"` +} + +func main() { + kong.Parse(&cli, + kong.Vars{ + "config_file": "~/.app.conf", + }) +} +``` + +## Modifying Kong's behaviour + +Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`. + +The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option). + +### `Name(help)` and `Description(help)` - set the application name description + +Set the application name and/or description. + +The name of the application will default to the binary name, but can be overridden with `Name(name)`. + +As with all help in Kong, text will be wrapped to the terminal. + +### `Configuration(loader, paths...)` - load defaults from configuration files + +This option provides Kong with support for loading defaults from a set of configuration files. Each file is opened, if possible, and the loader called to create a resolver for that file. + +eg. + +```go +kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json")) +``` + +[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L103) for an example of how the JSON file is structured. + +### `Resolver(...)` - support for default values from external sources + +Resolvers are Kong's extension point for providing default values from external sources. As an example, support for environment variables via the `env` tag is provided by a resolver. There's also a builtin resolver for JSON configuration files. + +Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go). + +### `*Mapper(...)` - customising how the command-line is mapped to Go values + +Command-line arguments are mapped to Go values via the Mapper interface: + +```go +// A Mapper knows how to map command-line input to Go. +type Mapper interface { + // Decode scan into target. + // + // "ctx" contains context about the value being decoded that may be useful + // to some mapperss. + Decode(ctx *MapperContext, scan *Scanner, target reflect.Value) error +} +``` + +All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default. Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways: + +1. `NamedMapper(string, Mapper)` and using the tag key `type:""`. +2. `KindMapper(reflect.Kind, Mapper)`. +3. `TypeMapper(reflect.Type, Mapper)`. +4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar. + +### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help + +The default help output is usually sufficient, but if not there are two solutions. + +1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details). +2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example. +3. Use `HelpFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments. + +### `Bind(...)` - bind values for callback hooks and Run() methods + +See the [section on hooks](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) for details. + +### Other options + +The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option). diff --git a/vendor/github.com/alecthomas/kong/build.go b/vendor/github.com/alecthomas/kong/build.go new file mode 100644 index 0000000..c629275 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/build.go @@ -0,0 +1,211 @@ +package kong + +import ( + "fmt" + "reflect" + "strings" +) + +func build(k *Kong, ast interface{}) (app *Application, err error) { + defer catch(&err) + v := reflect.ValueOf(ast) + iv := reflect.Indirect(v) + if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct { + return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast) + } + + app = &Application{} + extraFlags := k.extraFlags() + seenFlags := map[string]bool{} + for _, flag := range extraFlags { + seenFlags[flag.Name] = true + } + node := buildNode(k, iv, ApplicationNode, seenFlags) + if len(node.Positional) > 0 && len(node.Children) > 0 { + return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast) + } + app.Node = node + app.Node.Flags = append(extraFlags, app.Node.Flags...) + app.Tag = newEmptyTag() + app.Tag.Vars = k.vars + return app, nil +} + +func dashedString(s string) string { + return strings.Join(camelCase(s), "-") +} + +type flattenedField struct { + field reflect.StructField + value reflect.Value + tag *Tag +} + +func flattenedFields(v reflect.Value) (out []flattenedField) { + v = reflect.Indirect(v) + for i := 0; i < v.NumField(); i++ { + ft := v.Type().Field(i) + fv := v.Field(i) + tag := parseTag(fv, ft) + if tag.Ignored { + continue + } + if ft.Anonymous || tag.Embed { + if fv.Kind() == reflect.Interface { + fv = fv.Elem() + } + sub := flattenedFields(fv) + for _, subf := range sub { + // Assign parent if it's not already set. + if subf.tag.Group == "" { + subf.tag.Group = tag.Group + } + // Accumulate prefixes. + subf.tag.Prefix = tag.Prefix + subf.tag.Prefix + // Combine parent vars. + subf.tag.Vars = tag.Vars.CloneWith(subf.tag.Vars) + } + out = append(out, sub...) + continue + } + if !fv.CanSet() { + continue + } + out = append(out, flattenedField{field: ft, value: fv, tag: tag}) + } + return out +} + +func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node { + node := &Node{ + Type: typ, + Target: v, + Tag: newEmptyTag(), + } + for _, field := range flattenedFields(v) { + ft := field.field + fv := field.value + + tag := field.tag + name := tag.Name + if name == "" { + name = tag.Prefix + strings.ToLower(dashedString(ft.Name)) + } else { + name = tag.Prefix + name + } + + // Nested structs are either commands or args, unless they implement the Mapper interface. + if ft.Type.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil { + typ := CommandNode + if tag.Arg { + typ = ArgumentNode + } + buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags) + } else { + buildField(k, node, v, ft, fv, tag, name, seenFlags) + } + } + + // "Unsee" flags. + for _, flag := range node.Flags { + delete(seenFlags, flag.Name) + } + + // Scan through argument positionals to ensure optional is never before a required. + last := true + for i, p := range node.Positional { + if !last && p.Required { + fail("argument %q can not be required after an optional", p.Name) + } + + last = p.Required + p.Position = i + } + + return node +} + +func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) { + child := buildNode(k, fv, typ, seenFlags) + child.Tag = tag + child.Parent = node + child.Help = tag.Help + child.Hidden = tag.Hidden + child.Group = tag.Group + + if provider, ok := fv.Addr().Interface().(HelpProvider); ok { + child.Detail = provider.Help() + } + + // A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that + // a positional argument is provided to the child, and move it to the branching argument field. + if tag.Arg { + if len(child.Positional) == 0 { + fail("positional branch %s.%s must have at least one child positional argument named %q", + v.Type().Name(), ft.Name, name) + } + + value := child.Positional[0] + child.Positional = child.Positional[1:] + if child.Help == "" { + child.Help = value.Help + } + + child.Name = value.Name + if child.Name != name { + fail("first field in positional branch %s.%s must have the same name as the parent field (%s).", + v.Type().Name(), ft.Name, child.Name) + } + + child.Argument = value + } else { + child.Name = name + } + node.Children = append(node.Children, child) + + if len(child.Positional) > 0 && len(child.Children) > 0 { + fail("can't mix positional arguments and branching arguments on %s.%s", v.Type().Name(), ft.Name) + } +} + +func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) { + mapper := k.registry.ForNamedValue(tag.Type, fv) + if mapper == nil { + fail("unsupported field type %s.%s (of type %s)", v.Type(), ft.Name, ft.Type) + } + + value := &Value{ + Name: name, + Help: tag.Help, + Default: tag.Default, + DefaultValue: reflect.New(fv.Type()).Elem(), + Mapper: mapper, + Tag: tag, + Target: fv, + Enum: tag.Enum, + + // Flags are optional by default, and args are required by default. + Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional), + Format: tag.Format, + } + + if tag.Arg { + node.Positional = append(node.Positional, value) + } else { + if seenFlags[value.Name] { + fail("duplicate flag --%s", value.Name) + } + seenFlags[value.Name] = true + flag := &Flag{ + Value: value, + Short: tag.Short, + PlaceHolder: tag.PlaceHolder, + Env: tag.Env, + Group: tag.Group, + Xor: tag.Xor, + Hidden: tag.Hidden, + } + value.Flag = flag + node.Flags = append(node.Flags, flag) + } +} diff --git a/vendor/github.com/alecthomas/kong/callbacks.go b/vendor/github.com/alecthomas/kong/callbacks.go new file mode 100644 index 0000000..139a83b --- /dev/null +++ b/vendor/github.com/alecthomas/kong/callbacks.go @@ -0,0 +1,76 @@ +package kong + +import ( + "fmt" + "reflect" + "strings" +) + +type bindings map[reflect.Type]func() (reflect.Value, error) + +func (b bindings) String() string { + out := []string{} + for k := range b { + out = append(out, k.String()) + } + return "bindings{" + strings.Join(out, ", ") + "}" +} + +func (b bindings) add(values ...interface{}) bindings { + for _, v := range values { + v := v + b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil } + } + return b +} + +// Clone and add values. +func (b bindings) clone() bindings { + out := make(bindings, len(b)) + for k, v := range b { + out[k] = v + } + return out +} + +func (b bindings) merge(other bindings) bindings { + for k, v := range other { + b[k] = v + } + return b +} + +func getMethod(value reflect.Value, name string) reflect.Value { + method := value.MethodByName(name) + if !method.IsValid() { + if value.CanAddr() { + method = value.Addr().MethodByName(name) + } + } + return method +} + +func callMethod(name string, v, f reflect.Value, bindings bindings) error { + in := []reflect.Value{} + t := f.Type() + if t.NumOut() != 1 || t.Out(0) != callbackReturnSignature { + return fmt.Errorf("return value of %T.%s() must be exactly \"error\"", v.Type(), name) + } + for i := 0; i < t.NumIn(); i++ { + pt := t.In(i) + if argf, ok := bindings[pt]; ok { + argv, err := argf() + if err != nil { + return err + } + in = append(in, argv) + } else { + return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s.%s(), use kong.Bind(%s)", pt, i, v.Type(), name, pt) + } + } + out := f.Call(in) + if out[0].IsNil() { + return nil + } + return out[0].Interface().(error) +} diff --git a/vendor/github.com/alecthomas/kong/camelcase.go b/vendor/github.com/alecthomas/kong/camelcase.go new file mode 100644 index 0000000..acf29f7 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/camelcase.go @@ -0,0 +1,90 @@ +package kong + +// NOTE: This code is from https://github.com/fatih/camelcase. MIT license. + +import ( + "unicode" + "unicode/utf8" +) + +// Split splits the camelcase word and returns a list of words. It also +// supports digits. Both lower camel case and upper camel case are supported. +// For more info please check: http://en.wikipedia.org/wiki/CamelCase +// +// Examples +// +// "" => [""] +// "lowercase" => ["lowercase"] +// "Class" => ["Class"] +// "MyClass" => ["My", "Class"] +// "MyC" => ["My", "C"] +// "HTML" => ["HTML"] +// "PDFLoader" => ["PDF", "Loader"] +// "AString" => ["A", "String"] +// "SimpleXMLParser" => ["Simple", "XML", "Parser"] +// "vimRPCPlugin" => ["vim", "RPC", "Plugin"] +// "GL11Version" => ["GL", "11", "Version"] +// "99Bottles" => ["99", "Bottles"] +// "May5" => ["May", "5"] +// "BFG9000" => ["BFG", "9000"] +// "BöseÜberraschung" => ["Böse", "Überraschung"] +// "Two spaces" => ["Two", " ", "spaces"] +// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"] +// +// Splitting rules +// +// 1) If string is not valid UTF-8, return it without splitting as +// single item array. +// 2) Assign all unicode characters into one of 4 sets: lower case +// letters, upper case letters, numbers, and all other characters. +// 3) Iterate through characters of string, introducing splits +// between adjacent characters that belong to different sets. +// 4) Iterate through array of split strings, and if a given string +// is upper case: +// if subsequent string is lower case: +// move last character of upper case string to beginning of +// lower case string +func camelCase(src string) (entries []string) { + // don't split invalid utf8 + if !utf8.ValidString(src) { + return []string{src} + } + entries = []string{} + var runes [][]rune + lastClass := 0 + // split into fields based on class of unicode character + for _, r := range src { + var class int + switch { + case unicode.IsLower(r): + class = 1 + case unicode.IsUpper(r): + class = 2 + case unicode.IsDigit(r): + class = 3 + default: + class = 4 + } + if class == lastClass { + runes[len(runes)-1] = append(runes[len(runes)-1], r) + } else { + runes = append(runes, []rune{r}) + } + lastClass = class + } + // handle upper case -> lower case sequences, e.g. + // "PDFL", "oader" -> "PDF", "Loader" + for i := 0; i < len(runes)-1; i++ { + if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) { + runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...) + runes[i] = runes[i][:len(runes[i])-1] + } + } + // construct []string from results + for _, s := range runes { + if len(s) > 0 { + entries = append(entries, string(s)) + } + } + return entries +} diff --git a/vendor/github.com/alecthomas/kong/context.go b/vendor/github.com/alecthomas/kong/context.go new file mode 100644 index 0000000..ada529e --- /dev/null +++ b/vendor/github.com/alecthomas/kong/context.go @@ -0,0 +1,787 @@ +package kong + +import ( + "fmt" + "reflect" + "sort" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +// Path records the nodes and parsed values from the current command-line. +type Path struct { + Parent *Node + + // One of these will be non-nil. + App *Application + Positional *Positional + Flag *Flag + Argument *Argument + Command *Command + + // Flags added by this node. + Flags []*Flag + + // True if this Path element was created as the result of a resolver. + Resolved bool +} + +// Node returns the Node associated with this Path, or nil if Path is a non-Node. +func (p *Path) Node() *Node { + switch { + case p.App != nil: + return p.App.Node + + case p.Argument != nil: + return p.Argument + + case p.Command != nil: + return p.Command + } + return nil +} + +// Context contains the current parse context. +type Context struct { + *Kong + // A trace through parsed nodes. + Path []*Path + // Original command-line arguments. + Args []string + // Error that occurred during trace, if any. + Error error + + values map[*Value]reflect.Value // Temporary values during tracing. + bindings bindings + resolvers []Resolver // Extra context-specific resolvers. + scan *Scanner +} + +// Trace path of "args" through the grammar tree. +// +// The returned Context will include a Path of all commands, arguments, positionals and flags. +// +// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(), +// Validate() and Apply(). +func Trace(k *Kong, args []string) (*Context, error) { + c := &Context{ + Kong: k, + Args: args, + Path: []*Path{ + {App: k.Model, Flags: k.Model.Flags}, + }, + values: map[*Value]reflect.Value{}, + scan: Scan(args...), + bindings: bindings{}, + } + c.Error = c.trace(c.Model.Node) + return c, nil +} + +// Bind adds bindings to the Context. +func (c *Context) Bind(args ...interface{}) { + c.bindings.add(args...) +} + +// BindTo adds a binding to the Context. +// +// This will typically have to be called like so: +// +// BindTo(impl, (*MyInterface)(nil)) +func (c *Context) BindTo(impl, iface interface{}) { + valueOf := reflect.ValueOf(impl) + c.bindings[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil } +} + +// Value returns the value for a particular path element. +func (c *Context) Value(path *Path) reflect.Value { + switch { + case path.Positional != nil: + return c.values[path.Positional] + case path.Flag != nil: + return c.values[path.Flag.Value] + case path.Argument != nil: + return c.values[path.Argument.Argument] + } + panic("can only retrieve value for flag, argument or positional") +} + +// Selected command or argument. +func (c *Context) Selected() *Node { + var selected *Node + for _, path := range c.Path { + switch { + case path.Command != nil: + selected = path.Command + case path.Argument != nil: + selected = path.Argument + } + } + return selected +} + +// Empty returns true if there were no arguments provided. +func (c *Context) Empty() bool { + for _, path := range c.Path { + if !path.Resolved && path.App == nil { + return false + } + } + return true +} + +// Validate the current context. +func (c *Context) Validate() error { // nolint: gocyclo + err := Visit(c.Model, func(node Visitable, next Next) error { + if value, ok := node.(*Value); ok { + if value.Enum != "" && (!value.Required || value.Default != "") { + if err := checkEnum(value, value.Target); err != nil { + return err + } + } + } + return next(nil) + }) + if err != nil { + return err + } + for _, resolver := range c.combineResolvers() { + if err := resolver.Validate(c.Model); err != nil { + return err + } + } + for _, path := range c.Path { + var value *Value + switch { + case path.Flag != nil: + value = path.Flag.Value + + case path.Positional != nil: + value = path.Positional + } + if value != nil && value.Tag.Enum != "" { + if err := checkEnum(value, value.Target); err != nil { + return err + } + } + if err := checkMissingFlags(path.Flags); err != nil { + return err + } + } + // Check the terminal node. + node := c.Selected() + if node == nil { + node = c.Model.Node + } + + // Find deepest positional argument so we can check if all required positionals have been provided. + positionals := 0 + for _, path := range c.Path { + if path.Positional != nil { + positionals = path.Positional.Position + 1 + } + } + + if err := checkMissingChildren(node); err != nil { + return err + } + if err := checkMissingPositionals(positionals, node.Positional); err != nil { + return err + } + if err := checkXorDuplicates(c.Path); err != nil { + return err + } + + if node.Type == ArgumentNode { + value := node.Argument + if value.Required && !value.Set { + return fmt.Errorf("%s is required", node.Summary()) + } + } + return nil +} + +// Flags returns the accumulated available flags. +func (c *Context) Flags() (flags []*Flag) { + for _, trace := range c.Path { + flags = append(flags, trace.Flags...) + } + return +} + +// Command returns the full command path. +func (c *Context) Command() string { + command := []string{} + for _, trace := range c.Path { + switch { + case trace.Positional != nil: + command = append(command, "<"+trace.Positional.Name+">") + + case trace.Argument != nil: + command = append(command, "<"+trace.Argument.Name+">") + + case trace.Command != nil: + command = append(command, trace.Command.Name) + } + } + return strings.Join(command, " ") +} + +// AddResolver adds a context-specific resolver. +// +// This is most useful in the BeforeResolve() hook. +func (c *Context) AddResolver(resolver Resolver) { + c.resolvers = append(c.resolvers, resolver) +} + +// FlagValue returns the set value of a flag if it was encountered and exists, or its default value. +func (c *Context) FlagValue(flag *Flag) interface{} { + for _, trace := range c.Path { + if trace.Flag == flag { + v, ok := c.values[trace.Flag.Value] + if !ok { + break + } + return v.Interface() + } + } + if flag.Target.IsValid() { + return flag.Target.Interface() + } + return flag.DefaultValue.Interface() +} + +// Reset recursively resets values to defaults (as specified in the grammar) or the zero value. +func (c *Context) Reset() error { + return Visit(c.Model.Node, func(node Visitable, next Next) error { + if value, ok := node.(*Value); ok { + return next(value.Reset()) + } + return next(nil) + }) +} + +func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo + positional := 0 + + flags := []*Flag{} + for _, group := range node.AllFlags(false) { + flags = append(flags, group...) + } + + for !c.scan.Peek().IsEOL() { + token := c.scan.Peek() + switch token.Type { + case UntypedToken: + switch v := token.Value.(type) { + case string: + + switch { + case v == "-": + fallthrough + default: // nolint + c.scan.Pop() + c.scan.PushTyped(token.Value, PositionalArgumentToken) + + // Indicates end of parsing. All remaining arguments are treated as positional arguments only. + case v == "--": + c.scan.Pop() + args := []string{} + for { + token = c.scan.Pop() + if token.Type == EOLToken { + break + } + args = append(args, token.String()) + } + // Note: tokens must be pushed in reverse order. + for i := range args { + c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken) + } + + // Long flag. + case strings.HasPrefix(v, "--"): + c.scan.Pop() + // Parse it and push the tokens. + parts := strings.SplitN(v[2:], "=", 2) + if len(parts) > 1 { + c.scan.PushTyped(parts[1], FlagValueToken) + } + c.scan.PushTyped(parts[0], FlagToken) + + // Short flag. + case strings.HasPrefix(v, "-"): + c.scan.Pop() + // Note: tokens must be pushed in reverse order. + if tail := v[2:]; tail != "" { + c.scan.PushTyped(tail, ShortFlagTailToken) + } + c.scan.PushTyped(v[1:2], ShortFlagToken) + } + default: + c.scan.Pop() + c.scan.PushTyped(token.Value, PositionalArgumentToken) + } + + case ShortFlagTailToken: + c.scan.Pop() + // Note: tokens must be pushed in reverse order. + if tail := token.String()[1:]; tail != "" { + c.scan.PushTyped(tail, ShortFlagTailToken) + } + c.scan.PushTyped(token.String()[0:1], ShortFlagToken) + + case FlagToken: + if err := c.parseFlag(flags, token.String()); err != nil { + return err + } + + case ShortFlagToken: + if err := c.parseFlag(flags, token.String()); err != nil { + return err + } + + case FlagValueToken: + return fmt.Errorf("unexpected flag argument %q", token.Value) + + case PositionalArgumentToken: + candidates := []string{} + + // Ensure we've consumed all positional arguments. + if positional < len(node.Positional) { + arg := node.Positional[positional] + err := arg.Parse(c.scan, c.getValue(arg)) + if err != nil { + return err + } + c.Path = append(c.Path, &Path{ + Parent: node, + Positional: arg, + }) + positional++ + break + } + + // After positional arguments have been consumed, check commands next... + for _, branch := range node.Children { + if branch.Type == CommandNode && !branch.Hidden { + candidates = append(candidates, branch.Name) + } + if branch.Type == CommandNode && branch.Name == token.Value { + c.scan.Pop() + c.Path = append(c.Path, &Path{ + Parent: node, + Command: branch, + Flags: branch.Flags, + }) + return c.trace(branch) + } + } + + // Finally, check arguments. + for _, branch := range node.Children { + if branch.Type == ArgumentNode { + arg := branch.Argument + if err := arg.Parse(c.scan, c.getValue(arg)); err == nil { + c.Path = append(c.Path, &Path{ + Parent: node, + Argument: branch, + Flags: branch.Flags, + }) + return c.trace(branch) + } + } + } + + return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token) + default: + return fmt.Errorf("unexpected token %s", token) + } + } + return c.maybeSelectDefault(flags, node) +} + +// End of the line, check for a default command, but only if we're not displaying help, +// otherwise we'd only ever display the help for the default command. +func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error { + for _, flag := range flags { + if flag.Name == "help" && flag.Set { + return nil + } + } + var defaultNode *Path + for _, child := range node.Children { + if child.Type == CommandNode && child.Tag.Default != "" { + if defaultNode != nil { + return fmt.Errorf("can't have more than one default command under %s", node.Summary()) + } + defaultNode = &Path{ + Parent: child, + Command: child, + Flags: child.Flags, + } + } + } + if defaultNode != nil { + c.Path = append(c.Path, defaultNode) + } + return nil +} + +// Resolve walks through the traced path, applying resolvers to any unset flags. +func (c *Context) Resolve() error { + resolvers := c.combineResolvers() + if len(resolvers) == 0 { + return nil + } + + inserted := []*Path{} + for _, path := range c.Path { + for _, flag := range path.Flags { + // Flag has already been set on the command-line. + if _, ok := c.values[flag.Value]; ok { + continue + } + + // Pick the last resolved value. + var selected interface{} + for _, resolver := range resolvers { + s, err := resolver.Resolve(c, path, flag) + if err != nil { + return errors.Wrap(err, flag.ShortSummary()) + } + if s == nil { + continue + } + selected = s + } + + if selected == nil { + continue + } + + scan := Scan().PushTyped(selected, FlagValueToken) + delete(c.values, flag.Value) + err := flag.Parse(scan, c.getValue(flag.Value)) + if err != nil { + return err + } + inserted = append(inserted, &Path{ + Flag: flag, + Resolved: true, + }) + } + } + c.Path = append(inserted, c.Path...) + return nil +} + +// Combine application-level resolvers and context resolvers. +func (c *Context) combineResolvers() []Resolver { + resolvers := []Resolver{} + resolvers = append(resolvers, c.Kong.resolvers...) + resolvers = append(resolvers, c.resolvers...) + return resolvers +} + +func (c *Context) getValue(value *Value) reflect.Value { + v, ok := c.values[value] + if !ok { + v = reflect.New(value.Target.Type()).Elem() + c.values[value] = v + } + return v +} + +// ApplyDefaults if they are not already set. +func (c *Context) ApplyDefaults() error { + return Visit(c.Model.Node, func(node Visitable, next Next) error { + var value *Value + switch node := node.(type) { + case *Flag: + value = node.Value + case *Node: + value = node.Argument + case *Value: + value = node + default: + } + if value != nil { + if err := value.ApplyDefault(); err != nil { + return err + } + } + return next(nil) + }) +} + +// Apply traced context to the target grammar. +func (c *Context) Apply() (string, error) { + path := []string{} + + for _, trace := range c.Path { + var value *Value + switch { + case trace.App != nil: + case trace.Argument != nil: + path = append(path, "<"+trace.Argument.Name+">") + value = trace.Argument.Argument + case trace.Command != nil: + path = append(path, trace.Command.Name) + case trace.Flag != nil: + value = trace.Flag.Value + case trace.Positional != nil: + path = append(path, "<"+trace.Positional.Name+">") + value = trace.Positional + default: + panic("unsupported path ?!") + } + if value != nil { + value.Apply(c.getValue(value)) + } + } + + return strings.Join(path, " "), nil +} + +func (c *Context) parseFlag(flags []*Flag, match string) (err error) { + defer catch(&err) + candidates := []string{} + for _, flag := range flags { + long := "--" + flag.Name + short := "-" + string(flag.Short) + candidates = append(candidates, long) + if flag.Short != 0 { + candidates = append(candidates, short) + } + if short != match && long != match { + continue + } + // Found a matching flag. + c.scan.Pop() + err := flag.Parse(c.scan, c.getValue(flag.Value)) + if err != nil { + if e, ok := errors.Cause(err).(*expectedError); ok && e.token.InferredType().IsAny(FlagToken, ShortFlagToken) { + return errors.Errorf("%s; perhaps try %s=%q?", err, flag.ShortSummary(), e.token) + } + return err + } + c.Path = append(c.Path, &Path{Flag: flag}) + return nil + } + return findPotentialCandidates(match, candidates, "unknown flag %s", match) +} + +// RunNode calls the Run() method on an arbitrary node. +// +// This is useful in conjunction with Visit(), for dynamically running commands. +// +// Any passed values will be bindable to arguments of the target Run() method. Additionally, +// all parent nodes in the command structure will be bound. +func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) { + type targetMethod struct { + node *Node + method reflect.Value + binds bindings + } + methodBinds := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings) + methods := []targetMethod{} + for i := 0; node != nil; i, node = i+1, node.Parent { + method := getMethod(node.Target, "Run") + methodBinds = methodBinds.clone() + for p := node; p != nil; p = p.Parent { + methodBinds = methodBinds.add(p.Target.Addr().Interface()) + } + if method.IsValid() { + methods = append(methods, targetMethod{node, method, methodBinds}) + } + } + if len(methods) == 0 { + return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary()) + } + _, err = c.Apply() + if err != nil { + return err + } + + for _, method := range methods { + if err = callMethod("Run", method.node.Target, method.method, method.binds); err != nil { + return err + } + } + return nil +} + +// Run executes the Run() method on the selected command, which must exist. +// +// Any passed values will be bindable to arguments of the target Run() method. Additionally, +// all parent nodes in the command structure will be bound. +func (c *Context) Run(binds ...interface{}) (err error) { + defer catch(&err) + node := c.Selected() + if node == nil { + return fmt.Errorf("no command selected") + } + return c.RunNode(node, binds...) +} + +// PrintUsage to Kong's stdout. +// +// If summary is true, a summarised version of the help will be output. +func (c *Context) PrintUsage(summary bool) error { + options := c.helpOptions + options.Summary = summary + _ = c.help(options, c) + return nil +} + +func checkMissingFlags(flags []*Flag) error { + missing := []string{} + for _, flag := range flags { + if !flag.Required || flag.Set { + continue + } + missing = append(missing, flag.Summary()) + } + if len(missing) == 0 { + return nil + } + + return fmt.Errorf("missing flags: %s", strings.Join(missing, ", ")) +} + +func checkMissingChildren(node *Node) error { + missing := []string{} + + missingArgs := []string{} + for _, arg := range node.Positional { + if arg.Required && !arg.Set { + missingArgs = append(missingArgs, arg.Summary()) + } + } + if len(missingArgs) > 0 { + missing = append(missing, strconv.Quote(strings.Join(missingArgs, " "))) + } + + haveDefault := 0 + for _, child := range node.Children { + if child.Hidden { + continue + } + if child.Argument != nil { + if !child.Argument.Required { + continue + } + missing = append(missing, strconv.Quote(child.Summary())) + } else { + if child.Tag.Default != "" { + if len(child.Children) > 0 { + return fmt.Errorf("default command %s must not have subcommands or arguments", child.Summary()) + } + haveDefault++ + } + missing = append(missing, strconv.Quote(child.Name)) + } + } + if haveDefault > 1 { + return fmt.Errorf("more than one default command found under %s", node.Summary()) + } + if len(missing) == 0 || haveDefault > 0 { + return nil + } + + if len(missing) > 5 { + missing = append(missing[:5], "...") + } + if len(missing) == 1 { + return fmt.Errorf("expected %s", missing[0]) + } + return fmt.Errorf("expected one of %s", strings.Join(missing, ", ")) +} + +// If we're missing any positionals and they're required, return an error. +func checkMissingPositionals(positional int, values []*Value) error { + // All the positionals are in. + if positional >= len(values) { + return nil + } + + // We're low on supplied positionals, but the missing one is optional. + if !values[positional].Required { + return nil + } + + missing := []string{} + for ; positional < len(values); positional++ { + missing = append(missing, "<"+values[positional].Name+">") + } + return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " ")) +} + +func checkEnum(value *Value, target reflect.Value) error { + switch target.Kind() { + case reflect.Slice, reflect.Array: + for i := 0; i < target.Len(); i++ { + if err := checkEnum(value, target.Index(i)); err != nil { + return err + } + } + return nil + + case reflect.Map, reflect.Struct: + return errors.Errorf("enum can only be applied to a slice or value") + + default: + enumMap := value.EnumMap() + v := fmt.Sprintf("%v", target) + if enumMap[v] { + return nil + } + enums := []string{} + for enum := range enumMap { + enums = append(enums, fmt.Sprintf("%q", enum)) + } + sort.Strings(enums) + return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface()) + } +} + +func checkXorDuplicates(paths []*Path) error { + for _, path := range paths { + seen := map[string]*Flag{} + for _, flag := range path.Flags { + if !flag.Set { + continue + } + if flag.Xor == "" { + continue + } + if seen[flag.Xor] != nil { + return fmt.Errorf("--%s and --%s can't be used together", seen[flag.Xor].Name, flag.Name) + } + seen[flag.Xor] = flag + } + } + return nil +} + +func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error { + if len(haystack) == 0 { + return fmt.Errorf(format, args...) + } + closestCandidates := []string{} + for _, candidate := range haystack { + if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 { + closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate)) + } + } + prefix := fmt.Sprintf(format, args...) + if len(closestCandidates) == 1 { + return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0]) + } else if len(closestCandidates) > 1 { + return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", ")) + } + return fmt.Errorf("%s", prefix) +} diff --git a/vendor/github.com/alecthomas/kong/defaults.go b/vendor/github.com/alecthomas/kong/defaults.go new file mode 100644 index 0000000..f6728d7 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/defaults.go @@ -0,0 +1,21 @@ +package kong + +// ApplyDefaults if they are not already set. +func ApplyDefaults(target interface{}, options ...Option) error { + app, err := New(target, options...) + if err != nil { + return err + } + ctx, err := Trace(app, nil) + if err != nil { + return err + } + err = ctx.Resolve() + if err != nil { + return err + } + if err = ctx.ApplyDefaults(); err != nil { + return err + } + return ctx.Validate() +} diff --git a/vendor/github.com/alecthomas/kong/doc.go b/vendor/github.com/alecthomas/kong/doc.go new file mode 100644 index 0000000..78c4d11 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/doc.go @@ -0,0 +1,32 @@ +// Package kong aims to support arbitrarily complex command-line structures with as little developer effort as possible. +// +// Here's an example: +// +// shell rm [-f] [-r] ... +// shell ls [ ...] +// +// This can be represented by the following command-line structure: +// +// package main +// +// import "github.com/alecthomas/kong" +// +// var CLI struct { +// Rm struct { +// Force bool `short:"f" help:"Force removal."` +// Recursive bool `short:"r" help:"Recursively remove files."` +// +// Paths []string `arg help:"Paths to remove." type:"path"` +// } `cmd help:"Remove files."` +// +// Ls struct { +// Paths []string `arg optional help:"Paths to list." type:"path"` +// } `cmd help:"List paths."` +// } +// +// func main() { +// kong.Parse(&CLI) +// } +// +// See https://github.com/alecthomas/kong for details. +package kong diff --git a/vendor/github.com/alecthomas/kong/error.go b/vendor/github.com/alecthomas/kong/error.go new file mode 100644 index 0000000..30b8858 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/error.go @@ -0,0 +1,12 @@ +package kong + +// ParseError is the error type returned by Kong.Parse(). +// +// It contains the parse Context that triggered the error. +type ParseError struct { + error + Context *Context +} + +// Cause returns the original cause of the error. +func (p *ParseError) Cause() error { return p.error } diff --git a/vendor/github.com/alecthomas/kong/global.go b/vendor/github.com/alecthomas/kong/global.go new file mode 100644 index 0000000..d4b3cb5 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/global.go @@ -0,0 +1,16 @@ +package kong + +import ( + "os" +) + +// Parse constructs a new parser and parses the default command-line. +func Parse(cli interface{}, options ...Option) *Context { + parser, err := New(cli, options...) + if err != nil { + panic(err) + } + ctx, err := parser.Parse(os.Args[1:]) + parser.FatalIfErrorf(err) + return ctx +} diff --git a/vendor/github.com/alecthomas/kong/go.mod b/vendor/github.com/alecthomas/kong/go.mod new file mode 100644 index 0000000..8bbc12d --- /dev/null +++ b/vendor/github.com/alecthomas/kong/go.mod @@ -0,0 +1,10 @@ +module github.com/alecthomas/kong + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pkg/errors v0.8.1 + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.2.2 +) + +go 1.13 diff --git a/vendor/github.com/alecthomas/kong/go.sum b/vendor/github.com/alecthomas/kong/go.sum new file mode 100644 index 0000000..c935d68 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/go.sum @@ -0,0 +1,8 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/vendor/github.com/alecthomas/kong/guesswidth.go b/vendor/github.com/alecthomas/kong/guesswidth.go new file mode 100644 index 0000000..46768e6 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/guesswidth.go @@ -0,0 +1,9 @@ +// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd + +package kong + +import "io" + +func guessWidth(w io.Writer) int { + return 80 +} diff --git a/vendor/github.com/alecthomas/kong/guesswidth_unix.go b/vendor/github.com/alecthomas/kong/guesswidth_unix.go new file mode 100644 index 0000000..41d54e6 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/guesswidth_unix.go @@ -0,0 +1,41 @@ +// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd + +package kong + +import ( + "io" + "os" + "strconv" + "syscall" + "unsafe" +) + +func guessWidth(w io.Writer) int { + // check if COLUMNS env is set to comply with + // http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html + colsStr := os.Getenv("COLUMNS") + if colsStr != "" { + if cols, err := strconv.Atoi(colsStr); err == nil { + return cols + } + } + + if t, ok := w.(*os.File); ok { + fd := t.Fd() + var dimensions [4]uint16 + + if _, _, err := syscall.Syscall6( + syscall.SYS_IOCTL, + uintptr(fd), // nolint: unconvert + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(&dimensions)), // nolint: gas + 0, 0, 0, + ); err == 0 { + if dimensions[1] == 0 { + return 80 + } + return int(dimensions[1]) + } + } + return 80 +} diff --git a/vendor/github.com/alecthomas/kong/help.go b/vendor/github.com/alecthomas/kong/help.go new file mode 100644 index 0000000..4814b1c --- /dev/null +++ b/vendor/github.com/alecthomas/kong/help.go @@ -0,0 +1,416 @@ +package kong + +import ( + "bytes" + "fmt" + "go/doc" + "io" + "strings" +) + +const ( + defaultIndent = 2 + defaultColumnPadding = 4 +) + +// Help flag. +type helpValue bool + +func (h helpValue) BeforeApply(ctx *Context) error { + options := ctx.Kong.helpOptions + options.Summary = false + err := ctx.Kong.help(options, ctx) + if err != nil { + return err + } + ctx.Kong.Exit(0) + return nil +} + +// HelpOptions for HelpPrinters. +type HelpOptions struct { + // Don't print top-level usage summary. + NoAppSummary bool + + // Write a one-line summary of the context. + Summary bool + + // Write help in a more compact, but still fully-specified, form. + Compact bool + + // Tree writes command chains in a tree structure instead of listing them separately. + Tree bool + + // Indenter modulates the given prefix for the next layer in the tree view. + // The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter + // The kong.SpaceIndenter will be used by default. + Indenter HelpIndenter +} + +// Apply options to Kong as a configuration option. +func (h HelpOptions) Apply(k *Kong) error { + k.helpOptions = h + return nil +} + +// HelpProvider can be implemented by commands/args to provide detailed help. +type HelpProvider interface { + // This string is formatted by go/doc and thus has the same formatting rules. + Help() string +} + +// HelpIndenter is used to indent new layers in the help tree. +type HelpIndenter func(prefix string) string + +// HelpPrinter is used to print context-sensitive help. +type HelpPrinter func(options HelpOptions, ctx *Context) error + +// HelpValueFormatter is used to format the help text of flags and positional arguments. +type HelpValueFormatter func(value *Value) string + +// DefaultHelpValueFormatter is the default HelpValueFormatter. +func DefaultHelpValueFormatter(value *Value) string { + if value.Tag.Env == "" { + return value.Help + } + suffix := "($" + value.Tag.Env + ")" + switch { + case strings.HasSuffix(value.Help, "."): + return value.Help[:len(value.Help)-1] + " " + suffix + "." + case value.Help == "": + return suffix + default: + return value.Help + " " + suffix + } +} + +// DefaultHelpPrinter is the default HelpPrinter. +func DefaultHelpPrinter(options HelpOptions, ctx *Context) error { + if ctx.Empty() { + options.Summary = false + } + w := newHelpWriter(ctx, options) + selected := ctx.Selected() + if selected == nil { + printApp(w, ctx.Model) + } else { + printCommand(w, ctx.Model, selected) + } + return w.Write(ctx.Stdout) +} + +func printApp(w *helpWriter, app *Application) { + if !w.NoAppSummary { + w.Printf("Usage: %s%s", app.Name, app.Summary()) + } + printNodeDetail(w, app.Node, true) + cmds := app.Leaves(true) + if len(cmds) > 0 && app.HelpFlag != nil { + w.Print("") + if w.Summary { + w.Printf(`Run "%s --help" for more information.`, app.Name) + } else { + w.Printf(`Run "%s --help" for more information on a command.`, app.Name) + } + } +} + +func printCommand(w *helpWriter, app *Application, cmd *Command) { + if !w.NoAppSummary { + w.Printf("Usage: %s %s", app.Name, cmd.Summary()) + } + printNodeDetail(w, cmd, true) + if w.Summary && app.HelpFlag != nil { + w.Print("") + w.Printf(`Run "%s --help" for more information.`, cmd.FullPath()) + } +} + +func printNodeDetail(w *helpWriter, node *Node, hide bool) { + if node.Help != "" { + w.Print("") + w.Wrap(node.Help) + } + if w.Summary { + return + } + if node.Detail != "" { + w.Print("") + w.Wrap(node.Detail) + } + if len(node.Positional) > 0 { + w.Print("") + w.Print("Arguments:") + writePositionals(w.Indent(), node.Positional) + } + if flags := node.AllFlags(true); len(flags) > 0 { + w.Print("") + w.Print("Flags:") + writeFlags(w.Indent(), flags) + } + cmds := node.Leaves(hide) + if len(cmds) > 0 { + w.Print("") + w.Print("Commands:") + if w.Tree { + writeCommandTree(w, node) + } else { + iw := w.Indent() + if w.Compact { + writeCompactCommandList(cmds, iw) + } else { + writeCommandList(cmds, iw) + } + } + } +} + +func writeCommandList(cmds []*Node, iw *helpWriter) { + for i, cmd := range cmds { + if cmd.Hidden { + continue + } + printCommandSummary(iw, cmd) + if i != len(cmds)-1 { + iw.Print("") + } + } +} + +func writeCompactCommandList(cmds []*Node, iw *helpWriter) { + rows := [][2]string{} + for _, cmd := range cmds { + if cmd.Hidden { + continue + } + rows = append(rows, [2]string{cmd.Path(), cmd.Help}) + } + writeTwoColumns(iw, rows) +} + +func writeCommandTree(w *helpWriter, node *Node) { + iw := w.Indent() + rows := make([][2]string, 0, len(node.Children)*2) + for i, cmd := range node.Children { + if cmd.Hidden { + continue + } + rows = append(rows, w.CommandTree(cmd, "")...) + if i != len(node.Children)-1 { + rows = append(rows, [2]string{"", ""}) + } + } + writeTwoColumns(iw, rows) +} + +// nolint: unused +type helpCommandGroup struct { + Name string + Commands []*Node +} + +// nolint: unused, deadcode +func collectCommandGroups(nodes []*Node) []helpCommandGroup { + groups := map[string][]*Node{} + for _, node := range nodes { + groups[node.Group] = append(groups[node.Group], node) + } + out := []helpCommandGroup{} + for name, nodes := range groups { + if name == "" { + name = "Commands" + } + out = append(out, helpCommandGroup{Name: name, Commands: nodes}) + } + return out +} + +func printCommandSummary(w *helpWriter, cmd *Command) { + w.Print(cmd.Summary()) + if cmd.Help != "" { + w.Indent().Wrap(cmd.Help) + } +} + +type helpWriter struct { + indent string + width int + lines *[]string + helpFormatter HelpValueFormatter + HelpOptions +} + +func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter { + lines := []string{} + w := &helpWriter{ + indent: "", + width: guessWidth(ctx.Stdout), + lines: &lines, + helpFormatter: ctx.Kong.helpFormatter, + HelpOptions: options, + } + return w +} + +func (h *helpWriter) Printf(format string, args ...interface{}) { + h.Print(fmt.Sprintf(format, args...)) +} + +func (h *helpWriter) Print(text string) { + *h.lines = append(*h.lines, strings.TrimRight(h.indent+text, " ")) +} + +func (h *helpWriter) Indent() *helpWriter { + return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions, helpFormatter: h.helpFormatter} +} + +func (h *helpWriter) String() string { + return strings.Join(*h.lines, "\n") +} + +func (h *helpWriter) Write(w io.Writer) error { + for _, line := range *h.lines { + _, err := io.WriteString(w, line+"\n") + if err != nil { + return err + } + } + return nil +} + +func (h *helpWriter) Wrap(text string) { + w := bytes.NewBuffer(nil) + doc.ToText(w, strings.TrimSpace(text), "", " ", h.width) + for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") { + h.Print(line) + } +} + +func writePositionals(w *helpWriter, args []*Positional) { + rows := [][2]string{} + for _, arg := range args { + rows = append(rows, [2]string{arg.Summary(), w.helpFormatter(arg)}) + } + writeTwoColumns(w, rows) +} + +func writeFlags(w *helpWriter, groups [][]*Flag) { + rows := [][2]string{} + haveShort := false + for _, group := range groups { + for _, flag := range group { + if flag.Short != 0 { + haveShort = true + break + } + } + } + for i, group := range groups { + if i > 0 { + rows = append(rows, [2]string{"", ""}) + } + for _, flag := range group { + if !flag.Hidden { + rows = append(rows, [2]string{formatFlag(haveShort, flag), w.helpFormatter(flag.Value)}) + } + } + } + writeTwoColumns(w, rows) +} + +func writeTwoColumns(w *helpWriter, rows [][2]string) { + maxLeft := 375 * w.width / 1000 + if maxLeft < 30 { + maxLeft = 30 + } + // Find size of first column. + leftSize := 0 + for _, row := range rows { + if c := len(row[0]); c > leftSize && c < maxLeft { + leftSize = c + } + } + + offsetStr := strings.Repeat(" ", leftSize+defaultColumnPadding) + + for _, row := range rows { + buf := bytes.NewBuffer(nil) + doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding) + lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") + + line := fmt.Sprintf("%-*s", leftSize, row[0]) + if len(row[0]) < maxLeft { + line += fmt.Sprintf("%*s%s", defaultColumnPadding, "", lines[0]) + lines = lines[1:] + } + w.Print(line) + for _, line := range lines { + w.Printf("%s%s", offsetStr, line) + } + } +} + +// haveShort will be true if there are short flags present at all in the help. Useful for column alignment. +func formatFlag(haveShort bool, flag *Flag) string { + flagString := "" + name := flag.Name + isBool := flag.IsBool() + if flag.Short != 0 { + flagString += fmt.Sprintf("-%c, --%s", flag.Short, name) + } else { + if haveShort { + flagString += fmt.Sprintf(" --%s", name) + } else { + flagString += fmt.Sprintf("--%s", name) + } + } + if !isBool { + flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder()) + } + return flagString +} + +// CommandTree creates a tree with the given node name as root and its children's arguments and sub commands as leaves. +func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string) { + var nodeName string + switch node.Type { + default: + nodeName += prefix + node.Name + case ArgumentNode: + nodeName += prefix + "<" + node.Name + ">" + } + rows = append(rows, [2]string{nodeName, node.Help}) + if h.Indenter == nil { + prefix = SpaceIndenter(prefix) + } else { + prefix = h.Indenter(prefix) + } + for _, arg := range node.Positional { + rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help}) + } + for _, subCmd := range node.Children { + rows = append(rows, h.CommandTree(subCmd, prefix)...) + } + return +} + +// SpaceIndenter adds a space indent to the given prefix. +func SpaceIndenter(prefix string) string { + return prefix + strings.Repeat(" ", defaultIndent) +} + +// LineIndenter adds line points to every new indent. +func LineIndenter(prefix string) string { + if prefix == "" { + return "- " + } + return strings.Repeat(" ", defaultIndent) + prefix +} + +// TreeIndenter adds line points to every new indent and vertical lines to every layer. +func TreeIndenter(prefix string) string { + if prefix == "" { + return "|- " + } + return "|" + strings.Repeat(" ", defaultIndent) + prefix +} diff --git a/vendor/github.com/alecthomas/kong/hooks.go b/vendor/github.com/alecthomas/kong/hooks.go new file mode 100644 index 0000000..08fa171 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/hooks.go @@ -0,0 +1,19 @@ +package kong + +// BeforeResolve is a documentation-only interface describing hooks that run before values are set. +type BeforeResolve interface { + // This is not the correct signature - see README for details. + BeforeResolve(args ...interface{}) error +} + +// BeforeApply is a documentation-only interface describing hooks that run before values are set. +type BeforeApply interface { + // This is not the correct signature - see README for details. + BeforeApply(args ...interface{}) error +} + +// AfterApply is a documentation-only interface describing hooks that run after values are set. +type AfterApply interface { + // This is not the correct signature - see README for details. + AfterApply(args ...interface{}) error +} diff --git a/vendor/github.com/alecthomas/kong/interpolate.go b/vendor/github.com/alecthomas/kong/interpolate.go new file mode 100644 index 0000000..8c6f742 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/interpolate.go @@ -0,0 +1,39 @@ +package kong + +import ( + "fmt" + "regexp" +) + +var interpolationRegex = regexp.MustCompile(`((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`) + +// Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}. +func interpolate(s string, vars Vars, updatedVars map[string]string) (string, error) { + out := "" + matches := interpolationRegex.FindAllStringSubmatch(s, -1) + if len(matches) == 0 { + return s, nil + } + for key, val := range updatedVars { + if vars[key] != val { + vars = vars.CloneWith(updatedVars) + break + } + } + for _, match := range matches { + if name := match[2]; name != "" { + value, ok := vars[name] + if !ok { + // No default value. + if match[3] == "" { + return "", fmt.Errorf("undefined variable ${%s}", name) + } + value = match[3] + } + out += value + } else { + out += match[0] + } + } + return out, nil +} diff --git a/vendor/github.com/alecthomas/kong/kong.go b/vendor/github.com/alecthomas/kong/kong.go new file mode 100644 index 0000000..3e09f47 --- /dev/null +++ b/vendor/github.com/alecthomas/kong/kong.go @@ -0,0 +1,368 @@ +package kong + +import ( + "fmt" + "io" + "os" + "path/filepath" + "reflect" + "strings" +) + +var ( + callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem() +) + +// Error reported by Kong. +type Error struct{ msg string } + +func (e Error) Error() string { return e.msg } + +func fail(format string, args ...interface{}) { + panic(Error{msg: fmt.Sprintf(format, args...)}) +} + +// Must creates a new Parser or panics if there is an error. +func Must(ast interface{}, options ...Option) *Kong { + k, err := New(ast, options...) + if err != nil { + panic(err) + } + return k +} + +// Kong is the main parser type. +type Kong struct { + // Grammar model. + Model *Application + + // Termination function (defaults to os.Exit) + Exit func(int) + + Stdout io.Writer + Stderr io.Writer + + bindings bindings + loader ConfigurationLoader + resolvers []Resolver + registry *Registry + + noDefaultHelp bool + usageOnError bool + help HelpPrinter + helpFormatter HelpValueFormatter + helpOptions HelpOptions + helpFlag *Flag + vars Vars + + // Set temporarily by Options. These are applied after build(). + postBuildOptions []Option +} + +// New creates a new Kong parser on grammar. +// +// See the README (https://github.com/alecthomas/kong) for usage instructions. +func New(grammar interface{}, options ...Option) (*Kong, error) { + k := &Kong{ + Exit: os.Exit, + Stdout: os.Stdout, + Stderr: os.Stderr, + registry: NewRegistry().RegisterDefaults(), + vars: Vars{}, + bindings: bindings{}, + helpFormatter: DefaultHelpValueFormatter, + } + + options = append(options, Bind(k)) + + for _, option := range options { + if err := option.Apply(k); err != nil { + return nil, err + } + } + + if k.help == nil { + k.help = DefaultHelpPrinter + } + + model, err := build(k, grammar) + if err != nil { + return k, err + } + model.Name = filepath.Base(os.Args[0]) + k.Model = model + k.Model.HelpFlag = k.helpFlag + + for _, option := range k.postBuildOptions { + if err = option.Apply(k); err != nil { + return nil, err + } + } + k.postBuildOptions = nil + + if err = k.interpolate(k.Model.Node); err != nil { + return nil, err + } + + k.bindings.add(k.vars) + + return k, nil +} + +type varStack []Vars + +func (v *varStack) head() Vars { return (*v)[len(*v)-1] } +func (v *varStack) pop() { *v = (*v)[:len(*v)-1] } +func (v *varStack) push(vars Vars) Vars { + if len(*v) != 0 { + vars = (*v)[len(*v)-1].CloneWith(vars) + } + *v = append(*v, vars) + return vars +} + +// Interpolate variables into model. +func (k *Kong) interpolate(node *Node) (err error) { + stack := varStack{} + return Visit(node, func(node Visitable, next Next) error { + switch node := node.(type) { + case *Node: + vars := stack.push(node.Vars()) + node.Help, err = interpolate(node.Help, vars, nil) + if err != nil { + return fmt.Errorf("help for %s: %s", node.Path(), err) + } + err = next(nil) + stack.pop() + return err + + case *Value: + return next(k.interpolateValue(node, stack.head())) + } + return next(nil) + }) +} + +func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) { + if len(value.Tag.Vars) > 0 { + vars = vars.CloneWith(value.Tag.Vars) + } + if value.Default, err = interpolate(value.Default, vars, nil); err != nil { + return fmt.Errorf("default value for %s: %s", value.Summary(), err) + } + if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil { + return fmt.Errorf("enum value for %s: %s", value.Summary(), err) + } + value.Help, err = interpolate(value.Help, vars, map[string]string{ + "default": value.Default, + "enum": value.Enum, + }) + if err != nil { + return fmt.Errorf("help for %s: %s", value.Summary(), err) + } + return nil +} + +// Provide additional builtin flags, if any. +func (k *Kong) extraFlags() []*Flag { + if k.noDefaultHelp { + return nil + } + var helpTarget helpValue + value := reflect.ValueOf(&helpTarget).Elem() + helpFlag := &Flag{ + Short: 'h', + Value: &Value{ + Name: "help", + Help: "Show context-sensitive help.", + Target: value, + Tag: &Tag{}, + Mapper: k.registry.ForValue(value), + DefaultValue: reflect.ValueOf(false), + }, + } + helpFlag.Flag = helpFlag + k.helpFlag = helpFlag + return []*Flag{helpFlag} +} + +// Parse arguments into target. +// +// The return Context can be used to further inspect the parsed command-line, to format help, to find the +// selected command, to run command Run() methods, and so on. See Context and README for more information. +// +// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically +// invalid one, which will report a normal error). +func (k *Kong) Parse(args []string) (ctx *Context, err error) { + defer catch(&err) + ctx, err = Trace(k, args) + if err != nil { + return nil, err + } + if ctx.Error != nil { + return nil, &ParseError{error: ctx.Error, Context: ctx} + } + if err = ctx.Reset(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = k.applyHook(ctx, "BeforeResolve"); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = ctx.Resolve(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = k.applyHook(ctx, "BeforeApply"); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if _, err = ctx.Apply(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = ctx.Validate(); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + if err = k.applyHook(ctx, "AfterApply"); err != nil { + return nil, &ParseError{error: err, Context: ctx} + } + return ctx, nil +} + +func (k *Kong) applyHook(ctx *Context, name string) error { + for _, trace := range ctx.Path { + var value reflect.Value + switch { + case trace.App != nil: + value = trace.App.Target + case trace.Argument != nil: + value = trace.Argument.Target + case trace.Command != nil: + value = trace.Command.Target + case trace.Positional != nil: + value = trace.Positional.Target + case trace.Flag != nil: + value = trace.Flag.Value.Target + default: + panic("unsupported Path") + } + method := getMethod(value, name) + if !method.IsValid() { + continue + } + binds := k.bindings.clone() + binds.add(ctx, trace) + binds.add(trace.Node().Vars().CloneWith(k.vars)) + binds.merge(ctx.bindings) + if err := callMethod(name, value, method, binds); err != nil { + return err + } + } + // Path[0] will always be the app root. + return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name) +} + +// Call hook on any unset flags with default values. +func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error { + if node == nil { + return nil + } + return Visit(node, func(n Visitable, next Next) error { + node, ok := n.(*Node) + if !ok { + return next(nil) + } + binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars)) + for _, flag := range node.Flags { + if flag.Default == "" || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() { + continue + } + method := getMethod(flag.Target, name) + if !method.IsValid() { + continue + } + path := &Path{Flag: flag} + if err := callMethod(name, flag.Target, method, binds.clone().add(path)); err != nil { + return next(err) + } + } + return next(nil) + }) +} + +func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) { + lines := strings.Split(fmt.Sprintf(format, args...), "\n") + leader := "" + for _, l := range leaders { + if l == "" { + continue + } + leader += l + ": " + } + fmt.Fprintf(w, "%s%s\n", leader, lines[0]) + for _, line := range lines[1:] { + fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line) + } +} + +// Printf writes a message to Kong.Stdout with the application name prefixed. +func (k *Kong) Printf(format string, args ...interface{}) *Kong { + formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...) + return k +} + +// Errorf writes a message to Kong.Stderr with the application name prefixed. +func (k *Kong) Errorf(format string, args ...interface{}) *Kong { + formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...) + return k +} + +// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status. +func (k *Kong) Fatalf(format string, args ...interface{}) { + k.Errorf(format, args...) + k.Exit(1) +} + +// FatalIfErrorf terminates with an error message if err != nil. +func (k *Kong) FatalIfErrorf(err error, args ...interface{}) { + if err == nil { + return + } + msg := err.Error() + if len(args) > 0 { + msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error() + } + // Maybe display usage information. + if err, ok := err.(*ParseError); ok && k.usageOnError { + options := k.helpOptions + _ = k.help(options, err.Context) + fmt.Fprintln(k.Stdout) + } + k.Errorf("%s", msg) + k.Exit(1) +} + +// LoadConfig from path using the loader configured via Configuration(loader). +// +// "path" will have ~ and any variables expanded. +func (k *Kong) LoadConfig(path string) (Resolver, error) { + var err error + path = ExpandPath(path) + path, err = interpolate(path, k.vars, nil) + if err != nil { + return nil, err + } + r, err := os.Open(path) // nolint: gas + if err != nil { + return nil, err + } + defer r.Close() + + return k.loader(r) +} + +func catch(err *error) { + msg := recover() + if test, ok := msg.(Error); ok { + *err = test + } else if msg != nil { + panic(msg) + } +} diff --git a/vendor/github.com/alecthomas/kong/kong.png b/vendor/github.com/alecthomas/kong/kong.png new file mode 100644 index 0000000000000000000000000000000000000000..151fb08dbd4858c4fbebb76f46bf6c6535f8f099 GIT binary patch literal 68610 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%0Au<7CSicYLeX!B6|rppsfl&f{V4* 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