├── .gitignore ├── files └── animation-dcsg-usage-example.gif ├── vendor ├── gopkg.in │ └── alecthomas │ │ └── kingpin.v2 │ │ ├── guesswidth.go │ │ ├── guesswidth_unix.go │ │ ├── completions.go │ │ ├── envar.go │ │ ├── COPYING │ │ ├── actions.go │ │ ├── values.json │ │ ├── doc.go │ │ ├── global.go │ │ ├── args.go │ │ ├── model.go │ │ ├── parsers.go │ │ ├── usage.go │ │ ├── cmd.go │ │ ├── templates.go │ │ ├── flags.go │ │ ├── parser.go │ │ └── values.go ├── github.com │ ├── alecthomas │ │ ├── units │ │ │ ├── README.md │ │ │ ├── doc.go │ │ │ ├── si.go │ │ │ ├── COPYING │ │ │ ├── bytes.go │ │ │ └── util.go │ │ └── template │ │ │ ├── README.md │ │ │ ├── LICENSE │ │ │ ├── helper.go │ │ │ ├── template.go │ │ │ ├── parse │ │ │ └── lex.go │ │ │ ├── doc.go │ │ │ └── funcs.go │ └── pkg │ │ └── errors │ │ ├── appveyor.yml │ │ ├── LICENSE │ │ ├── README.md │ │ ├── stack.go │ │ └── errors.go └── vendor.json ├── Makefile ├── dcsg_test.go ├── CHANGELOG.md ├── installer_test.go ├── executor.go ├── uninstaller.go ├── main.go ├── README.md ├── dcsg.go ├── installer.go └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | .DS_Store 3 | 4 | /dcsg 5 | -------------------------------------------------------------------------------- /files/animation-dcsg-usage-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andreaskoch/dcsg/HEAD/files/animation-dcsg-usage-example.gif -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth.go: -------------------------------------------------------------------------------- 1 | // +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd 2 | 3 | package kingpin 4 | 5 | import "io" 6 | 7 | func guessWidth(w io.Writer) int { 8 | return 80 9 | } 10 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/units/README.md: -------------------------------------------------------------------------------- 1 | # Units - Helpful unit multipliers and functions for Go 2 | 3 | The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package. 4 | 5 | It allows for code like this: 6 | 7 | ```go 8 | n, err := ParseBase2Bytes("1KB") 9 | // n == 1024 10 | n = units.Mebibyte * 512 11 | ``` 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | go build -o bin/dcsg 3 | 4 | install: 5 | go install 6 | 7 | test: 8 | go test 9 | 10 | crosscompile: 11 | GOOS=linux GOARCH=amd64 go build -o bin/dcsg_linux_amd64 12 | GOOS=linux GOARCH=arm GOARM=5 go build -o bin/dcsg_linux_arm_5 13 | GOOS=linux GOARCH=arm GOARM=6 go build -o bin/dcsg_linux_arm_6 14 | GOOS=linux GOARCH=arm GOARM=7 go build -o bin/dcsg_linux_arm_7 15 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/units/doc.go: -------------------------------------------------------------------------------- 1 | // Package units provides helpful unit multipliers and functions for Go. 2 | // 3 | // The goal of this package is to have functionality similar to the time [1] package. 4 | // 5 | // 6 | // [1] http://golang.org/pkg/time/ 7 | // 8 | // It allows for code like this: 9 | // 10 | // n, err := ParseBase2Bytes("1KB") 11 | // // n == 1024 12 | // n = units.Mebibyte * 512 13 | package units 14 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/template/README.md: -------------------------------------------------------------------------------- 1 | # Go's `text/template` package with newline elision 2 | 3 | This is a fork of Go 1.4's [text/template](http://golang.org/pkg/text/template/) package with one addition: a backslash immediately after a closing delimiter will delete all subsequent newlines until a non-newline. 4 | 5 | eg. 6 | 7 | ``` 8 | {{if true}}\ 9 | hello 10 | {{end}}\ 11 | ``` 12 | 13 | Will result in: 14 | 15 | ``` 16 | hello\n 17 | ``` 18 | 19 | Rather than: 20 | 21 | ``` 22 | \n 23 | hello\n 24 | \n 25 | ``` 26 | -------------------------------------------------------------------------------- /dcsg_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func Test_getProjectDirectory(t *testing.T) { 6 | 7 | fileName := "/var/www/some-project/docker-compose.yml" 8 | result, _ := getProjectDirectory(fileName) 9 | 10 | expected := "/var/www/some-project" 11 | if result != expected { 12 | t.Fail() 13 | t.Logf("getProjectDirectory(%s) returned %q instead of %q", fileName, result, expected) 14 | } 15 | } 16 | 17 | func Test_getProjectName(t *testing.T) { 18 | 19 | fileName := "/var/www/some-project/docker-compose.yml" 20 | result, _ := getProjectName(fileName) 21 | 22 | expected := "someproject" 23 | if result != expected { 24 | t.Fail() 25 | t.Logf("getProjectName(%s) returned %q instead of %q", fileName, result, expected) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/units/si.go: -------------------------------------------------------------------------------- 1 | package units 2 | 3 | // SI units. 4 | type SI int64 5 | 6 | // SI unit multiples. 7 | const ( 8 | Kilo SI = 1000 9 | Mega = Kilo * 1000 10 | Giga = Mega * 1000 11 | Tera = Giga * 1000 12 | Peta = Tera * 1000 13 | Exa = Peta * 1000 14 | ) 15 | 16 | func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 { 17 | return map[string]float64{ 18 | shortSuffix: 1, 19 | "K" + suffix: float64(scale), 20 | "M" + suffix: float64(scale * scale), 21 | "G" + suffix: float64(scale * scale * scale), 22 | "T" + suffix: float64(scale * scale * scale * scale), 23 | "P" + suffix: float64(scale * scale * scale * scale * scale), 24 | "E" + suffix: float64(scale * scale * scale * scale * scale * scale), 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/appveyor.yml: -------------------------------------------------------------------------------- 1 | version: build-{build}.{branch} 2 | 3 | clone_folder: C:\gopath\src\github.com\pkg\errors 4 | shallow_clone: true # for startup speed 5 | 6 | environment: 7 | GOPATH: C:\gopath 8 | 9 | platform: 10 | - x64 11 | 12 | # http://www.appveyor.com/docs/installed-software 13 | install: 14 | # some helpful output for debugging builds 15 | - go version 16 | - go env 17 | # pre-installed MinGW at C:\MinGW is 32bit only 18 | # but MSYS2 at C:\msys64 has mingw64 19 | - set PATH=C:\msys64\mingw64\bin;%PATH% 20 | - gcc --version 21 | - g++ --version 22 | 23 | build_script: 24 | - go install -v ./... 25 | 26 | test_script: 27 | - set PATH=C:\gopath\bin;%PATH% 28 | - go test -v ./... 29 | 30 | #artifacts: 31 | # - path: '%GOPATH%\bin\*.exe' 32 | deploy: off 33 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/guesswidth_unix.go: -------------------------------------------------------------------------------- 1 | // +build !appengine,linux freebsd darwin dragonfly netbsd openbsd 2 | 3 | package kingpin 4 | 5 | import ( 6 | "io" 7 | "os" 8 | "strconv" 9 | "syscall" 10 | "unsafe" 11 | ) 12 | 13 | func guessWidth(w io.Writer) int { 14 | // check if COLUMNS env is set to comply with 15 | // http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html 16 | colsStr := os.Getenv("COLUMNS") 17 | if colsStr != "" { 18 | if cols, err := strconv.Atoi(colsStr); err == nil { 19 | return cols 20 | } 21 | } 22 | 23 | if t, ok := w.(*os.File); ok { 24 | fd := t.Fd() 25 | var dimensions [4]uint16 26 | 27 | if _, _, err := syscall.Syscall6( 28 | syscall.SYS_IOCTL, 29 | uintptr(fd), 30 | uintptr(syscall.TIOCGWINSZ), 31 | uintptr(unsafe.Pointer(&dimensions)), 32 | 0, 0, 0, 33 | ); err == 0 { 34 | return int(dimensions[1]) 35 | } 36 | } 37 | return 80 38 | } 39 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/completions.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | // HintAction is a function type who is expected to return a slice of possible 4 | // command line arguments. 5 | type HintAction func() []string 6 | type completionsMixin struct { 7 | hintActions []HintAction 8 | builtinHintActions []HintAction 9 | } 10 | 11 | func (a *completionsMixin) addHintAction(action HintAction) { 12 | a.hintActions = append(a.hintActions, action) 13 | } 14 | 15 | // Allow adding of HintActions which are added internally, ie, EnumVar 16 | func (a *completionsMixin) addHintActionBuiltin(action HintAction) { 17 | a.builtinHintActions = append(a.builtinHintActions, action) 18 | } 19 | 20 | func (a *completionsMixin) resolveCompletions() []string { 21 | var hints []string 22 | 23 | options := a.builtinHintActions 24 | if len(a.hintActions) > 0 { 25 | // User specified their own hintActions. Use those instead. 26 | options = a.hintActions 27 | } 28 | 29 | for _, hintAction := range options { 30 | hints = append(hints, hintAction()...) 31 | } 32 | return hints 33 | } 34 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/envar.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "os" 5 | "regexp" 6 | ) 7 | 8 | var ( 9 | envVarValuesSeparator = "\r?\n" 10 | envVarValuesTrimmer = regexp.MustCompile(envVarValuesSeparator + "$") 11 | envVarValuesSplitter = regexp.MustCompile(envVarValuesSeparator) 12 | ) 13 | 14 | type envarMixin struct { 15 | envar string 16 | noEnvar bool 17 | } 18 | 19 | func (e *envarMixin) HasEnvarValue() bool { 20 | return e.GetEnvarValue() != "" 21 | } 22 | 23 | func (e *envarMixin) GetEnvarValue() string { 24 | if e.noEnvar || e.envar == "" { 25 | return "" 26 | } 27 | return os.Getenv(e.envar) 28 | } 29 | 30 | func (e *envarMixin) GetSplitEnvarValue() []string { 31 | values := make([]string, 0) 32 | 33 | envarValue := e.GetEnvarValue() 34 | if envarValue == "" { 35 | return values 36 | } 37 | 38 | // Split by new line to extract multiple values, if any. 39 | trimmed := envVarValuesTrimmer.ReplaceAllString(envarValue, "") 40 | for _, value := range envVarValuesSplitter.Split(trimmed, -1) { 41 | values = append(values, value) 42 | } 43 | 44 | return values 45 | } 46 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/units/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Alec Thomas 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Alec Thomas 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/actions.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | // Action callback executed at various stages after all values are populated. 4 | // The application, commands, arguments and flags all have corresponding 5 | // actions. 6 | type Action func(*ParseContext) error 7 | 8 | type actionMixin struct { 9 | actions []Action 10 | preActions []Action 11 | } 12 | 13 | type actionApplier interface { 14 | applyActions(*ParseContext) error 15 | applyPreActions(*ParseContext) error 16 | } 17 | 18 | func (a *actionMixin) addAction(action Action) { 19 | a.actions = append(a.actions, action) 20 | } 21 | 22 | func (a *actionMixin) addPreAction(action Action) { 23 | a.preActions = append(a.preActions, action) 24 | } 25 | 26 | func (a *actionMixin) applyActions(context *ParseContext) error { 27 | for _, action := range a.actions { 28 | if err := action(context); err != nil { 29 | return err 30 | } 31 | } 32 | return nil 33 | } 34 | 35 | func (a *actionMixin) applyPreActions(context *ParseContext) error { 36 | for _, preAction := range a.preActions { 37 | if err := preAction(context); err != nil { 38 | return err 39 | } 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /vendor/vendor.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment": "", 3 | "ignore": "test", 4 | "package": [ 5 | { 6 | "checksumSHA1": "KmjnydoAbofMieIWm+it5OWERaM=", 7 | "path": "github.com/alecthomas/template", 8 | "revision": "a0175ee3bccc567396460bf5acd36800cb10c49c", 9 | "revisionTime": "2016-04-05T07:15:01Z" 10 | }, 11 | { 12 | "checksumSHA1": "3wt0pTXXeS+S93unwhGoLIyGX/Q=", 13 | "path": "github.com/alecthomas/template/parse", 14 | "revision": "a0175ee3bccc567396460bf5acd36800cb10c49c", 15 | "revisionTime": "2016-04-05T07:15:01Z" 16 | }, 17 | { 18 | "checksumSHA1": "fCc3grA7vIxfBru7R3SqjcW+oLI=", 19 | "path": "github.com/alecthomas/units", 20 | "revision": "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a", 21 | "revisionTime": "2015-10-22T06:55:26Z" 22 | }, 23 | { 24 | "checksumSHA1": "Hky3u+8Rqum+wB5BHMj0A8ZmT4g=", 25 | "path": "github.com/pkg/errors", 26 | "revision": "a887431f7f6ef7687b556dbf718d9f351d4858a0", 27 | "revisionTime": "2016-09-16T11:02:12Z" 28 | }, 29 | { 30 | "checksumSHA1": "SeYI7DRWrd0Ku+CLavuwIz3EEmQ=", 31 | "path": "gopkg.in/alecthomas/kingpin.v2", 32 | "revision": "e9044be3ab2a8e11d4e1f418d12f0790d57e8d70", 33 | "revisionTime": "2016-08-29T10:30:05Z" 34 | } 35 | ], 36 | "rootPath": "github.com/andreaskoch/dcsg" 37 | } 38 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## [v0.4.0] 8 | 9 | Improved systemd reliability (thanks to @hermsi1337) 10 | 11 | ### Changed 12 | - Change the application version to v0.3.0 13 | - Improve systemd-reliability (see: dcsg#6) 14 | 15 | ## [v0.3.0] - 2017-03-11 16 | 17 | Timeouts and restart policy 18 | 19 | ### Added 20 | - Add a startup timeout (see: dcsg#4) 21 | - Add restart-policy (see: dcsg#4) 22 | 23 | ## [v0.2.0] 24 | 25 | systemd service improvements (thanks to @hermsi1337) 26 | 27 | ### Added 28 | - Add an animation illustrating the usage of dcsg 29 | 30 | ### Changed 31 | - Pull the latest image before starting (see: dcsg/pull/2) 32 | - Stop the containers before removing them (see: dcsg/pull/2) 33 | 34 | ## [v0.1.1-alpha] - 2016-12-18 35 | 36 | Set the working directory for the systemd services 37 | 38 | ### Changed 39 | - Set the working directory (see: https://www.freedesktop.org/software/systemd/man/systemd.exec.html) for the docker-compose systemd services. Otherwise docker-compose cannot locate `env_file`s (see: https://docs.docker.com/compose/compose-file/#/envfile) 40 | 41 | ## [v0.1.0-alpha] - 2016-12-18 42 | 43 | The prototype 44 | 45 | ### Added 46 | - A prototype for the dcsg - A systemd service generator for Docker Compose projects 47 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/template/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /installer_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | ) 9 | 10 | func getTestInstaller() *systemdInstaller { 11 | targetDirectory := os.TempDir() 12 | commandExecutor := newExecutor(os.Stdin, os.Stdout, os.Stderr, "", false) 13 | return &systemdInstaller{targetDirectory, commandExecutor, false} 14 | } 15 | 16 | func Test_createSystemdService_NoErrorIsReturned(t *testing.T) { 17 | installer := getTestInstaller() 18 | 19 | serviceViewModel := serviceDefinition{ 20 | ProjectName: "exampleproject", 21 | ProjectDirectory: "/var/www/example-project", 22 | DockerComposeFile: "docker-compose.yml", 23 | } 24 | 25 | result := installer.createSystemdService(serviceViewModel) 26 | 27 | if result != nil { 28 | t.Fail() 29 | t.Logf("createSystemdService returned %s", result) 30 | } 31 | } 32 | 33 | func Test_createSystemdService_ServiceFileIsWritten(t *testing.T) { 34 | installer := getTestInstaller() 35 | 36 | serviceViewModel := serviceDefinition{ 37 | ProjectName: "exampleproject", 38 | ProjectDirectory: "/var/www/example-project", 39 | DockerComposeFile: "docker-compose.yml", 40 | } 41 | 42 | result := installer.createSystemdService(serviceViewModel) 43 | 44 | if result != nil { 45 | t.Fail() 46 | t.Logf("createSystemdService returned %s", result) 47 | } 48 | 49 | targetFilePath := filepath.Join(installer.systemdDirectory, fmt.Sprintf("%s.service", serviceViewModel.ProjectName)) 50 | _, err := os.Stat(targetFilePath) 51 | if err != nil { 52 | t.Fail() 53 | t.Logf("createSystemdService failed to create the service file %s: %s", targetFilePath, err) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/values.json: -------------------------------------------------------------------------------- 1 | [ 2 | {"type": "bool", "parser": "strconv.ParseBool(s)"}, 3 | {"type": "string", "parser": "s, error(nil)", "format": "string(*f.v)", "plural": "Strings"}, 4 | {"type": "uint", "parser": "strconv.ParseUint(s, 0, 64)", "plural": "Uints"}, 5 | {"type": "uint8", "parser": "strconv.ParseUint(s, 0, 8)"}, 6 | {"type": "uint16", "parser": "strconv.ParseUint(s, 0, 16)"}, 7 | {"type": "uint32", "parser": "strconv.ParseUint(s, 0, 32)"}, 8 | {"type": "uint64", "parser": "strconv.ParseUint(s, 0, 64)"}, 9 | {"type": "int", "parser": "strconv.ParseFloat(s, 64)", "plural": "Ints"}, 10 | {"type": "int8", "parser": "strconv.ParseInt(s, 0, 8)"}, 11 | {"type": "int16", "parser": "strconv.ParseInt(s, 0, 16)"}, 12 | {"type": "int32", "parser": "strconv.ParseInt(s, 0, 32)"}, 13 | {"type": "int64", "parser": "strconv.ParseInt(s, 0, 64)"}, 14 | {"type": "float64", "parser": "strconv.ParseFloat(s, 64)"}, 15 | {"type": "float32", "parser": "strconv.ParseFloat(s, 32)"}, 16 | {"name": "Duration", "type": "time.Duration", "no_value_parser": true}, 17 | {"name": "IP", "type": "net.IP", "no_value_parser": true}, 18 | {"name": "TCPAddr", "Type": "*net.TCPAddr", "plural": "TCPList", "no_value_parser": true}, 19 | {"name": "ExistingFile", "Type": "string", "plural": "ExistingFiles", "no_value_parser": true}, 20 | {"name": "ExistingDir", "Type": "string", "plural": "ExistingDirs", "no_value_parser": true}, 21 | {"name": "ExistingFileOrDir", "Type": "string", "plural": "ExistingFilesOrDirs", "no_value_parser": true}, 22 | {"name": "Regexp", "Type": "*regexp.Regexp", "parser": "regexp.Compile(s)"}, 23 | {"name": "ResolvedIP", "Type": "net.IP", "parser": "resolveHost(s)", "help": "Resolve a hostname or IP to an IP."}, 24 | {"name": "HexBytes", "Type": "[]byte", "parser": "hex.DecodeString(s)", "help": "Bytes as a hex string."} 25 | ] 26 | -------------------------------------------------------------------------------- /executor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | type Executor interface { 14 | Run(command ...string) error 15 | } 16 | 17 | func newExecutor(in io.Reader, out, err io.Writer, baseDirectory string, dryRun bool) Executor { 18 | return &CommandExecutor{ 19 | in: in, 20 | out: out, 21 | err: err, 22 | 23 | baseDirectory: baseDirectory, 24 | dryRun: dryRun, 25 | } 26 | } 27 | 28 | type CommandExecutor struct { 29 | in io.Reader 30 | out io.Writer 31 | err io.Writer 32 | 33 | baseDirectory string 34 | dryRun bool 35 | } 36 | 37 | func (executor *CommandExecutor) InDirectory(directory string) CommandExecutor { 38 | 39 | newWorkingDirectory := filepath.Join(executor.baseDirectory, directory) 40 | expandedWorkingDirectory := os.ExpandEnv(newWorkingDirectory) 41 | 42 | return CommandExecutor{ 43 | in: executor.in, 44 | out: executor.out, 45 | err: executor.err, 46 | 47 | baseDirectory: expandedWorkingDirectory, 48 | dryRun: executor.dryRun, 49 | } 50 | } 51 | 52 | func (executor *CommandExecutor) Run(command ...string) error { 53 | 54 | if len(command) == 0 { 55 | return fmt.Errorf("No command given") 56 | } 57 | 58 | workingDirectory := executor.baseDirectory 59 | expandedWorkingDirectory := os.ExpandEnv(workingDirectory) 60 | expandedCommandName := os.ExpandEnv(command[0]) 61 | var expandedArguments []string 62 | for _, argument := range command[1:] { 63 | expandedArguments = append(expandedArguments, os.ExpandEnv(argument)) 64 | } 65 | 66 | cmd := exec.Command(expandedCommandName, expandedArguments...) 67 | 68 | cmd.Dir = expandedWorkingDirectory 69 | cmd.Env = os.Environ() 70 | 71 | cmd.Stdout = executor.out 72 | cmd.Stderr = executor.err 73 | cmd.Stdin = executor.in 74 | 75 | log.Printf("%s: %s %s", expandedWorkingDirectory, command[0], strings.Join(command[1:], " ")) 76 | 77 | if !executor.dryRun { 78 | err := cmd.Run() 79 | if err != nil { 80 | log.Printf("Error running %s: %v", command, err) 81 | return err 82 | } 83 | } 84 | 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /uninstaller.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type uninstaller interface { 13 | Uninstall(projectDirectory, dockerComposeFileName, projectName string) error 14 | } 15 | 16 | type systemdUninstaller struct { 17 | systemdDirectory string 18 | commandExecutor Executor 19 | dryRun bool 20 | } 21 | 22 | func (uninstaller *systemdUninstaller) Uninstall(projectDirectory, dockerComposeFileName, projectName string) error { 23 | 24 | serviceName := getServiceName(projectName) 25 | serviceViewModel := serviceDefinition{ 26 | ProjectName: projectName, 27 | ProjectDirectory: projectDirectory, 28 | DockerComposeFile: dockerComposeFileName, 29 | } 30 | 31 | stopError := uninstaller.commandExecutor.Run("systemctl", "stop", serviceName) 32 | if stopError != nil { 33 | return errors.Wrap(stopError, fmt.Sprintf("Failed to stop %q", serviceName)) 34 | } 35 | 36 | disableError := uninstaller.commandExecutor.Run("systemctl", "disable", serviceName) 37 | if disableError != nil { 38 | return errors.Wrap(disableError, fmt.Sprintf("Failed to disable %q", serviceName)) 39 | } 40 | 41 | removeError := uninstaller.removeSystemdService(serviceViewModel) 42 | if removeError != nil { 43 | return errors.Wrap(removeError, fmt.Sprintf("Failed to remove the systemd service %q", serviceViewModel.ProjectName)) 44 | } 45 | 46 | reloadError := uninstaller.commandExecutor.Run("systemctl", "daemon-reload") 47 | if reloadError != nil { 48 | return errors.Wrap(reloadError, "Failed to reload the systemd configuration") 49 | } 50 | 51 | return nil 52 | } 53 | 54 | func (uninstaller *systemdUninstaller) removeSystemdService(service serviceDefinition) error { 55 | serviceFilePath := filepath.Join(uninstaller.systemdDirectory, getServiceName(service.ProjectName)) 56 | 57 | if uninstaller.dryRun { 58 | log.Println("Would remove file:", serviceFilePath) 59 | } else { 60 | removeError := os.Remove(serviceFilePath) 61 | if removeError != nil { 62 | return errors.Wrap(removeError, fmt.Sprintf("Failed to remove %q", serviceFilePath)) 63 | } 64 | } 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/doc.go: -------------------------------------------------------------------------------- 1 | // Package kingpin provides command line interfaces like this: 2 | // 3 | // $ chat 4 | // usage: chat [] [] [ ...] 5 | // 6 | // Flags: 7 | // --debug enable debug mode 8 | // --help Show help. 9 | // --server=127.0.0.1 server address 10 | // 11 | // Commands: 12 | // help 13 | // Show help for a command. 14 | // 15 | // post [] 16 | // Post a message to a channel. 17 | // 18 | // register 19 | // Register a new user. 20 | // 21 | // $ chat help post 22 | // usage: chat [] post [] [] 23 | // 24 | // Post a message to a channel. 25 | // 26 | // Flags: 27 | // --image=IMAGE image to post 28 | // 29 | // Args: 30 | // channel to post to 31 | // [] text to post 32 | // $ chat post --image=~/Downloads/owls.jpg pics 33 | // 34 | // From code like this: 35 | // 36 | // package main 37 | // 38 | // import "gopkg.in/alecthomas/kingpin.v1" 39 | // 40 | // var ( 41 | // debug = kingpin.Flag("debug", "enable debug mode").Default("false").Bool() 42 | // serverIP = kingpin.Flag("server", "server address").Default("127.0.0.1").IP() 43 | // 44 | // register = kingpin.Command("register", "Register a new user.") 45 | // registerNick = register.Arg("nick", "nickname for user").Required().String() 46 | // registerName = register.Arg("name", "name of user").Required().String() 47 | // 48 | // post = kingpin.Command("post", "Post a message to a channel.") 49 | // postImage = post.Flag("image", "image to post").ExistingFile() 50 | // postChannel = post.Arg("channel", "channel to post to").Required().String() 51 | // postText = post.Arg("text", "text to post").String() 52 | // ) 53 | // 54 | // func main() { 55 | // switch kingpin.Parse() { 56 | // // Register user 57 | // case "register": 58 | // println(*registerNick) 59 | // 60 | // // Post message 61 | // case "post": 62 | // if *postImage != nil { 63 | // } 64 | // if *postText != "" { 65 | // } 66 | // } 67 | // } 68 | package kingpin 69 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/README.md: -------------------------------------------------------------------------------- 1 | # errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) 2 | 3 | Package errors provides simple error handling primitives. 4 | 5 | `go get github.com/pkg/errors` 6 | 7 | The traditional error handling idiom in Go is roughly akin to 8 | ```go 9 | if err != nil { 10 | return err 11 | } 12 | ``` 13 | which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. 14 | 15 | ## Adding context to an error 16 | 17 | The errors.Wrap function returns a new error that adds context to the original error. For example 18 | ```go 19 | _, err := ioutil.ReadAll(r) 20 | if err != nil { 21 | return errors.Wrap(err, "read failed") 22 | } 23 | ``` 24 | ## Retrieving the cause of an error 25 | 26 | Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. 27 | ```go 28 | type causer interface { 29 | Cause() error 30 | } 31 | ``` 32 | `errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: 33 | ```go 34 | switch err := errors.Cause(err).(type) { 35 | case *MyError: 36 | // handle specifically 37 | default: 38 | // unknown error 39 | } 40 | ``` 41 | 42 | [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). 43 | 44 | ## Contributing 45 | 46 | We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. 47 | 48 | Before proposing a change, please discuss your change by raising an issue. 49 | 50 | ## Licence 51 | 52 | BSD-2-Clause 53 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | kingpin "gopkg.in/alecthomas/kingpin.v2" 8 | ) 9 | 10 | const applicationName = "dcsg" 11 | const applicationVersion = "v0.4.0" 12 | 13 | var ( 14 | app = kingpin.New(applicationName, fmt.Sprintf("%s creates systemd services for Docker Compose projects (%s)", applicationName, applicationVersion)) 15 | appDryRun = app.Flag("dry-run", "Print details of what would be done but do not install anything").Short('n').Bool() 16 | 17 | // install 18 | installCommand = app.Command("install", "Register a systemd service for the given docker-compose file") 19 | installDockerComposeFile = installCommand.Arg("docker-compose-file", "A docker-compose file").Default("docker-compose.yml").String() 20 | installProjectName = installCommand.Arg("project-name", "The project name of the docker-compose project").String() 21 | installDontPull = installCommand.Flag("no-pull", "The project name of the docker-compose project").Bool() 22 | 23 | // uninstall 24 | uninstallCommand = app.Command("uninstall", "Uninstall the systemd service for the given docker-compose file") 25 | uninstallDockerComposeFile = uninstallCommand.Arg("docker-compose-file", "A docker-compose file").Default("docker-compose.yml").String() 26 | uinstallProjectName = uninstallCommand.Arg("project-name", "The project name of the docker-compose project").String() 27 | ) 28 | 29 | func init() { 30 | app.Version(applicationVersion) 31 | app.Author("Andreas Koch ") 32 | } 33 | 34 | func main() { 35 | handleCommandlineArgument(os.Args[1:]) 36 | } 37 | 38 | func handleCommandlineArgument(arguments []string) { 39 | switch kingpin.MustParse(app.Parse(arguments)) { 40 | 41 | case installCommand.FullCommand(): 42 | service, err := newDscg(*installDockerComposeFile, *installProjectName, *appDryRun, !(*installDontPull)) 43 | if err != nil { 44 | fmt.Fprintf(os.Stderr, "%s\n", err) 45 | os.Exit(1) 46 | } 47 | 48 | if err := service.Install(); err != nil { 49 | fmt.Fprintf(os.Stderr, "%s\n", err) 50 | os.Exit(1) 51 | } 52 | 53 | case uninstallCommand.FullCommand(): 54 | service, err := newDscg(*uninstallDockerComposeFile, *uinstallProjectName, *appDryRun, !(*installDontPull)) 55 | if err != nil { 56 | fmt.Fprintf(os.Stderr, "%s\n", err) 57 | os.Exit(1) 58 | } 59 | 60 | if err := service.Uninstall(); err != nil { 61 | fmt.Fprintf(os.Stderr, "%s\n", err) 62 | os.Exit(1) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/units/bytes.go: -------------------------------------------------------------------------------- 1 | package units 2 | 3 | // Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte, 4 | // etc.). 5 | type Base2Bytes int64 6 | 7 | // Base-2 byte units. 8 | const ( 9 | Kibibyte Base2Bytes = 1024 10 | KiB = Kibibyte 11 | Mebibyte = Kibibyte * 1024 12 | MiB = Mebibyte 13 | Gibibyte = Mebibyte * 1024 14 | GiB = Gibibyte 15 | Tebibyte = Gibibyte * 1024 16 | TiB = Tebibyte 17 | Pebibyte = Tebibyte * 1024 18 | PiB = Pebibyte 19 | Exbibyte = Pebibyte * 1024 20 | EiB = Exbibyte 21 | ) 22 | 23 | var ( 24 | bytesUnitMap = MakeUnitMap("iB", "B", 1024) 25 | oldBytesUnitMap = MakeUnitMap("B", "B", 1024) 26 | ) 27 | 28 | // ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB 29 | // and KiB are both 1024. 30 | func ParseBase2Bytes(s string) (Base2Bytes, error) { 31 | n, err := ParseUnit(s, bytesUnitMap) 32 | if err != nil { 33 | n, err = ParseUnit(s, oldBytesUnitMap) 34 | } 35 | return Base2Bytes(n), err 36 | } 37 | 38 | func (b Base2Bytes) String() string { 39 | return ToString(int64(b), 1024, "iB", "B") 40 | } 41 | 42 | var ( 43 | metricBytesUnitMap = MakeUnitMap("B", "B", 1000) 44 | ) 45 | 46 | // MetricBytes are SI byte units (1000 bytes in a kilobyte). 47 | type MetricBytes SI 48 | 49 | // SI base-10 byte units. 50 | const ( 51 | Kilobyte MetricBytes = 1000 52 | KB = Kilobyte 53 | Megabyte = Kilobyte * 1000 54 | MB = Megabyte 55 | Gigabyte = Megabyte * 1000 56 | GB = Gigabyte 57 | Terabyte = Gigabyte * 1000 58 | TB = Terabyte 59 | Petabyte = Terabyte * 1000 60 | PB = Petabyte 61 | Exabyte = Petabyte * 1000 62 | EB = Exabyte 63 | ) 64 | 65 | // ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes. 66 | func ParseMetricBytes(s string) (MetricBytes, error) { 67 | n, err := ParseUnit(s, metricBytesUnitMap) 68 | return MetricBytes(n), err 69 | } 70 | 71 | func (m MetricBytes) String() string { 72 | return ToString(int64(m), 1000, "B", "B") 73 | } 74 | 75 | // ParseStrictBytes supports both iB and B suffixes for base 2 and metric, 76 | // respectively. That is, KiB represents 1024 and KB represents 1000. 77 | func ParseStrictBytes(s string) (int64, error) { 78 | n, err := ParseUnit(s, bytesUnitMap) 79 | if err != nil { 80 | n, err = ParseUnit(s, metricBytesUnitMap) 81 | } 82 | return int64(n), err 83 | } 84 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/global.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | var ( 9 | // CommandLine is the default Kingpin parser. 10 | CommandLine = New(filepath.Base(os.Args[0]), "") 11 | // Global help flag. Exposed for user customisation. 12 | HelpFlag = CommandLine.HelpFlag 13 | // Top-level help command. Exposed for user customisation. May be nil. 14 | HelpCommand = CommandLine.HelpCommand 15 | // Global version flag. Exposed for user customisation. May be nil. 16 | VersionFlag = CommandLine.VersionFlag 17 | ) 18 | 19 | // Command adds a new command to the default parser. 20 | func Command(name, help string) *CmdClause { 21 | return CommandLine.Command(name, help) 22 | } 23 | 24 | // Flag adds a new flag to the default parser. 25 | func Flag(name, help string) *FlagClause { 26 | return CommandLine.Flag(name, help) 27 | } 28 | 29 | // Arg adds a new argument to the top-level of the default parser. 30 | func Arg(name, help string) *ArgClause { 31 | return CommandLine.Arg(name, help) 32 | } 33 | 34 | // Parse and return the selected command. Will call the termination handler if 35 | // an error is encountered. 36 | func Parse() string { 37 | selected := MustParse(CommandLine.Parse(os.Args[1:])) 38 | if selected == "" && CommandLine.cmdGroup.have() { 39 | Usage() 40 | CommandLine.terminate(0) 41 | } 42 | return selected 43 | } 44 | 45 | // Errorf prints an error message to stderr. 46 | func Errorf(format string, args ...interface{}) { 47 | CommandLine.Errorf(format, args...) 48 | } 49 | 50 | // Fatalf prints an error message to stderr and exits. 51 | func Fatalf(format string, args ...interface{}) { 52 | CommandLine.Fatalf(format, args...) 53 | } 54 | 55 | // FatalIfError prints an error and exits if err is not nil. The error is printed 56 | // with the given prefix. 57 | func FatalIfError(err error, format string, args ...interface{}) { 58 | CommandLine.FatalIfError(err, format, args...) 59 | } 60 | 61 | // FatalUsage prints an error message followed by usage information, then 62 | // exits with a non-zero status. 63 | func FatalUsage(format string, args ...interface{}) { 64 | CommandLine.FatalUsage(format, args...) 65 | } 66 | 67 | // FatalUsageContext writes a printf formatted error message to stderr, then 68 | // usage information for the given ParseContext, before exiting. 69 | func FatalUsageContext(context *ParseContext, format string, args ...interface{}) { 70 | CommandLine.FatalUsageContext(context, format, args...) 71 | } 72 | 73 | // Usage prints usage to stderr. 74 | func Usage() { 75 | CommandLine.Usage(os.Args[1:]) 76 | } 77 | 78 | // Set global usage template to use (defaults to DefaultUsageTemplate). 79 | func UsageTemplate(template string) *Application { 80 | return CommandLine.UsageTemplate(template) 81 | } 82 | 83 | // MustParse can be used with app.Parse(args) to exit with an error if parsing fails. 84 | func MustParse(command string, err error) string { 85 | if err != nil { 86 | Fatalf("%s, try --help", err) 87 | } 88 | return command 89 | } 90 | 91 | // Version adds a flag for displaying the application version number. 92 | func Version(version string) *Application { 93 | return CommandLine.Version(version) 94 | } 95 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dcsg: A systemd service generator for docker-compose 2 | 3 | dcsg is a command-line utility for Linux that generates systemd services for Docker Compose projects. 4 | 5 | If you have one or more docker compose projects running on your server you might want **create a systemd service** for each of them. 6 | 7 | And **dcsg** is here to help you with just that. Quickly create systemd services from docker-compose files. 8 | 9 | ![Animation: Using dcsg on a docker-compose project](files/animation-dcsg-usage-example.gif) 10 | 11 | ## Usage 12 | 13 | `dcsg [] [` 14 | 15 | ### Install 16 | 17 | Register a Docker Compose project as a systemd service: 18 | 19 | ```bash 20 | dcsg install docker-compose.yml 21 | ``` 22 | 23 | ### Uninstall 24 | 25 | Uninstall the systemd service for the given Docker Compose project: 26 | 27 | ```bash 28 | dcsg uninstall docker-compose.yml 29 | ``` 30 | 31 | ### Help 32 | 33 | Show the available actions and arguments: 34 | 35 | ```bash 36 | dcsg help 37 | ``` 38 | 39 | The help for a specific action: 40 | 41 | ```bash 42 | dcsg install --help 43 | ``` 44 | 45 | ## What does dcsg do? 46 | 47 | **dcsg** doesn't do much. 48 | 49 | For the `install` action **dcsg** creates a systemd service (see: [installer.go](installer.go)): 50 | 51 | 1. Create a systemd service definition in `/etc/systemd/system/.service` 52 | 2. Execute systemctl `daemon-reload`, `enable` and `start` 53 | 54 | The name of the service created will be the project name of your docker-compose project. 55 | 56 | For the `uninstall` action **dcsg** remove the systemd service it created earlier (see: [uninstaller.go](uninstaller.go)): 57 | 58 | 1. Execute a systemctl `stop` and `disable` for the docker-compose service 59 | 2. Delete the systemd service definition from `/etc/systemd/system/.service` 60 | 3. Execute sytemctl `daemon-reload` 61 | 62 | ## Download 63 | 64 | You can download pre-built binaries for Linux (64bit, ARM 5, ARM 6 and ARM 7) from [github.com » andreaskoch » dcsg » releases](/releases/latest): 65 | 66 | - [Download for dcsg (Linux 64bit)](https://github.com/andreaskoch/dcsg/releases/download/v0.4.0/dcsg_linux_amd64) 67 | - [Download for dcsg (Linux ARM5)](https://github.com/andreaskoch/dcsg/releases/download/v0.4.0/dcsg_linux_arm5) 68 | - [Download for dcsg (Linux ARM6)](https://github.com/andreaskoch/dcsg/releases/download/v0.4.0/dcsg_linux_arm6) 69 | - [Download for dcsg (Linux ARM7)](https://github.com/andreaskoch/dcsg/releases/download/v0.4.0/dcsg_linux_arm7) 70 | 71 | ```bash 72 | curl -L https://github.com/andreaskoch/dcsg/releases/download/v0.4.0/dcsg_linux_amd64 > /usr/local/bin/dcsg 73 | chmod +x /usr/local/bin/dcsg 74 | ``` 75 | 76 | 77 | ## Build 78 | 79 | If you have go installed you can use `go get` to fetch and build **dcsg**: 80 | 81 | ```bash 82 | go get github.com/andreaskoch/dcsg 83 | ``` 84 | 85 | To **cross-compile dcsg** for the different Linux architectures (AMD64, ARM5, ARM6 and ARM7) you can use the `crosscompile` action of the make script: 86 | 87 | ```bash 88 | go get github.com/andreaskoch/dcsg 89 | cd $GOPATH/github.com/andreaskoch/dcsg 90 | make crosscompile 91 | ``` 92 | 93 | ## Licensing 94 | 95 | »dcsg« is licensed under the **Apache License, Version 2.0**. See [LICENSE](LICENSE) for the full license text. 96 | -------------------------------------------------------------------------------- /dcsg.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | func newDscg(dockerComposeFile, projectName string, dryRun bool, doPull bool) (*dcsg, error) { 14 | cleanedFilePath, err := filepath.Abs(dockerComposeFile) 15 | if err != nil { 16 | return nil, err 17 | } 18 | 19 | if !fileExists(cleanedFilePath) { 20 | return nil, fmt.Errorf("The Docker Compose file %q does not exist", cleanedFilePath) 21 | } 22 | 23 | if projectName == "" { 24 | projectNameFromDirectory, projectNameError := getProjectName(dockerComposeFile) 25 | if projectNameError != nil { 26 | return nil, projectNameError 27 | } 28 | 29 | projectName = projectNameFromDirectory 30 | } 31 | 32 | projectDirectory, err := getProjectDirectory(dockerComposeFile) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | dockerComposeFileName := filepath.Base(dockerComposeFile) 38 | 39 | systemdDirectory := "/etc/systemd/system" 40 | commandExecutor := newExecutor(os.Stdin, os.Stdout, os.Stderr, "", dryRun) 41 | 42 | return &dcsg{ 43 | projectDirectory: projectDirectory, 44 | dockerComposeFileName: dockerComposeFileName, 45 | projectName: projectName, 46 | 47 | installer: &systemdInstaller{systemdDirectory, commandExecutor, dryRun, doPull}, 48 | uninstaller: &systemdUninstaller{systemdDirectory, commandExecutor, dryRun}, 49 | }, nil 50 | } 51 | 52 | type dcsg struct { 53 | projectDirectory string 54 | dockerComposeFileName string 55 | projectName string 56 | 57 | installer installer 58 | uninstaller uninstaller 59 | } 60 | 61 | func (service dcsg) Install() error { 62 | err := service.installer.Install(service.projectDirectory, service.dockerComposeFileName, service.projectName) 63 | if err != nil { 64 | return errors.Wrap(err, "Installation failed") 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (service dcsg) Uninstall() error { 71 | err := service.uninstaller.Uninstall(service.projectDirectory, service.dockerComposeFileName, service.projectName) 72 | if err != nil { 73 | return errors.Wrap(err, "Uninstall failed") 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func getProjectDirectory(dockerComposeFile string) (string, error) { 80 | cleanedFilePath, err := filepath.Abs(dockerComposeFile) 81 | if err != nil { 82 | return "", errors.Wrap(err, fmt.Sprintf("Failed to determine the project directory from %q", dockerComposeFile)) 83 | } 84 | 85 | return filepath.Dir(cleanedFilePath), nil 86 | } 87 | 88 | var invalidProjectNameCharacters = regexp.MustCompile("[^a-z0-9]") 89 | 90 | func fileExists(filePath string) bool { 91 | _, err := os.Stat(filePath) 92 | if err != nil { 93 | return false 94 | } 95 | 96 | return true 97 | } 98 | 99 | func getProjectName(dockerComposeFile string) (string, error) { 100 | directoryPath, err := getProjectDirectory(dockerComposeFile) 101 | if err != nil { 102 | return "", errors.Wrap(err, fmt.Sprintf("Failed to determine the project directory from %q", dockerComposeFile)) 103 | } 104 | 105 | directoryName := filepath.Base(directoryPath) 106 | 107 | projectName := strings.ToLower(directoryName) 108 | projectName = strings.TrimSpace(projectName) 109 | projectName = invalidProjectNameCharacters.ReplaceAllString(projectName, "") 110 | 111 | return projectName, nil 112 | } 113 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/units/util.go: -------------------------------------------------------------------------------- 1 | package units 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | siUnits = []string{"", "K", "M", "G", "T", "P", "E"} 11 | ) 12 | 13 | func ToString(n int64, scale int64, suffix, baseSuffix string) string { 14 | mn := len(siUnits) 15 | out := make([]string, mn) 16 | for i, m := range siUnits { 17 | if n%scale != 0 || i == 0 && n == 0 { 18 | s := suffix 19 | if i == 0 { 20 | s = baseSuffix 21 | } 22 | out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s) 23 | } 24 | n /= scale 25 | if n == 0 { 26 | break 27 | } 28 | } 29 | return strings.Join(out, "") 30 | } 31 | 32 | // Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123 33 | var errLeadingInt = errors.New("units: bad [0-9]*") // never printed 34 | 35 | // leadingInt consumes the leading [0-9]* from s. 36 | func leadingInt(s string) (x int64, rem string, err error) { 37 | i := 0 38 | for ; i < len(s); i++ { 39 | c := s[i] 40 | if c < '0' || c > '9' { 41 | break 42 | } 43 | if x >= (1<<63-10)/10 { 44 | // overflow 45 | return 0, "", errLeadingInt 46 | } 47 | x = x*10 + int64(c) - '0' 48 | } 49 | return x, s[i:], nil 50 | } 51 | 52 | func ParseUnit(s string, unitMap map[string]float64) (int64, error) { 53 | // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ 54 | orig := s 55 | f := float64(0) 56 | neg := false 57 | 58 | // Consume [-+]? 59 | if s != "" { 60 | c := s[0] 61 | if c == '-' || c == '+' { 62 | neg = c == '-' 63 | s = s[1:] 64 | } 65 | } 66 | // Special case: if all that is left is "0", this is zero. 67 | if s == "0" { 68 | return 0, nil 69 | } 70 | if s == "" { 71 | return 0, errors.New("units: invalid " + orig) 72 | } 73 | for s != "" { 74 | g := float64(0) // this element of the sequence 75 | 76 | var x int64 77 | var err error 78 | 79 | // The next character must be [0-9.] 80 | if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) { 81 | return 0, errors.New("units: invalid " + orig) 82 | } 83 | // Consume [0-9]* 84 | pl := len(s) 85 | x, s, err = leadingInt(s) 86 | if err != nil { 87 | return 0, errors.New("units: invalid " + orig) 88 | } 89 | g = float64(x) 90 | pre := pl != len(s) // whether we consumed anything before a period 91 | 92 | // Consume (\.[0-9]*)? 93 | post := false 94 | if s != "" && s[0] == '.' { 95 | s = s[1:] 96 | pl := len(s) 97 | x, s, err = leadingInt(s) 98 | if err != nil { 99 | return 0, errors.New("units: invalid " + orig) 100 | } 101 | scale := 1.0 102 | for n := pl - len(s); n > 0; n-- { 103 | scale *= 10 104 | } 105 | g += float64(x) / scale 106 | post = pl != len(s) 107 | } 108 | if !pre && !post { 109 | // no digits (e.g. ".s" or "-.s") 110 | return 0, errors.New("units: invalid " + orig) 111 | } 112 | 113 | // Consume unit. 114 | i := 0 115 | for ; i < len(s); i++ { 116 | c := s[i] 117 | if c == '.' || ('0' <= c && c <= '9') { 118 | break 119 | } 120 | } 121 | u := s[:i] 122 | s = s[i:] 123 | unit, ok := unitMap[u] 124 | if !ok { 125 | return 0, errors.New("units: unknown unit " + u + " in " + orig) 126 | } 127 | 128 | f += g * unit 129 | } 130 | 131 | if neg { 132 | f = -f 133 | } 134 | if f < float64(-1<<63) || f > float64(1<<63-1) { 135 | return 0, errors.New("units: overflow parsing unit") 136 | } 137 | return int64(f), nil 138 | } 139 | -------------------------------------------------------------------------------- /installer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | type installer interface { 14 | Install(projectDirectory, dockerComposeFileName, projectName string) error 15 | } 16 | 17 | type systemdInstaller struct { 18 | systemdDirectory string 19 | commandExecutor Executor 20 | dryRun bool 21 | doPull bool 22 | } 23 | 24 | func (installer *systemdInstaller) Install(projectDirectory, dockerComposeFileName, projectName string) error { 25 | 26 | serviceName := getServiceName(projectName) 27 | serviceViewModel := serviceDefinition{ 28 | ProjectName: projectName, 29 | ProjectDirectory: projectDirectory, 30 | DockerComposeFile: dockerComposeFileName, 31 | DoPull: installer.doPull, 32 | } 33 | 34 | if err := installer.createSystemdService(serviceViewModel); err != nil { 35 | return errors.Wrap(err, fmt.Sprintf("Failed to create a systemd service for project %q (Directory: %q, Docker Compose File: %q)", projectName, projectDirectory, dockerComposeFileName)) 36 | } 37 | 38 | reloadError := installer.commandExecutor.Run("systemctl", "daemon-reload") 39 | if reloadError != nil { 40 | return errors.Wrap(reloadError, "Failed to reload the systemd configuration") 41 | } 42 | 43 | enableError := installer.commandExecutor.Run("systemctl", "enable", serviceName) 44 | if enableError != nil { 45 | return errors.Wrap(enableError, fmt.Sprintf("Failed to enable %q", serviceName)) 46 | } 47 | 48 | startError := installer.commandExecutor.Run("systemctl", "start", serviceName) 49 | if startError != nil { 50 | return errors.Wrap(startError, fmt.Sprintf("Failed to start %q", serviceName)) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func (installer *systemdInstaller) createSystemdService(service serviceDefinition) error { 57 | serviceFilePath := filepath.Join(installer.systemdDirectory, getServiceName(service.ProjectName)) 58 | 59 | var file *os.File 60 | if installer.dryRun { 61 | log.Println("Installing this service file at:", serviceFilePath) 62 | file = os.Stdout 63 | } else { 64 | var err error 65 | 66 | file, err = os.OpenFile(serviceFilePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0664) 67 | if err != nil { 68 | return errors.Wrap(err, fmt.Sprintf("Failed to open the systemd service file: %q", serviceFilePath)) 69 | } 70 | defer file.Close() 71 | } 72 | 73 | serviceTemplate, err := template.New("systemdservice").Parse(serviceTemplate) 74 | if err != nil { 75 | return errors.Wrap(err, "Failed to parse systemd service template") 76 | } 77 | 78 | serviceTemplate.Execute(file, service) 79 | 80 | return nil 81 | } 82 | 83 | func getServiceName(projectName string) string { 84 | return fmt.Sprintf("%s.service", projectName) 85 | } 86 | 87 | const serviceTemplate = `[Unit] 88 | Description={{ .ProjectName }} Service 89 | After=network.service docker.service 90 | Requires=docker.service 91 | 92 | [Service] 93 | Restart=always 94 | RestartSec=10 95 | TimeoutSec=300 96 | WorkingDirectory={{ .ProjectDirectory }} 97 | {{- if .DoPull }} 98 | ExecStartPre=/usr/bin/env docker-compose -p "{{ .ProjectName }}" -f "{{ .DockerComposeFile }}" pull 99 | {{- end }} 100 | ExecStart=/usr/bin/env docker-compose -p "{{ .ProjectName }}" -f "{{ .DockerComposeFile }}" up 101 | ExecStop=/usr/bin/env docker-compose -p "{{ .ProjectName }}" -f "{{ .DockerComposeFile }}" stop 102 | ExecStopPost=/usr/bin/env docker-compose -p "{{ .ProjectName }}" -f "{{ .DockerComposeFile }}" down 103 | 104 | [Install] 105 | WantedBy=docker.service 106 | ` 107 | 108 | type serviceDefinition struct { 109 | ProjectName string 110 | ProjectDirectory string 111 | DockerComposeFile string 112 | DoPull bool 113 | } 114 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/template/helper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Helper functions to make constructing templates easier. 6 | 7 | package template 8 | 9 | import ( 10 | "fmt" 11 | "io/ioutil" 12 | "path/filepath" 13 | ) 14 | 15 | // Functions and methods to parse templates. 16 | 17 | // Must is a helper that wraps a call to a function returning (*Template, error) 18 | // and panics if the error is non-nil. It is intended for use in variable 19 | // initializations such as 20 | // var t = template.Must(template.New("name").Parse("text")) 21 | func Must(t *Template, err error) *Template { 22 | if err != nil { 23 | panic(err) 24 | } 25 | return t 26 | } 27 | 28 | // ParseFiles creates a new Template and parses the template definitions from 29 | // the named files. The returned template's name will have the (base) name and 30 | // (parsed) contents of the first file. There must be at least one file. 31 | // If an error occurs, parsing stops and the returned *Template is nil. 32 | func ParseFiles(filenames ...string) (*Template, error) { 33 | return parseFiles(nil, filenames...) 34 | } 35 | 36 | // ParseFiles parses the named files and associates the resulting templates with 37 | // t. If an error occurs, parsing stops and the returned template is nil; 38 | // otherwise it is t. There must be at least one file. 39 | func (t *Template) ParseFiles(filenames ...string) (*Template, error) { 40 | return parseFiles(t, filenames...) 41 | } 42 | 43 | // parseFiles is the helper for the method and function. If the argument 44 | // template is nil, it is created from the first file. 45 | func parseFiles(t *Template, filenames ...string) (*Template, error) { 46 | if len(filenames) == 0 { 47 | // Not really a problem, but be consistent. 48 | return nil, fmt.Errorf("template: no files named in call to ParseFiles") 49 | } 50 | for _, filename := range filenames { 51 | b, err := ioutil.ReadFile(filename) 52 | if err != nil { 53 | return nil, err 54 | } 55 | s := string(b) 56 | name := filepath.Base(filename) 57 | // First template becomes return value if not already defined, 58 | // and we use that one for subsequent New calls to associate 59 | // all the templates together. Also, if this file has the same name 60 | // as t, this file becomes the contents of t, so 61 | // t, err := New(name).Funcs(xxx).ParseFiles(name) 62 | // works. Otherwise we create a new template associated with t. 63 | var tmpl *Template 64 | if t == nil { 65 | t = New(name) 66 | } 67 | if name == t.Name() { 68 | tmpl = t 69 | } else { 70 | tmpl = t.New(name) 71 | } 72 | _, err = tmpl.Parse(s) 73 | if err != nil { 74 | return nil, err 75 | } 76 | } 77 | return t, nil 78 | } 79 | 80 | // ParseGlob creates a new Template and parses the template definitions from the 81 | // files identified by the pattern, which must match at least one file. The 82 | // returned template will have the (base) name and (parsed) contents of the 83 | // first file matched by the pattern. ParseGlob is equivalent to calling 84 | // ParseFiles with the list of files matched by the pattern. 85 | func ParseGlob(pattern string) (*Template, error) { 86 | return parseGlob(nil, pattern) 87 | } 88 | 89 | // ParseGlob parses the template definitions in the files identified by the 90 | // pattern and associates the resulting templates with t. The pattern is 91 | // processed by filepath.Glob and must match at least one file. ParseGlob is 92 | // equivalent to calling t.ParseFiles with the list of files matched by the 93 | // pattern. 94 | func (t *Template) ParseGlob(pattern string) (*Template, error) { 95 | return parseGlob(t, pattern) 96 | } 97 | 98 | // parseGlob is the implementation of the function and method ParseGlob. 99 | func parseGlob(t *Template, pattern string) (*Template, error) { 100 | filenames, err := filepath.Glob(pattern) 101 | if err != nil { 102 | return nil, err 103 | } 104 | if len(filenames) == 0 { 105 | return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern) 106 | } 107 | return parseFiles(t, filenames...) 108 | } 109 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/args.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type argGroup struct { 8 | args []*ArgClause 9 | } 10 | 11 | func newArgGroup() *argGroup { 12 | return &argGroup{} 13 | } 14 | 15 | func (a *argGroup) have() bool { 16 | return len(a.args) > 0 17 | } 18 | 19 | // GetArg gets an argument definition. 20 | // 21 | // This allows existing arguments to be modified after definition but before parsing. Useful for 22 | // modular applications. 23 | func (a *argGroup) GetArg(name string) *ArgClause { 24 | for _, arg := range a.args { 25 | if arg.name == name { 26 | return arg 27 | } 28 | } 29 | return nil 30 | } 31 | 32 | func (a *argGroup) Arg(name, help string) *ArgClause { 33 | arg := newArg(name, help) 34 | a.args = append(a.args, arg) 35 | return arg 36 | } 37 | 38 | func (a *argGroup) init() error { 39 | required := 0 40 | seen := map[string]struct{}{} 41 | previousArgMustBeLast := false 42 | for i, arg := range a.args { 43 | if previousArgMustBeLast { 44 | return fmt.Errorf("Args() can't be followed by another argument '%s'", arg.name) 45 | } 46 | if arg.consumesRemainder() { 47 | previousArgMustBeLast = true 48 | } 49 | if _, ok := seen[arg.name]; ok { 50 | return fmt.Errorf("duplicate argument '%s'", arg.name) 51 | } 52 | seen[arg.name] = struct{}{} 53 | if arg.required && required != i { 54 | return fmt.Errorf("required arguments found after non-required") 55 | } 56 | if arg.required { 57 | required++ 58 | } 59 | if err := arg.init(); err != nil { 60 | return err 61 | } 62 | } 63 | return nil 64 | } 65 | 66 | type ArgClause struct { 67 | actionMixin 68 | parserMixin 69 | completionsMixin 70 | envarMixin 71 | name string 72 | help string 73 | defaultValues []string 74 | required bool 75 | } 76 | 77 | func newArg(name, help string) *ArgClause { 78 | a := &ArgClause{ 79 | name: name, 80 | help: help, 81 | } 82 | return a 83 | } 84 | 85 | func (a *ArgClause) setDefault() error { 86 | if a.HasEnvarValue() { 87 | if v, ok := a.value.(remainderArg); !ok || !v.IsCumulative() { 88 | // Use the value as-is 89 | return a.value.Set(a.GetEnvarValue()) 90 | } else { 91 | for _, value := range a.GetSplitEnvarValue() { 92 | if err := a.value.Set(value); err != nil { 93 | return err 94 | } 95 | } 96 | return nil 97 | } 98 | } 99 | 100 | if len(a.defaultValues) > 0 { 101 | for _, defaultValue := range a.defaultValues { 102 | if err := a.value.Set(defaultValue); err != nil { 103 | return err 104 | } 105 | } 106 | return nil 107 | } 108 | 109 | return nil 110 | } 111 | 112 | func (a *ArgClause) needsValue() bool { 113 | haveDefault := len(a.defaultValues) > 0 114 | return a.required && !(haveDefault || a.HasEnvarValue()) 115 | } 116 | 117 | func (a *ArgClause) consumesRemainder() bool { 118 | if r, ok := a.value.(remainderArg); ok { 119 | return r.IsCumulative() 120 | } 121 | return false 122 | } 123 | 124 | // Required arguments must be input by the user. They can not have a Default() value provided. 125 | func (a *ArgClause) Required() *ArgClause { 126 | a.required = true 127 | return a 128 | } 129 | 130 | // Default values for this argument. They *must* be parseable by the value of the argument. 131 | func (a *ArgClause) Default(values ...string) *ArgClause { 132 | a.defaultValues = values 133 | return a 134 | } 135 | 136 | // Envar overrides the default value(s) for a flag from an environment variable, 137 | // if it is set. Several default values can be provided by using new lines to 138 | // separate them. 139 | func (a *ArgClause) Envar(name string) *ArgClause { 140 | a.envar = name 141 | a.noEnvar = false 142 | return a 143 | } 144 | 145 | // NoEnvar forces environment variable defaults to be disabled for this flag. 146 | // Most useful in conjunction with app.DefaultEnvars(). 147 | func (a *ArgClause) NoEnvar() *ArgClause { 148 | a.envar = "" 149 | a.noEnvar = true 150 | return a 151 | } 152 | 153 | func (a *ArgClause) Action(action Action) *ArgClause { 154 | a.addAction(action) 155 | return a 156 | } 157 | 158 | func (a *ArgClause) PreAction(action Action) *ArgClause { 159 | a.addPreAction(action) 160 | return a 161 | } 162 | 163 | // HintAction registers a HintAction (function) for the arg to provide completions 164 | func (a *ArgClause) HintAction(action HintAction) *ArgClause { 165 | a.addHintAction(action) 166 | return a 167 | } 168 | 169 | // HintOptions registers any number of options for the flag to provide completions 170 | func (a *ArgClause) HintOptions(options ...string) *ArgClause { 171 | a.addHintAction(func() []string { 172 | return options 173 | }) 174 | return a 175 | } 176 | 177 | func (a *ArgClause) init() error { 178 | if a.required && len(a.defaultValues) > 0 { 179 | return fmt.Errorf("required argument '%s' with unusable default value", a.name) 180 | } 181 | if a.value == nil { 182 | return fmt.Errorf("no parser defined for arg '%s'", a.name) 183 | } 184 | return nil 185 | } 186 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/stack.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "path" 7 | "runtime" 8 | "strings" 9 | ) 10 | 11 | // Frame represents a program counter inside a stack frame. 12 | type Frame uintptr 13 | 14 | // pc returns the program counter for this frame; 15 | // multiple frames may have the same PC value. 16 | func (f Frame) pc() uintptr { return uintptr(f) - 1 } 17 | 18 | // file returns the full path to the file that contains the 19 | // function for this Frame's pc. 20 | func (f Frame) file() string { 21 | fn := runtime.FuncForPC(f.pc()) 22 | if fn == nil { 23 | return "unknown" 24 | } 25 | file, _ := fn.FileLine(f.pc()) 26 | return file 27 | } 28 | 29 | // line returns the line number of source code of the 30 | // function for this Frame's pc. 31 | func (f Frame) line() int { 32 | fn := runtime.FuncForPC(f.pc()) 33 | if fn == nil { 34 | return 0 35 | } 36 | _, line := fn.FileLine(f.pc()) 37 | return line 38 | } 39 | 40 | // Format formats the frame according to the fmt.Formatter interface. 41 | // 42 | // %s source file 43 | // %d source line 44 | // %n function name 45 | // %v equivalent to %s:%d 46 | // 47 | // Format accepts flags that alter the printing of some verbs, as follows: 48 | // 49 | // %+s path of source file relative to the compile time GOPATH 50 | // %+v equivalent to %+s:%d 51 | func (f Frame) Format(s fmt.State, verb rune) { 52 | switch verb { 53 | case 's': 54 | switch { 55 | case s.Flag('+'): 56 | pc := f.pc() 57 | fn := runtime.FuncForPC(pc) 58 | if fn == nil { 59 | io.WriteString(s, "unknown") 60 | } else { 61 | file, _ := fn.FileLine(pc) 62 | fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) 63 | } 64 | default: 65 | io.WriteString(s, path.Base(f.file())) 66 | } 67 | case 'd': 68 | fmt.Fprintf(s, "%d", f.line()) 69 | case 'n': 70 | name := runtime.FuncForPC(f.pc()).Name() 71 | io.WriteString(s, funcname(name)) 72 | case 'v': 73 | f.Format(s, 's') 74 | io.WriteString(s, ":") 75 | f.Format(s, 'd') 76 | } 77 | } 78 | 79 | // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). 80 | type StackTrace []Frame 81 | 82 | func (st StackTrace) Format(s fmt.State, verb rune) { 83 | switch verb { 84 | case 'v': 85 | switch { 86 | case s.Flag('+'): 87 | for _, f := range st { 88 | fmt.Fprintf(s, "\n%+v", f) 89 | } 90 | case s.Flag('#'): 91 | fmt.Fprintf(s, "%#v", []Frame(st)) 92 | default: 93 | fmt.Fprintf(s, "%v", []Frame(st)) 94 | } 95 | case 's': 96 | fmt.Fprintf(s, "%s", []Frame(st)) 97 | } 98 | } 99 | 100 | // stack represents a stack of program counters. 101 | type stack []uintptr 102 | 103 | func (s *stack) Format(st fmt.State, verb rune) { 104 | switch verb { 105 | case 'v': 106 | switch { 107 | case st.Flag('+'): 108 | for _, pc := range *s { 109 | f := Frame(pc) 110 | fmt.Fprintf(st, "\n%+v", f) 111 | } 112 | } 113 | } 114 | } 115 | 116 | func (s *stack) StackTrace() StackTrace { 117 | f := make([]Frame, len(*s)) 118 | for i := 0; i < len(f); i++ { 119 | f[i] = Frame((*s)[i]) 120 | } 121 | return f 122 | } 123 | 124 | func callers() *stack { 125 | const depth = 32 126 | var pcs [depth]uintptr 127 | n := runtime.Callers(3, pcs[:]) 128 | var st stack = pcs[0:n] 129 | return &st 130 | } 131 | 132 | // funcname removes the path prefix component of a function's name reported by func.Name(). 133 | func funcname(name string) string { 134 | i := strings.LastIndex(name, "/") 135 | name = name[i+1:] 136 | i = strings.Index(name, ".") 137 | return name[i+1:] 138 | } 139 | 140 | func trimGOPATH(name, file string) string { 141 | // Here we want to get the source file path relative to the compile time 142 | // GOPATH. As of Go 1.6.x there is no direct way to know the compiled 143 | // GOPATH at runtime, but we can infer the number of path segments in the 144 | // GOPATH. We note that fn.Name() returns the function name qualified by 145 | // the import path, which does not include the GOPATH. Thus we can trim 146 | // segments from the beginning of the file path until the number of path 147 | // separators remaining is one more than the number of path separators in 148 | // the function name. For example, given: 149 | // 150 | // GOPATH /home/user 151 | // file /home/user/src/pkg/sub/file.go 152 | // fn.Name() pkg/sub.Type.Method 153 | // 154 | // We want to produce: 155 | // 156 | // pkg/sub/file.go 157 | // 158 | // From this we can easily see that fn.Name() has one less path separator 159 | // than our desired output. We count separators from the end of the file 160 | // path until it finds two more than in the function name and then move 161 | // one character forward to preserve the initial path segment without a 162 | // leading separator. 163 | const sep = "/" 164 | goal := strings.Count(name, sep) + 2 165 | i := len(file) 166 | for n := 0; n < goal; n++ { 167 | i = strings.LastIndex(file[:i], sep) 168 | if i == -1 { 169 | // not enough separators found, set i so that the slice expression 170 | // below leaves file unmodified 171 | i = -len(sep) 172 | break 173 | } 174 | } 175 | // get back to 0 or trim the leading separator 176 | file = file[i+len(sep):] 177 | return file 178 | } 179 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/model.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Data model for Kingpin command-line structure. 10 | 11 | type FlagGroupModel struct { 12 | Flags []*FlagModel 13 | } 14 | 15 | func (f *FlagGroupModel) FlagSummary() string { 16 | out := []string{} 17 | count := 0 18 | for _, flag := range f.Flags { 19 | if flag.Name != "help" { 20 | count++ 21 | } 22 | if flag.Required { 23 | if flag.IsBoolFlag() { 24 | out = append(out, fmt.Sprintf("--[no-]%s", flag.Name)) 25 | } else { 26 | out = append(out, fmt.Sprintf("--%s=%s", flag.Name, flag.FormatPlaceHolder())) 27 | } 28 | } 29 | } 30 | if count != len(out) { 31 | out = append(out, "[]") 32 | } 33 | return strings.Join(out, " ") 34 | } 35 | 36 | type FlagModel struct { 37 | Name string 38 | Help string 39 | Short rune 40 | Default []string 41 | Envar string 42 | PlaceHolder string 43 | Required bool 44 | Hidden bool 45 | Value Value 46 | } 47 | 48 | func (f *FlagModel) String() string { 49 | return f.Value.String() 50 | } 51 | 52 | func (f *FlagModel) IsBoolFlag() bool { 53 | if fl, ok := f.Value.(boolFlag); ok { 54 | return fl.IsBoolFlag() 55 | } 56 | return false 57 | } 58 | 59 | func (f *FlagModel) FormatPlaceHolder() string { 60 | if f.PlaceHolder != "" { 61 | return f.PlaceHolder 62 | } 63 | if len(f.Default) > 0 { 64 | ellipsis := "" 65 | if len(f.Default) > 1 { 66 | ellipsis = "..." 67 | } 68 | if _, ok := f.Value.(*stringValue); ok { 69 | return strconv.Quote(f.Default[0]) + ellipsis 70 | } 71 | return f.Default[0] + ellipsis 72 | } 73 | return strings.ToUpper(f.Name) 74 | } 75 | 76 | type ArgGroupModel struct { 77 | Args []*ArgModel 78 | } 79 | 80 | func (a *ArgGroupModel) ArgSummary() string { 81 | depth := 0 82 | out := []string{} 83 | for _, arg := range a.Args { 84 | h := "<" + arg.Name + ">" 85 | if !arg.Required { 86 | h = "[" + h 87 | depth++ 88 | } 89 | out = append(out, h) 90 | } 91 | out[len(out)-1] = out[len(out)-1] + strings.Repeat("]", depth) 92 | return strings.Join(out, " ") 93 | } 94 | 95 | type ArgModel struct { 96 | Name string 97 | Help string 98 | Default []string 99 | Envar string 100 | Required bool 101 | Value Value 102 | } 103 | 104 | func (a *ArgModel) String() string { 105 | return a.Value.String() 106 | } 107 | 108 | type CmdGroupModel struct { 109 | Commands []*CmdModel 110 | } 111 | 112 | func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) { 113 | for _, cmd := range c.Commands { 114 | if len(cmd.Commands) == 0 { 115 | out = append(out, cmd) 116 | } 117 | out = append(out, cmd.FlattenedCommands()...) 118 | } 119 | return 120 | } 121 | 122 | type CmdModel struct { 123 | Name string 124 | Aliases []string 125 | Help string 126 | FullCommand string 127 | Depth int 128 | Hidden bool 129 | Default bool 130 | *FlagGroupModel 131 | *ArgGroupModel 132 | *CmdGroupModel 133 | } 134 | 135 | func (c *CmdModel) String() string { 136 | return c.FullCommand 137 | } 138 | 139 | type ApplicationModel struct { 140 | Name string 141 | Help string 142 | Version string 143 | Author string 144 | *ArgGroupModel 145 | *CmdGroupModel 146 | *FlagGroupModel 147 | } 148 | 149 | func (a *Application) Model() *ApplicationModel { 150 | return &ApplicationModel{ 151 | Name: a.Name, 152 | Help: a.Help, 153 | Version: a.version, 154 | Author: a.author, 155 | FlagGroupModel: a.flagGroup.Model(), 156 | ArgGroupModel: a.argGroup.Model(), 157 | CmdGroupModel: a.cmdGroup.Model(), 158 | } 159 | } 160 | 161 | func (a *argGroup) Model() *ArgGroupModel { 162 | m := &ArgGroupModel{} 163 | for _, arg := range a.args { 164 | m.Args = append(m.Args, arg.Model()) 165 | } 166 | return m 167 | } 168 | 169 | func (a *ArgClause) Model() *ArgModel { 170 | return &ArgModel{ 171 | Name: a.name, 172 | Help: a.help, 173 | Default: a.defaultValues, 174 | Envar: a.envar, 175 | Required: a.required, 176 | Value: a.value, 177 | } 178 | } 179 | 180 | func (f *flagGroup) Model() *FlagGroupModel { 181 | m := &FlagGroupModel{} 182 | for _, fl := range f.flagOrder { 183 | m.Flags = append(m.Flags, fl.Model()) 184 | } 185 | return m 186 | } 187 | 188 | func (f *FlagClause) Model() *FlagModel { 189 | return &FlagModel{ 190 | Name: f.name, 191 | Help: f.help, 192 | Short: rune(f.shorthand), 193 | Default: f.defaultValues, 194 | Envar: f.envar, 195 | PlaceHolder: f.placeholder, 196 | Required: f.required, 197 | Hidden: f.hidden, 198 | Value: f.value, 199 | } 200 | } 201 | 202 | func (c *cmdGroup) Model() *CmdGroupModel { 203 | m := &CmdGroupModel{} 204 | for _, cm := range c.commandOrder { 205 | m.Commands = append(m.Commands, cm.Model()) 206 | } 207 | return m 208 | } 209 | 210 | func (c *CmdClause) Model() *CmdModel { 211 | depth := 0 212 | for i := c; i != nil; i = i.parent { 213 | depth++ 214 | } 215 | return &CmdModel{ 216 | Name: c.name, 217 | Aliases: c.aliases, 218 | Help: c.help, 219 | Depth: depth, 220 | Hidden: c.hidden, 221 | Default: c.isDefault, 222 | FullCommand: c.FullCommand(), 223 | FlagGroupModel: c.flagGroup.Model(), 224 | ArgGroupModel: c.argGroup.Model(), 225 | CmdGroupModel: c.cmdGroup.Model(), 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/parsers.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "net" 5 | "net/url" 6 | "os" 7 | "time" 8 | 9 | "github.com/alecthomas/units" 10 | ) 11 | 12 | type Settings interface { 13 | SetValue(value Value) 14 | } 15 | 16 | type parserMixin struct { 17 | value Value 18 | required bool 19 | } 20 | 21 | func (p *parserMixin) SetValue(value Value) { 22 | p.value = value 23 | } 24 | 25 | // StringMap provides key=value parsing into a map. 26 | func (p *parserMixin) StringMap() (target *map[string]string) { 27 | target = &(map[string]string{}) 28 | p.StringMapVar(target) 29 | return 30 | } 31 | 32 | // Duration sets the parser to a time.Duration parser. 33 | func (p *parserMixin) Duration() (target *time.Duration) { 34 | target = new(time.Duration) 35 | p.DurationVar(target) 36 | return 37 | } 38 | 39 | // Bytes parses numeric byte units. eg. 1.5KB 40 | func (p *parserMixin) Bytes() (target *units.Base2Bytes) { 41 | target = new(units.Base2Bytes) 42 | p.BytesVar(target) 43 | return 44 | } 45 | 46 | // IP sets the parser to a net.IP parser. 47 | func (p *parserMixin) IP() (target *net.IP) { 48 | target = new(net.IP) 49 | p.IPVar(target) 50 | return 51 | } 52 | 53 | // TCP (host:port) address. 54 | func (p *parserMixin) TCP() (target **net.TCPAddr) { 55 | target = new(*net.TCPAddr) 56 | p.TCPVar(target) 57 | return 58 | } 59 | 60 | // TCPVar (host:port) address. 61 | func (p *parserMixin) TCPVar(target **net.TCPAddr) { 62 | p.SetValue(newTCPAddrValue(target)) 63 | } 64 | 65 | // ExistingFile sets the parser to one that requires and returns an existing file. 66 | func (p *parserMixin) ExistingFile() (target *string) { 67 | target = new(string) 68 | p.ExistingFileVar(target) 69 | return 70 | } 71 | 72 | // ExistingDir sets the parser to one that requires and returns an existing directory. 73 | func (p *parserMixin) ExistingDir() (target *string) { 74 | target = new(string) 75 | p.ExistingDirVar(target) 76 | return 77 | } 78 | 79 | // ExistingFileOrDir sets the parser to one that requires and returns an existing file OR directory. 80 | func (p *parserMixin) ExistingFileOrDir() (target *string) { 81 | target = new(string) 82 | p.ExistingFileOrDirVar(target) 83 | return 84 | } 85 | 86 | // File returns an os.File against an existing file. 87 | func (p *parserMixin) File() (target **os.File) { 88 | target = new(*os.File) 89 | p.FileVar(target) 90 | return 91 | } 92 | 93 | // File attempts to open a File with os.OpenFile(flag, perm). 94 | func (p *parserMixin) OpenFile(flag int, perm os.FileMode) (target **os.File) { 95 | target = new(*os.File) 96 | p.OpenFileVar(target, flag, perm) 97 | return 98 | } 99 | 100 | // URL provides a valid, parsed url.URL. 101 | func (p *parserMixin) URL() (target **url.URL) { 102 | target = new(*url.URL) 103 | p.URLVar(target) 104 | return 105 | } 106 | 107 | // StringMap provides key=value parsing into a map. 108 | func (p *parserMixin) StringMapVar(target *map[string]string) { 109 | p.SetValue(newStringMapValue(target)) 110 | } 111 | 112 | // Float sets the parser to a float64 parser. 113 | func (p *parserMixin) Float() (target *float64) { 114 | return p.Float64() 115 | } 116 | 117 | // Float sets the parser to a float64 parser. 118 | func (p *parserMixin) FloatVar(target *float64) { 119 | p.Float64Var(target) 120 | } 121 | 122 | // Duration sets the parser to a time.Duration parser. 123 | func (p *parserMixin) DurationVar(target *time.Duration) { 124 | p.SetValue(newDurationValue(target)) 125 | } 126 | 127 | // BytesVar parses numeric byte units. eg. 1.5KB 128 | func (p *parserMixin) BytesVar(target *units.Base2Bytes) { 129 | p.SetValue(newBytesValue(target)) 130 | } 131 | 132 | // IP sets the parser to a net.IP parser. 133 | func (p *parserMixin) IPVar(target *net.IP) { 134 | p.SetValue(newIPValue(target)) 135 | } 136 | 137 | // ExistingFile sets the parser to one that requires and returns an existing file. 138 | func (p *parserMixin) ExistingFileVar(target *string) { 139 | p.SetValue(newExistingFileValue(target)) 140 | } 141 | 142 | // ExistingDir sets the parser to one that requires and returns an existing directory. 143 | func (p *parserMixin) ExistingDirVar(target *string) { 144 | p.SetValue(newExistingDirValue(target)) 145 | } 146 | 147 | // ExistingDir sets the parser to one that requires and returns an existing directory. 148 | func (p *parserMixin) ExistingFileOrDirVar(target *string) { 149 | p.SetValue(newExistingFileOrDirValue(target)) 150 | } 151 | 152 | // FileVar opens an existing file. 153 | func (p *parserMixin) FileVar(target **os.File) { 154 | p.SetValue(newFileValue(target, os.O_RDONLY, 0)) 155 | } 156 | 157 | // OpenFileVar calls os.OpenFile(flag, perm) 158 | func (p *parserMixin) OpenFileVar(target **os.File, flag int, perm os.FileMode) { 159 | p.SetValue(newFileValue(target, flag, perm)) 160 | } 161 | 162 | // URL provides a valid, parsed url.URL. 163 | func (p *parserMixin) URLVar(target **url.URL) { 164 | p.SetValue(newURLValue(target)) 165 | } 166 | 167 | // URLList provides a parsed list of url.URL values. 168 | func (p *parserMixin) URLList() (target *[]*url.URL) { 169 | target = new([]*url.URL) 170 | p.URLListVar(target) 171 | return 172 | } 173 | 174 | // URLListVar provides a parsed list of url.URL values. 175 | func (p *parserMixin) URLListVar(target *[]*url.URL) { 176 | p.SetValue(newURLListValue(target)) 177 | } 178 | 179 | // Enum allows a value from a set of options. 180 | func (p *parserMixin) Enum(options ...string) (target *string) { 181 | target = new(string) 182 | p.EnumVar(target, options...) 183 | return 184 | } 185 | 186 | // EnumVar allows a value from a set of options. 187 | func (p *parserMixin) EnumVar(target *string, options ...string) { 188 | p.SetValue(newEnumFlag(target, options...)) 189 | } 190 | 191 | // Enums allows a set of values from a set of options. 192 | func (p *parserMixin) Enums(options ...string) (target *[]string) { 193 | target = new([]string) 194 | p.EnumsVar(target, options...) 195 | return 196 | } 197 | 198 | // EnumVar allows a value from a set of options. 199 | func (p *parserMixin) EnumsVar(target *[]string, options ...string) { 200 | p.SetValue(newEnumsFlag(target, options...)) 201 | } 202 | 203 | // A Counter increments a number each time it is encountered. 204 | func (p *parserMixin) Counter() (target *int) { 205 | target = new(int) 206 | p.CounterVar(target) 207 | return 208 | } 209 | 210 | func (p *parserMixin) CounterVar(target *int) { 211 | p.SetValue(newCounterValue(target)) 212 | } 213 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/usage.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/doc" 7 | "io" 8 | "strings" 9 | 10 | "github.com/alecthomas/template" 11 | ) 12 | 13 | var ( 14 | preIndent = " " 15 | ) 16 | 17 | func formatTwoColumns(w io.Writer, indent, padding, width int, rows [][2]string) { 18 | // Find size of first column. 19 | s := 0 20 | for _, row := range rows { 21 | if c := len(row[0]); c > s && c < 30 { 22 | s = c 23 | } 24 | } 25 | 26 | indentStr := strings.Repeat(" ", indent) 27 | offsetStr := strings.Repeat(" ", s+padding) 28 | 29 | for _, row := range rows { 30 | buf := bytes.NewBuffer(nil) 31 | doc.ToText(buf, row[1], "", preIndent, width-s-padding-indent) 32 | lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n") 33 | fmt.Fprintf(w, "%s%-*s%*s", indentStr, s, row[0], padding, "") 34 | if len(row[0]) >= 30 { 35 | fmt.Fprintf(w, "\n%s%s", indentStr, offsetStr) 36 | } 37 | fmt.Fprintf(w, "%s\n", lines[0]) 38 | for _, line := range lines[1:] { 39 | fmt.Fprintf(w, "%s%s%s\n", indentStr, offsetStr, line) 40 | } 41 | } 42 | } 43 | 44 | // Usage writes application usage to w. It parses args to determine 45 | // appropriate help context, such as which command to show help for. 46 | func (a *Application) Usage(args []string) { 47 | context, err := a.parseContext(true, args) 48 | a.FatalIfError(err, "") 49 | if err := a.UsageForContextWithTemplate(context, 2, a.usageTemplate); err != nil { 50 | panic(err) 51 | } 52 | } 53 | 54 | func formatAppUsage(app *ApplicationModel) string { 55 | s := []string{app.Name} 56 | if len(app.Flags) > 0 { 57 | s = append(s, app.FlagSummary()) 58 | } 59 | if len(app.Args) > 0 { 60 | s = append(s, app.ArgSummary()) 61 | } 62 | return strings.Join(s, " ") 63 | } 64 | 65 | func formatCmdUsage(app *ApplicationModel, cmd *CmdModel) string { 66 | s := []string{app.Name, cmd.String()} 67 | if len(app.Flags) > 0 { 68 | s = append(s, app.FlagSummary()) 69 | } 70 | if len(app.Args) > 0 { 71 | s = append(s, app.ArgSummary()) 72 | } 73 | return strings.Join(s, " ") 74 | } 75 | 76 | func formatFlag(haveShort bool, flag *FlagModel) string { 77 | flagString := "" 78 | if flag.Short != 0 { 79 | flagString += fmt.Sprintf("-%c, --%s", flag.Short, flag.Name) 80 | } else { 81 | if haveShort { 82 | flagString += fmt.Sprintf(" --%s", flag.Name) 83 | } else { 84 | flagString += fmt.Sprintf("--%s", flag.Name) 85 | } 86 | } 87 | if !flag.IsBoolFlag() { 88 | flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder()) 89 | } 90 | if v, ok := flag.Value.(repeatableFlag); ok && v.IsCumulative() { 91 | flagString += " ..." 92 | } 93 | return flagString 94 | } 95 | 96 | type templateParseContext struct { 97 | SelectedCommand *CmdModel 98 | *FlagGroupModel 99 | *ArgGroupModel 100 | } 101 | 102 | type templateContext struct { 103 | App *ApplicationModel 104 | Width int 105 | Context *templateParseContext 106 | } 107 | 108 | // UsageForContext displays usage information from a ParseContext (obtained from 109 | // Application.ParseContext() or Action(f) callbacks). 110 | func (a *Application) UsageForContext(context *ParseContext) error { 111 | return a.UsageForContextWithTemplate(context, 2, a.usageTemplate) 112 | } 113 | 114 | // UsageForContextWithTemplate is the base usage function. You generally don't need to use this. 115 | func (a *Application) UsageForContextWithTemplate(context *ParseContext, indent int, tmpl string) error { 116 | width := guessWidth(a.usageWriter) 117 | funcs := template.FuncMap{ 118 | "Indent": func(level int) string { 119 | return strings.Repeat(" ", level*indent) 120 | }, 121 | "Wrap": func(indent int, s string) string { 122 | buf := bytes.NewBuffer(nil) 123 | indentText := strings.Repeat(" ", indent) 124 | doc.ToText(buf, s, indentText, " "+indentText, width-indent) 125 | return buf.String() 126 | }, 127 | "FormatFlag": formatFlag, 128 | "FlagsToTwoColumns": func(f []*FlagModel) [][2]string { 129 | rows := [][2]string{} 130 | haveShort := false 131 | for _, flag := range f { 132 | if flag.Short != 0 { 133 | haveShort = true 134 | break 135 | } 136 | } 137 | for _, flag := range f { 138 | if !flag.Hidden { 139 | rows = append(rows, [2]string{formatFlag(haveShort, flag), flag.Help}) 140 | } 141 | } 142 | return rows 143 | }, 144 | "RequiredFlags": func(f []*FlagModel) []*FlagModel { 145 | requiredFlags := []*FlagModel{} 146 | for _, flag := range f { 147 | if flag.Required { 148 | requiredFlags = append(requiredFlags, flag) 149 | } 150 | } 151 | return requiredFlags 152 | }, 153 | "OptionalFlags": func(f []*FlagModel) []*FlagModel { 154 | optionalFlags := []*FlagModel{} 155 | for _, flag := range f { 156 | if !flag.Required { 157 | optionalFlags = append(optionalFlags, flag) 158 | } 159 | } 160 | return optionalFlags 161 | }, 162 | "ArgsToTwoColumns": func(a []*ArgModel) [][2]string { 163 | rows := [][2]string{} 164 | for _, arg := range a { 165 | s := "<" + arg.Name + ">" 166 | if !arg.Required { 167 | s = "[" + s + "]" 168 | } 169 | rows = append(rows, [2]string{s, arg.Help}) 170 | } 171 | return rows 172 | }, 173 | "FormatTwoColumns": func(rows [][2]string) string { 174 | buf := bytes.NewBuffer(nil) 175 | formatTwoColumns(buf, indent, indent, width, rows) 176 | return buf.String() 177 | }, 178 | "FormatTwoColumnsWithIndent": func(rows [][2]string, indent, padding int) string { 179 | buf := bytes.NewBuffer(nil) 180 | formatTwoColumns(buf, indent, padding, width, rows) 181 | return buf.String() 182 | }, 183 | "FormatAppUsage": formatAppUsage, 184 | "FormatCommandUsage": formatCmdUsage, 185 | "IsCumulative": func(value Value) bool { 186 | r, ok := value.(remainderArg) 187 | return ok && r.IsCumulative() 188 | }, 189 | "Char": func(c rune) string { 190 | return string(c) 191 | }, 192 | } 193 | t, err := template.New("usage").Funcs(funcs).Parse(tmpl) 194 | if err != nil { 195 | return err 196 | } 197 | var selectedCommand *CmdModel 198 | if context.SelectedCommand != nil { 199 | selectedCommand = context.SelectedCommand.Model() 200 | } 201 | ctx := templateContext{ 202 | App: a.Model(), 203 | Width: width, 204 | Context: &templateParseContext{ 205 | SelectedCommand: selectedCommand, 206 | FlagGroupModel: context.flags.Model(), 207 | ArgGroupModel: context.arguments.Model(), 208 | }, 209 | } 210 | return t.Execute(a.usageWriter, ctx) 211 | } 212 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/errors/errors.go: -------------------------------------------------------------------------------- 1 | // Package errors provides simple error handling primitives. 2 | // 3 | // The traditional error handling idiom in Go is roughly akin to 4 | // 5 | // if err != nil { 6 | // return err 7 | // } 8 | // 9 | // which applied recursively up the call stack results in error reports 10 | // without context or debugging information. The errors package allows 11 | // programmers to add context to the failure path in their code in a way 12 | // that does not destroy the original value of the error. 13 | // 14 | // Adding context to an error 15 | // 16 | // The errors.Wrap function returns a new error that adds context to the 17 | // original error. For example 18 | // 19 | // _, err := ioutil.ReadAll(r) 20 | // if err != nil { 21 | // return errors.Wrap(err, "read failed") 22 | // } 23 | // 24 | // Retrieving the cause of an error 25 | // 26 | // Using errors.Wrap constructs a stack of errors, adding context to the 27 | // preceding error. Depending on the nature of the error it may be necessary 28 | // to reverse the operation of errors.Wrap to retrieve the original error 29 | // for inspection. Any error value which implements this interface 30 | // 31 | // type causer interface { 32 | // Cause() error 33 | // } 34 | // 35 | // can be inspected by errors.Cause. errors.Cause will recursively retrieve 36 | // the topmost error which does not implement causer, which is assumed to be 37 | // the original cause. For example: 38 | // 39 | // switch err := errors.Cause(err).(type) { 40 | // case *MyError: 41 | // // handle specifically 42 | // default: 43 | // // unknown error 44 | // } 45 | // 46 | // causer interface is not exported by this package, but is considered a part 47 | // of stable public API. 48 | // 49 | // Formatted printing of errors 50 | // 51 | // All error values returned from this package implement fmt.Formatter and can 52 | // be formatted by the fmt package. The following verbs are supported 53 | // 54 | // %s print the error. If the error has a Cause it will be 55 | // printed recursively 56 | // %v see %s 57 | // %+v extended format. Each Frame of the error's StackTrace will 58 | // be printed in detail. 59 | // 60 | // Retrieving the stack trace of an error or wrapper 61 | // 62 | // New, Errorf, Wrap, and Wrapf record a stack trace at the point they are 63 | // invoked. This information can be retrieved with the following interface. 64 | // 65 | // type stackTracer interface { 66 | // StackTrace() errors.StackTrace 67 | // } 68 | // 69 | // Where errors.StackTrace is defined as 70 | // 71 | // type StackTrace []Frame 72 | // 73 | // The Frame type represents a call site in the stack trace. Frame supports 74 | // the fmt.Formatter interface that can be used for printing information about 75 | // the stack trace of this error. For example: 76 | // 77 | // if err, ok := err.(stackTracer); ok { 78 | // for _, f := range err.StackTrace() { 79 | // fmt.Printf("%+s:%d", f) 80 | // } 81 | // } 82 | // 83 | // stackTracer interface is not exported by this package, but is considered a part 84 | // of stable public API. 85 | // 86 | // See the documentation for Frame.Format for more details. 87 | package errors 88 | 89 | import ( 90 | "fmt" 91 | "io" 92 | ) 93 | 94 | // New returns an error with the supplied message. 95 | // New also records the stack trace at the point it was called. 96 | func New(message string) error { 97 | return &fundamental{ 98 | msg: message, 99 | stack: callers(), 100 | } 101 | } 102 | 103 | // Errorf formats according to a format specifier and returns the string 104 | // as a value that satisfies error. 105 | // Errorf also records the stack trace at the point it was called. 106 | func Errorf(format string, args ...interface{}) error { 107 | return &fundamental{ 108 | msg: fmt.Sprintf(format, args...), 109 | stack: callers(), 110 | } 111 | } 112 | 113 | // fundamental is an error that has a message and a stack, but no caller. 114 | type fundamental struct { 115 | msg string 116 | *stack 117 | } 118 | 119 | func (f *fundamental) Error() string { return f.msg } 120 | 121 | func (f *fundamental) Format(s fmt.State, verb rune) { 122 | switch verb { 123 | case 'v': 124 | if s.Flag('+') { 125 | io.WriteString(s, f.msg) 126 | f.stack.Format(s, verb) 127 | return 128 | } 129 | fallthrough 130 | case 's': 131 | io.WriteString(s, f.msg) 132 | case 'q': 133 | fmt.Fprintf(s, "%q", f.msg) 134 | } 135 | } 136 | 137 | type withStack struct { 138 | error 139 | *stack 140 | } 141 | 142 | func (w *withStack) Cause() error { return w.error } 143 | 144 | func (w *withStack) Format(s fmt.State, verb rune) { 145 | switch verb { 146 | case 'v': 147 | if s.Flag('+') { 148 | fmt.Fprintf(s, "%+v", w.Cause()) 149 | w.stack.Format(s, verb) 150 | return 151 | } 152 | fallthrough 153 | case 's': 154 | io.WriteString(s, w.Error()) 155 | case 'q': 156 | fmt.Fprintf(s, "%q", w.Error()) 157 | } 158 | } 159 | 160 | // Wrap returns an error annotating err with message. 161 | // If err is nil, Wrap returns nil. 162 | func Wrap(err error, message string) error { 163 | if err == nil { 164 | return nil 165 | } 166 | err = &withMessage{ 167 | cause: err, 168 | msg: message, 169 | } 170 | return &withStack{ 171 | err, 172 | callers(), 173 | } 174 | } 175 | 176 | // Wrapf returns an error annotating err with the format specifier. 177 | // If err is nil, Wrapf returns nil. 178 | func Wrapf(err error, format string, args ...interface{}) error { 179 | if err == nil { 180 | return nil 181 | } 182 | err = &withMessage{ 183 | cause: err, 184 | msg: fmt.Sprintf(format, args...), 185 | } 186 | return &withStack{ 187 | err, 188 | callers(), 189 | } 190 | } 191 | 192 | type withMessage struct { 193 | cause error 194 | msg string 195 | } 196 | 197 | func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } 198 | func (w *withMessage) Cause() error { return w.cause } 199 | 200 | func (w *withMessage) Format(s fmt.State, verb rune) { 201 | switch verb { 202 | case 'v': 203 | if s.Flag('+') { 204 | fmt.Fprintf(s, "%+v\n", w.Cause()) 205 | io.WriteString(s, w.msg) 206 | return 207 | } 208 | fallthrough 209 | case 's', 'q': 210 | io.WriteString(s, w.Error()) 211 | } 212 | } 213 | 214 | // Cause returns the underlying cause of the error, if possible. 215 | // An error value has a cause if it implements the following 216 | // interface: 217 | // 218 | // type causer interface { 219 | // Cause() error 220 | // } 221 | // 222 | // If the error does not implement Cause, the original error will 223 | // be returned. If the error is nil, nil will be returned without further 224 | // investigation. 225 | func Cause(err error) error { 226 | type causer interface { 227 | Cause() error 228 | } 229 | 230 | for err != nil { 231 | cause, ok := err.(causer) 232 | if !ok { 233 | break 234 | } 235 | err = cause.Cause() 236 | } 237 | return err 238 | } 239 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/template/template.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package template 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | 11 | "github.com/alecthomas/template/parse" 12 | ) 13 | 14 | // common holds the information shared by related templates. 15 | type common struct { 16 | tmpl map[string]*Template 17 | // We use two maps, one for parsing and one for execution. 18 | // This separation makes the API cleaner since it doesn't 19 | // expose reflection to the client. 20 | parseFuncs FuncMap 21 | execFuncs map[string]reflect.Value 22 | } 23 | 24 | // Template is the representation of a parsed template. The *parse.Tree 25 | // field is exported only for use by html/template and should be treated 26 | // as unexported by all other clients. 27 | type Template struct { 28 | name string 29 | *parse.Tree 30 | *common 31 | leftDelim string 32 | rightDelim string 33 | } 34 | 35 | // New allocates a new template with the given name. 36 | func New(name string) *Template { 37 | return &Template{ 38 | name: name, 39 | } 40 | } 41 | 42 | // Name returns the name of the template. 43 | func (t *Template) Name() string { 44 | return t.name 45 | } 46 | 47 | // New allocates a new template associated with the given one and with the same 48 | // delimiters. The association, which is transitive, allows one template to 49 | // invoke another with a {{template}} action. 50 | func (t *Template) New(name string) *Template { 51 | t.init() 52 | return &Template{ 53 | name: name, 54 | common: t.common, 55 | leftDelim: t.leftDelim, 56 | rightDelim: t.rightDelim, 57 | } 58 | } 59 | 60 | func (t *Template) init() { 61 | if t.common == nil { 62 | t.common = new(common) 63 | t.tmpl = make(map[string]*Template) 64 | t.parseFuncs = make(FuncMap) 65 | t.execFuncs = make(map[string]reflect.Value) 66 | } 67 | } 68 | 69 | // Clone returns a duplicate of the template, including all associated 70 | // templates. The actual representation is not copied, but the name space of 71 | // associated templates is, so further calls to Parse in the copy will add 72 | // templates to the copy but not to the original. Clone can be used to prepare 73 | // common templates and use them with variant definitions for other templates 74 | // by adding the variants after the clone is made. 75 | func (t *Template) Clone() (*Template, error) { 76 | nt := t.copy(nil) 77 | nt.init() 78 | nt.tmpl[t.name] = nt 79 | for k, v := range t.tmpl { 80 | if k == t.name { // Already installed. 81 | continue 82 | } 83 | // The associated templates share nt's common structure. 84 | tmpl := v.copy(nt.common) 85 | nt.tmpl[k] = tmpl 86 | } 87 | for k, v := range t.parseFuncs { 88 | nt.parseFuncs[k] = v 89 | } 90 | for k, v := range t.execFuncs { 91 | nt.execFuncs[k] = v 92 | } 93 | return nt, nil 94 | } 95 | 96 | // copy returns a shallow copy of t, with common set to the argument. 97 | func (t *Template) copy(c *common) *Template { 98 | nt := New(t.name) 99 | nt.Tree = t.Tree 100 | nt.common = c 101 | nt.leftDelim = t.leftDelim 102 | nt.rightDelim = t.rightDelim 103 | return nt 104 | } 105 | 106 | // AddParseTree creates a new template with the name and parse tree 107 | // and associates it with t. 108 | func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) { 109 | if t.common != nil && t.tmpl[name] != nil { 110 | return nil, fmt.Errorf("template: redefinition of template %q", name) 111 | } 112 | nt := t.New(name) 113 | nt.Tree = tree 114 | t.tmpl[name] = nt 115 | return nt, nil 116 | } 117 | 118 | // Templates returns a slice of the templates associated with t, including t 119 | // itself. 120 | func (t *Template) Templates() []*Template { 121 | if t.common == nil { 122 | return nil 123 | } 124 | // Return a slice so we don't expose the map. 125 | m := make([]*Template, 0, len(t.tmpl)) 126 | for _, v := range t.tmpl { 127 | m = append(m, v) 128 | } 129 | return m 130 | } 131 | 132 | // Delims sets the action delimiters to the specified strings, to be used in 133 | // subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template 134 | // definitions will inherit the settings. An empty delimiter stands for the 135 | // corresponding default: {{ or }}. 136 | // The return value is the template, so calls can be chained. 137 | func (t *Template) Delims(left, right string) *Template { 138 | t.leftDelim = left 139 | t.rightDelim = right 140 | return t 141 | } 142 | 143 | // Funcs adds the elements of the argument map to the template's function map. 144 | // It panics if a value in the map is not a function with appropriate return 145 | // type. However, it is legal to overwrite elements of the map. The return 146 | // value is the template, so calls can be chained. 147 | func (t *Template) Funcs(funcMap FuncMap) *Template { 148 | t.init() 149 | addValueFuncs(t.execFuncs, funcMap) 150 | addFuncs(t.parseFuncs, funcMap) 151 | return t 152 | } 153 | 154 | // Lookup returns the template with the given name that is associated with t, 155 | // or nil if there is no such template. 156 | func (t *Template) Lookup(name string) *Template { 157 | if t.common == nil { 158 | return nil 159 | } 160 | return t.tmpl[name] 161 | } 162 | 163 | // Parse parses a string into a template. Nested template definitions will be 164 | // associated with the top-level template t. Parse may be called multiple times 165 | // to parse definitions of templates to associate with t. It is an error if a 166 | // resulting template is non-empty (contains content other than template 167 | // definitions) and would replace a non-empty template with the same name. 168 | // (In multiple calls to Parse with the same receiver template, only one call 169 | // can contain text other than space, comments, and template definitions.) 170 | func (t *Template) Parse(text string) (*Template, error) { 171 | t.init() 172 | trees, err := parse.Parse(t.name, text, t.leftDelim, t.rightDelim, t.parseFuncs, builtins) 173 | if err != nil { 174 | return nil, err 175 | } 176 | // Add the newly parsed trees, including the one for t, into our common structure. 177 | for name, tree := range trees { 178 | // If the name we parsed is the name of this template, overwrite this template. 179 | // The associate method checks it's not a redefinition. 180 | tmpl := t 181 | if name != t.name { 182 | tmpl = t.New(name) 183 | } 184 | // Even if t == tmpl, we need to install it in the common.tmpl map. 185 | if replace, err := t.associate(tmpl, tree); err != nil { 186 | return nil, err 187 | } else if replace { 188 | tmpl.Tree = tree 189 | } 190 | tmpl.leftDelim = t.leftDelim 191 | tmpl.rightDelim = t.rightDelim 192 | } 193 | return t, nil 194 | } 195 | 196 | // associate installs the new template into the group of templates associated 197 | // with t. It is an error to reuse a name except to overwrite an empty 198 | // template. The two are already known to share the common structure. 199 | // The boolean return value reports wither to store this tree as t.Tree. 200 | func (t *Template) associate(new *Template, tree *parse.Tree) (bool, error) { 201 | if new.common != t.common { 202 | panic("internal error: associate not common") 203 | } 204 | name := new.name 205 | if old := t.tmpl[name]; old != nil { 206 | oldIsEmpty := parse.IsEmptyTree(old.Root) 207 | newIsEmpty := parse.IsEmptyTree(tree.Root) 208 | if newIsEmpty { 209 | // Whether old is empty or not, new is empty; no reason to replace old. 210 | return false, nil 211 | } 212 | if !oldIsEmpty { 213 | return false, fmt.Errorf("template: redefinition of template %q", name) 214 | } 215 | } 216 | t.tmpl[name] = new 217 | return true, nil 218 | } 219 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/cmd.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type cmdMixin struct { 9 | *flagGroup 10 | *argGroup 11 | *cmdGroup 12 | actionMixin 13 | } 14 | 15 | // CmdCompletion returns completion options for arguments, if that's where 16 | // parsing left off, or commands if there aren't any unsatisfied args. 17 | func (c *cmdMixin) CmdCompletion(context *ParseContext) []string { 18 | var options []string 19 | 20 | // Count args already satisfied - we won't complete those, and add any 21 | // default commands' alternatives, since they weren't listed explicitly 22 | // and the user may want to explicitly list something else. 23 | argsSatisfied := 0 24 | for _, el := range context.Elements { 25 | switch clause := el.Clause.(type) { 26 | case *ArgClause: 27 | if el.Value != nil && *el.Value != "" { 28 | argsSatisfied++ 29 | } 30 | case *CmdClause: 31 | options = append(options, clause.completionAlts...) 32 | default: 33 | } 34 | } 35 | 36 | if argsSatisfied < len(c.argGroup.args) { 37 | // Since not all args have been satisfied, show options for the current one 38 | options = append(options, c.argGroup.args[argsSatisfied].resolveCompletions()...) 39 | } else { 40 | // If all args are satisfied, then go back to completing commands 41 | for _, cmd := range c.cmdGroup.commandOrder { 42 | if !cmd.hidden { 43 | options = append(options, cmd.name) 44 | } 45 | } 46 | } 47 | 48 | return options 49 | } 50 | 51 | func (c *cmdMixin) FlagCompletion(flagName string, flagValue string) (choices []string, flagMatch bool, optionMatch bool) { 52 | // Check if flagName matches a known flag. 53 | // If it does, show the options for the flag 54 | // Otherwise, show all flags 55 | 56 | options := []string{} 57 | 58 | for _, flag := range c.flagGroup.flagOrder { 59 | // Loop through each flag and determine if a match exists 60 | if flag.name == flagName { 61 | // User typed entire flag. Need to look for flag options. 62 | options = flag.resolveCompletions() 63 | if len(options) == 0 { 64 | // No Options to Choose From, Assume Match. 65 | return options, true, true 66 | } 67 | 68 | // Loop options to find if the user specified value matches 69 | isPrefix := false 70 | matched := false 71 | 72 | for _, opt := range options { 73 | if flagValue == opt { 74 | matched = true 75 | } else if strings.HasPrefix(opt, flagValue) { 76 | isPrefix = true 77 | } 78 | } 79 | 80 | // Matched Flag Directly 81 | // Flag Value Not Prefixed, and Matched Directly 82 | return options, true, !isPrefix && matched 83 | } 84 | 85 | if !flag.hidden { 86 | options = append(options, "--"+flag.name) 87 | } 88 | } 89 | // No Flag directly matched. 90 | return options, false, false 91 | 92 | } 93 | 94 | type cmdGroup struct { 95 | app *Application 96 | parent *CmdClause 97 | commands map[string]*CmdClause 98 | commandOrder []*CmdClause 99 | } 100 | 101 | func (c *cmdGroup) defaultSubcommand() *CmdClause { 102 | for _, cmd := range c.commandOrder { 103 | if cmd.isDefault { 104 | return cmd 105 | } 106 | } 107 | return nil 108 | } 109 | 110 | func (c *cmdGroup) cmdNames() []string { 111 | names := make([]string, 0, len(c.commandOrder)) 112 | for _, cmd := range c.commandOrder { 113 | names = append(names, cmd.name) 114 | } 115 | return names 116 | } 117 | 118 | // GetArg gets a command definition. 119 | // 120 | // This allows existing commands to be modified after definition but before parsing. Useful for 121 | // modular applications. 122 | func (c *cmdGroup) GetCommand(name string) *CmdClause { 123 | return c.commands[name] 124 | } 125 | 126 | func newCmdGroup(app *Application) *cmdGroup { 127 | return &cmdGroup{ 128 | app: app, 129 | commands: make(map[string]*CmdClause), 130 | } 131 | } 132 | 133 | func (c *cmdGroup) flattenedCommands() (out []*CmdClause) { 134 | for _, cmd := range c.commandOrder { 135 | if len(cmd.commands) == 0 { 136 | out = append(out, cmd) 137 | } 138 | out = append(out, cmd.flattenedCommands()...) 139 | } 140 | return 141 | } 142 | 143 | func (c *cmdGroup) addCommand(name, help string) *CmdClause { 144 | cmd := newCommand(c.app, name, help) 145 | c.commands[name] = cmd 146 | c.commandOrder = append(c.commandOrder, cmd) 147 | return cmd 148 | } 149 | 150 | func (c *cmdGroup) init() error { 151 | seen := map[string]bool{} 152 | if c.defaultSubcommand() != nil && !c.have() { 153 | return fmt.Errorf("default subcommand %q provided but no subcommands defined", c.defaultSubcommand().name) 154 | } 155 | defaults := []string{} 156 | for _, cmd := range c.commandOrder { 157 | if cmd.isDefault { 158 | defaults = append(defaults, cmd.name) 159 | } 160 | if seen[cmd.name] { 161 | return fmt.Errorf("duplicate command %q", cmd.name) 162 | } 163 | seen[cmd.name] = true 164 | for _, alias := range cmd.aliases { 165 | if seen[alias] { 166 | return fmt.Errorf("alias duplicates existing command %q", alias) 167 | } 168 | c.commands[alias] = cmd 169 | } 170 | if err := cmd.init(); err != nil { 171 | return err 172 | } 173 | } 174 | if len(defaults) > 1 { 175 | return fmt.Errorf("more than one default subcommand exists: %s", strings.Join(defaults, ", ")) 176 | } 177 | return nil 178 | } 179 | 180 | func (c *cmdGroup) have() bool { 181 | return len(c.commands) > 0 182 | } 183 | 184 | type CmdClauseValidator func(*CmdClause) error 185 | 186 | // A CmdClause is a single top-level command. It encapsulates a set of flags 187 | // and either subcommands or positional arguments. 188 | type CmdClause struct { 189 | cmdMixin 190 | app *Application 191 | name string 192 | aliases []string 193 | help string 194 | isDefault bool 195 | validator CmdClauseValidator 196 | hidden bool 197 | completionAlts []string 198 | } 199 | 200 | func newCommand(app *Application, name, help string) *CmdClause { 201 | c := &CmdClause{ 202 | app: app, 203 | name: name, 204 | help: help, 205 | } 206 | c.flagGroup = newFlagGroup() 207 | c.argGroup = newArgGroup() 208 | c.cmdGroup = newCmdGroup(app) 209 | return c 210 | } 211 | 212 | // Add an Alias for this command. 213 | func (c *CmdClause) Alias(name string) *CmdClause { 214 | c.aliases = append(c.aliases, name) 215 | return c 216 | } 217 | 218 | // Validate sets a validation function to run when parsing. 219 | func (c *CmdClause) Validate(validator CmdClauseValidator) *CmdClause { 220 | c.validator = validator 221 | return c 222 | } 223 | 224 | func (c *CmdClause) FullCommand() string { 225 | out := []string{c.name} 226 | for p := c.parent; p != nil; p = p.parent { 227 | out = append([]string{p.name}, out...) 228 | } 229 | return strings.Join(out, " ") 230 | } 231 | 232 | // Command adds a new sub-command. 233 | func (c *CmdClause) Command(name, help string) *CmdClause { 234 | cmd := c.addCommand(name, help) 235 | cmd.parent = c 236 | return cmd 237 | } 238 | 239 | // Default makes this command the default if commands don't match. 240 | func (c *CmdClause) Default() *CmdClause { 241 | c.isDefault = true 242 | return c 243 | } 244 | 245 | func (c *CmdClause) Action(action Action) *CmdClause { 246 | c.addAction(action) 247 | return c 248 | } 249 | 250 | func (c *CmdClause) PreAction(action Action) *CmdClause { 251 | c.addPreAction(action) 252 | return c 253 | } 254 | 255 | func (c *CmdClause) init() error { 256 | if err := c.flagGroup.init(c.app.defaultEnvarPrefix()); err != nil { 257 | return err 258 | } 259 | if c.argGroup.have() && c.cmdGroup.have() { 260 | return fmt.Errorf("can't mix Arg()s with Command()s") 261 | } 262 | if err := c.argGroup.init(); err != nil { 263 | return err 264 | } 265 | if err := c.cmdGroup.init(); err != nil { 266 | return err 267 | } 268 | return nil 269 | } 270 | 271 | func (c *CmdClause) Hidden() *CmdClause { 272 | c.hidden = true 273 | return c 274 | } 275 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/templates.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | // Default usage template. 4 | var DefaultUsageTemplate = `{{define "FormatCommand"}}\ 5 | {{if .FlagSummary}} {{.FlagSummary}}{{end}}\ 6 | {{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ 7 | {{end}}\ 8 | 9 | {{define "FormatCommands"}}\ 10 | {{range .FlattenedCommands}}\ 11 | {{if not .Hidden}}\ 12 | {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} 13 | {{.Help|Wrap 4}} 14 | {{end}}\ 15 | {{end}}\ 16 | {{end}}\ 17 | 18 | {{define "FormatUsage"}}\ 19 | {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} 20 | {{if .Help}} 21 | {{.Help|Wrap 0}}\ 22 | {{end}}\ 23 | 24 | {{end}}\ 25 | 26 | {{if .Context.SelectedCommand}}\ 27 | usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} 28 | {{else}}\ 29 | usage: {{.App.Name}}{{template "FormatUsage" .App}} 30 | {{end}}\ 31 | {{if .Context.Flags}}\ 32 | Flags: 33 | {{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} 34 | {{end}}\ 35 | {{if .Context.Args}}\ 36 | Args: 37 | {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} 38 | {{end}}\ 39 | {{if .Context.SelectedCommand}}\ 40 | {{if len .Context.SelectedCommand.Commands}}\ 41 | Subcommands: 42 | {{template "FormatCommands" .Context.SelectedCommand}} 43 | {{end}}\ 44 | {{else if .App.Commands}}\ 45 | Commands: 46 | {{template "FormatCommands" .App}} 47 | {{end}}\ 48 | ` 49 | 50 | // Usage template where command's optional flags are listed separately 51 | var SeparateOptionalFlagsUsageTemplate = `{{define "FormatCommand"}}\ 52 | {{if .FlagSummary}} {{.FlagSummary}}{{end}}\ 53 | {{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ 54 | {{end}}\ 55 | 56 | {{define "FormatCommands"}}\ 57 | {{range .FlattenedCommands}}\ 58 | {{if not .Hidden}}\ 59 | {{.FullCommand}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} 60 | {{.Help|Wrap 4}} 61 | {{end}}\ 62 | {{end}}\ 63 | {{end}}\ 64 | 65 | {{define "FormatUsage"}}\ 66 | {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} 67 | {{if .Help}} 68 | {{.Help|Wrap 0}}\ 69 | {{end}}\ 70 | 71 | {{end}}\ 72 | {{if .Context.SelectedCommand}}\ 73 | usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} 74 | {{else}}\ 75 | usage: {{.App.Name}}{{template "FormatUsage" .App}} 76 | {{end}}\ 77 | 78 | {{if .Context.Flags|RequiredFlags}}\ 79 | Required flags: 80 | {{.Context.Flags|RequiredFlags|FlagsToTwoColumns|FormatTwoColumns}} 81 | {{end}}\ 82 | {{if .Context.Flags|OptionalFlags}}\ 83 | Optional flags: 84 | {{.Context.Flags|OptionalFlags|FlagsToTwoColumns|FormatTwoColumns}} 85 | {{end}}\ 86 | {{if .Context.Args}}\ 87 | Args: 88 | {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} 89 | {{end}}\ 90 | {{if .Context.SelectedCommand}}\ 91 | Subcommands: 92 | {{if .Context.SelectedCommand.Commands}}\ 93 | {{template "FormatCommands" .Context.SelectedCommand}} 94 | {{end}}\ 95 | {{else if .App.Commands}}\ 96 | Commands: 97 | {{template "FormatCommands" .App}} 98 | {{end}}\ 99 | ` 100 | 101 | // Usage template with compactly formatted commands. 102 | var CompactUsageTemplate = `{{define "FormatCommand"}}\ 103 | {{if .FlagSummary}} {{.FlagSummary}}{{end}}\ 104 | {{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ 105 | {{end}}\ 106 | 107 | {{define "FormatCommandList"}}\ 108 | {{range .}}\ 109 | {{if not .Hidden}}\ 110 | {{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}} 111 | {{end}}\ 112 | {{template "FormatCommandList" .Commands}}\ 113 | {{end}}\ 114 | {{end}}\ 115 | 116 | {{define "FormatUsage"}}\ 117 | {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} 118 | {{if .Help}} 119 | {{.Help|Wrap 0}}\ 120 | {{end}}\ 121 | 122 | {{end}}\ 123 | 124 | {{if .Context.SelectedCommand}}\ 125 | usage: {{.App.Name}} {{.Context.SelectedCommand}}{{template "FormatUsage" .Context.SelectedCommand}} 126 | {{else}}\ 127 | usage: {{.App.Name}}{{template "FormatUsage" .App}} 128 | {{end}}\ 129 | {{if .Context.Flags}}\ 130 | Flags: 131 | {{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} 132 | {{end}}\ 133 | {{if .Context.Args}}\ 134 | Args: 135 | {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} 136 | {{end}}\ 137 | {{if .Context.SelectedCommand}}\ 138 | {{if .Context.SelectedCommand.Commands}}\ 139 | Commands: 140 | {{.Context.SelectedCommand}} 141 | {{template "FormatCommandList" .Context.SelectedCommand.Commands}} 142 | {{end}}\ 143 | {{else if .App.Commands}}\ 144 | Commands: 145 | {{template "FormatCommandList" .App.Commands}} 146 | {{end}}\ 147 | ` 148 | 149 | var ManPageTemplate = `{{define "FormatFlags"}}\ 150 | {{range .Flags}}\ 151 | {{if not .Hidden}}\ 152 | .TP 153 | \fB{{if .Short}}-{{.Short|Char}}, {{end}}--{{.Name}}{{if not .IsBoolFlag}}={{.FormatPlaceHolder}}{{end}}\\fR 154 | {{.Help}} 155 | {{end}}\ 156 | {{end}}\ 157 | {{end}}\ 158 | 159 | {{define "FormatCommand"}}\ 160 | {{if .FlagSummary}} {{.FlagSummary}}{{end}}\ 161 | {{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}{{if .Default}}*{{end}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ 162 | {{end}}\ 163 | 164 | {{define "FormatCommands"}}\ 165 | {{range .FlattenedCommands}}\ 166 | {{if not .Hidden}}\ 167 | .SS 168 | \fB{{.FullCommand}}{{template "FormatCommand" .}}\\fR 169 | .PP 170 | {{.Help}} 171 | {{template "FormatFlags" .}}\ 172 | {{end}}\ 173 | {{end}}\ 174 | {{end}}\ 175 | 176 | {{define "FormatUsage"}}\ 177 | {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}}\\fR 178 | {{end}}\ 179 | 180 | .TH {{.App.Name}} 1 {{.App.Version}} "{{.App.Author}}" 181 | .SH "NAME" 182 | {{.App.Name}} 183 | .SH "SYNOPSIS" 184 | .TP 185 | \fB{{.App.Name}}{{template "FormatUsage" .App}} 186 | .SH "DESCRIPTION" 187 | {{.App.Help}} 188 | .SH "OPTIONS" 189 | {{template "FormatFlags" .App}}\ 190 | {{if .App.Commands}}\ 191 | .SH "COMMANDS" 192 | {{template "FormatCommands" .App}}\ 193 | {{end}}\ 194 | ` 195 | 196 | // Default usage template. 197 | var LongHelpTemplate = `{{define "FormatCommand"}}\ 198 | {{if .FlagSummary}} {{.FlagSummary}}{{end}}\ 199 | {{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}}...{{end}}{{if not .Required}}]{{end}}{{end}}\ 200 | {{end}}\ 201 | 202 | {{define "FormatCommands"}}\ 203 | {{range .FlattenedCommands}}\ 204 | {{if not .Hidden}}\ 205 | {{.FullCommand}}{{template "FormatCommand" .}} 206 | {{.Help|Wrap 4}} 207 | {{with .Flags|FlagsToTwoColumns}}{{FormatTwoColumnsWithIndent . 4 2}}{{end}} 208 | {{end}}\ 209 | {{end}}\ 210 | {{end}}\ 211 | 212 | {{define "FormatUsage"}}\ 213 | {{template "FormatCommand" .}}{{if .Commands}} [ ...]{{end}} 214 | {{if .Help}} 215 | {{.Help|Wrap 0}}\ 216 | {{end}}\ 217 | 218 | {{end}}\ 219 | 220 | usage: {{.App.Name}}{{template "FormatUsage" .App}} 221 | {{if .Context.Flags}}\ 222 | Flags: 223 | {{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}} 224 | {{end}}\ 225 | {{if .Context.Args}}\ 226 | Args: 227 | {{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}} 228 | {{end}}\ 229 | {{if .App.Commands}}\ 230 | Commands: 231 | {{template "FormatCommands" .App}} 232 | {{end}}\ 233 | ` 234 | 235 | var BashCompletionTemplate = ` 236 | _{{.App.Name}}_bash_autocomplete() { 237 | local cur prev opts base 238 | COMPREPLY=() 239 | cur="${COMP_WORDS[COMP_CWORD]}" 240 | opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} ) 241 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 242 | return 0 243 | } 244 | complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}} 245 | 246 | ` 247 | 248 | var ZshCompletionTemplate = ` 249 | #compdef {{.App.Name}} 250 | autoload -U compinit && compinit 251 | autoload -U bashcompinit && bashcompinit 252 | 253 | _{{.App.Name}}_bash_autocomplete() { 254 | local cur prev opts base 255 | COMPREPLY=() 256 | cur="${COMP_WORDS[COMP_CWORD]}" 257 | opts=$( ${COMP_WORDS[0]} --completion-bash ${COMP_WORDS[@]:1:$COMP_CWORD} ) 258 | COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) 259 | return 0 260 | } 261 | complete -F _{{.App.Name}}_bash_autocomplete {{.App.Name}} 262 | ` 263 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/flags.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | type flagGroup struct { 9 | short map[string]*FlagClause 10 | long map[string]*FlagClause 11 | flagOrder []*FlagClause 12 | } 13 | 14 | func newFlagGroup() *flagGroup { 15 | return &flagGroup{ 16 | short: map[string]*FlagClause{}, 17 | long: map[string]*FlagClause{}, 18 | } 19 | } 20 | 21 | // GetFlag gets a flag definition. 22 | // 23 | // This allows existing flags to be modified after definition but before parsing. Useful for 24 | // modular applications. 25 | func (f *flagGroup) GetFlag(name string) *FlagClause { 26 | return f.long[name] 27 | } 28 | 29 | // Flag defines a new flag with the given long name and help. 30 | func (f *flagGroup) Flag(name, help string) *FlagClause { 31 | flag := newFlag(name, help) 32 | f.long[name] = flag 33 | f.flagOrder = append(f.flagOrder, flag) 34 | return flag 35 | } 36 | 37 | func (f *flagGroup) init(defaultEnvarPrefix string) error { 38 | if err := f.checkDuplicates(); err != nil { 39 | return err 40 | } 41 | for _, flag := range f.long { 42 | if defaultEnvarPrefix != "" && !flag.noEnvar && flag.envar == "" { 43 | flag.envar = envarTransform(defaultEnvarPrefix + "_" + flag.name) 44 | } 45 | if err := flag.init(); err != nil { 46 | return err 47 | } 48 | if flag.shorthand != 0 { 49 | f.short[string(flag.shorthand)] = flag 50 | } 51 | } 52 | return nil 53 | } 54 | 55 | func (f *flagGroup) checkDuplicates() error { 56 | seenShort := map[byte]bool{} 57 | seenLong := map[string]bool{} 58 | for _, flag := range f.flagOrder { 59 | if flag.shorthand != 0 { 60 | if _, ok := seenShort[flag.shorthand]; ok { 61 | return fmt.Errorf("duplicate short flag -%c", flag.shorthand) 62 | } 63 | seenShort[flag.shorthand] = true 64 | } 65 | if _, ok := seenLong[flag.name]; ok { 66 | return fmt.Errorf("duplicate long flag --%s", flag.name) 67 | } 68 | seenLong[flag.name] = true 69 | } 70 | return nil 71 | } 72 | 73 | func (f *flagGroup) parse(context *ParseContext) (*FlagClause, error) { 74 | var token *Token 75 | 76 | loop: 77 | for { 78 | token = context.Peek() 79 | switch token.Type { 80 | case TokenEOL: 81 | break loop 82 | 83 | case TokenLong, TokenShort: 84 | flagToken := token 85 | defaultValue := "" 86 | var flag *FlagClause 87 | var ok bool 88 | invert := false 89 | 90 | name := token.Value 91 | if token.Type == TokenLong { 92 | flag, ok = f.long[name] 93 | if !ok { 94 | if strings.HasPrefix(name, "no-") { 95 | name = name[3:] 96 | invert = true 97 | } 98 | flag, ok = f.long[name] 99 | } 100 | if !ok { 101 | return nil, fmt.Errorf("unknown long flag '%s'", flagToken) 102 | } 103 | } else { 104 | flag, ok = f.short[name] 105 | if !ok { 106 | return nil, fmt.Errorf("unknown short flag '%s'", flagToken) 107 | } 108 | } 109 | 110 | context.Next() 111 | 112 | fb, ok := flag.value.(boolFlag) 113 | if ok && fb.IsBoolFlag() { 114 | if invert { 115 | defaultValue = "false" 116 | } else { 117 | defaultValue = "true" 118 | } 119 | } else { 120 | if invert { 121 | context.Push(token) 122 | return nil, fmt.Errorf("unknown long flag '%s'", flagToken) 123 | } 124 | token = context.Peek() 125 | if token.Type != TokenArg { 126 | context.Push(token) 127 | return nil, fmt.Errorf("expected argument for flag '%s'", flagToken) 128 | } 129 | context.Next() 130 | defaultValue = token.Value 131 | } 132 | 133 | context.matchedFlag(flag, defaultValue) 134 | return flag, nil 135 | 136 | default: 137 | break loop 138 | } 139 | } 140 | return nil, nil 141 | } 142 | 143 | // FlagClause is a fluid interface used to build flags. 144 | type FlagClause struct { 145 | parserMixin 146 | actionMixin 147 | completionsMixin 148 | envarMixin 149 | name string 150 | shorthand byte 151 | help string 152 | defaultValues []string 153 | placeholder string 154 | hidden bool 155 | } 156 | 157 | func newFlag(name, help string) *FlagClause { 158 | f := &FlagClause{ 159 | name: name, 160 | help: help, 161 | } 162 | return f 163 | } 164 | 165 | func (f *FlagClause) setDefault() error { 166 | if f.HasEnvarValue() { 167 | if v, ok := f.value.(repeatableFlag); !ok || !v.IsCumulative() { 168 | // Use the value as-is 169 | return f.value.Set(f.GetEnvarValue()) 170 | } else { 171 | for _, value := range f.GetSplitEnvarValue() { 172 | if err := f.value.Set(value); err != nil { 173 | return err 174 | } 175 | } 176 | return nil 177 | } 178 | } 179 | 180 | if len(f.defaultValues) > 0 { 181 | for _, defaultValue := range f.defaultValues { 182 | if err := f.value.Set(defaultValue); err != nil { 183 | return err 184 | } 185 | } 186 | return nil 187 | } 188 | 189 | return nil 190 | } 191 | 192 | func (f *FlagClause) needsValue() bool { 193 | haveDefault := len(f.defaultValues) > 0 194 | return f.required && !(haveDefault || f.HasEnvarValue()) 195 | } 196 | 197 | func (f *FlagClause) init() error { 198 | if f.required && len(f.defaultValues) > 0 { 199 | return fmt.Errorf("required flag '--%s' with default value that will never be used", f.name) 200 | } 201 | if f.value == nil { 202 | return fmt.Errorf("no type defined for --%s (eg. .String())", f.name) 203 | } 204 | if v, ok := f.value.(repeatableFlag); (!ok || !v.IsCumulative()) && len(f.defaultValues) > 1 { 205 | return fmt.Errorf("invalid default for '--%s', expecting single value", f.name) 206 | } 207 | return nil 208 | } 209 | 210 | // Dispatch to the given function after the flag is parsed and validated. 211 | func (f *FlagClause) Action(action Action) *FlagClause { 212 | f.addAction(action) 213 | return f 214 | } 215 | 216 | func (f *FlagClause) PreAction(action Action) *FlagClause { 217 | f.addPreAction(action) 218 | return f 219 | } 220 | 221 | // HintAction registers a HintAction (function) for the flag to provide completions 222 | func (a *FlagClause) HintAction(action HintAction) *FlagClause { 223 | a.addHintAction(action) 224 | return a 225 | } 226 | 227 | // HintOptions registers any number of options for the flag to provide completions 228 | func (a *FlagClause) HintOptions(options ...string) *FlagClause { 229 | a.addHintAction(func() []string { 230 | return options 231 | }) 232 | return a 233 | } 234 | 235 | func (a *FlagClause) EnumVar(target *string, options ...string) { 236 | a.parserMixin.EnumVar(target, options...) 237 | a.addHintActionBuiltin(func() []string { 238 | return options 239 | }) 240 | } 241 | 242 | func (a *FlagClause) Enum(options ...string) (target *string) { 243 | a.addHintActionBuiltin(func() []string { 244 | return options 245 | }) 246 | return a.parserMixin.Enum(options...) 247 | } 248 | 249 | // Default values for this flag. They *must* be parseable by the value of the flag. 250 | func (f *FlagClause) Default(values ...string) *FlagClause { 251 | f.defaultValues = values 252 | return f 253 | } 254 | 255 | // DEPRECATED: Use Envar(name) instead. 256 | func (f *FlagClause) OverrideDefaultFromEnvar(envar string) *FlagClause { 257 | return f.Envar(envar) 258 | } 259 | 260 | // Envar overrides the default value(s) for a flag from an environment variable, 261 | // if it is set. Several default values can be provided by using new lines to 262 | // separate them. 263 | func (f *FlagClause) Envar(name string) *FlagClause { 264 | f.envar = name 265 | f.noEnvar = false 266 | return f 267 | } 268 | 269 | // NoEnvar forces environment variable defaults to be disabled for this flag. 270 | // Most useful in conjunction with app.DefaultEnvars(). 271 | func (f *FlagClause) NoEnvar() *FlagClause { 272 | f.envar = "" 273 | f.noEnvar = true 274 | return f 275 | } 276 | 277 | // PlaceHolder sets the place-holder string used for flag values in the help. The 278 | // default behaviour is to use the value provided by Default() if provided, 279 | // then fall back on the capitalized flag name. 280 | func (f *FlagClause) PlaceHolder(placeholder string) *FlagClause { 281 | f.placeholder = placeholder 282 | return f 283 | } 284 | 285 | // Hidden hides a flag from usage but still allows it to be used. 286 | func (f *FlagClause) Hidden() *FlagClause { 287 | f.hidden = true 288 | return f 289 | } 290 | 291 | // Required makes the flag required. You can not provide a Default() value to a Required() flag. 292 | func (f *FlagClause) Required() *FlagClause { 293 | f.required = true 294 | return f 295 | } 296 | 297 | // Short sets the short flag name. 298 | func (f *FlagClause) Short(name byte) *FlagClause { 299 | f.shorthand = name 300 | return f 301 | } 302 | 303 | // Bool makes this flag a boolean flag. 304 | func (f *FlagClause) Bool() (target *bool) { 305 | target = new(bool) 306 | f.SetValue(newBoolValue(target)) 307 | return 308 | } 309 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/parser.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type TokenType int 11 | 12 | // Token types. 13 | const ( 14 | TokenShort TokenType = iota 15 | TokenLong 16 | TokenArg 17 | TokenError 18 | TokenEOL 19 | ) 20 | 21 | func (t TokenType) String() string { 22 | switch t { 23 | case TokenShort: 24 | return "short flag" 25 | case TokenLong: 26 | return "long flag" 27 | case TokenArg: 28 | return "argument" 29 | case TokenError: 30 | return "error" 31 | case TokenEOL: 32 | return "" 33 | } 34 | return "?" 35 | } 36 | 37 | var ( 38 | TokenEOLMarker = Token{-1, TokenEOL, ""} 39 | ) 40 | 41 | type Token struct { 42 | Index int 43 | Type TokenType 44 | Value string 45 | } 46 | 47 | func (t *Token) Equal(o *Token) bool { 48 | return t.Index == o.Index 49 | } 50 | 51 | func (t *Token) IsFlag() bool { 52 | return t.Type == TokenShort || t.Type == TokenLong 53 | } 54 | 55 | func (t *Token) IsEOF() bool { 56 | return t.Type == TokenEOL 57 | } 58 | 59 | func (t *Token) String() string { 60 | switch t.Type { 61 | case TokenShort: 62 | return "-" + t.Value 63 | case TokenLong: 64 | return "--" + t.Value 65 | case TokenArg: 66 | return t.Value 67 | case TokenError: 68 | return "error: " + t.Value 69 | case TokenEOL: 70 | return "" 71 | default: 72 | panic("unhandled type") 73 | } 74 | } 75 | 76 | // A union of possible elements in a parse stack. 77 | type ParseElement struct { 78 | // Clause is either *CmdClause, *ArgClause or *FlagClause. 79 | Clause interface{} 80 | // Value is corresponding value for an ArgClause or FlagClause (if any). 81 | Value *string 82 | } 83 | 84 | // ParseContext holds the current context of the parser. When passed to 85 | // Action() callbacks Elements will be fully populated with *FlagClause, 86 | // *ArgClause and *CmdClause values and their corresponding arguments (if 87 | // any). 88 | type ParseContext struct { 89 | SelectedCommand *CmdClause 90 | ignoreDefault bool 91 | argsOnly bool 92 | peek []*Token 93 | argi int // Index of current command-line arg we're processing. 94 | args []string 95 | rawArgs []string 96 | flags *flagGroup 97 | arguments *argGroup 98 | argumenti int // Cursor into arguments 99 | // Flags, arguments and commands encountered and collected during parse. 100 | Elements []*ParseElement 101 | } 102 | 103 | func (p *ParseContext) nextArg() *ArgClause { 104 | if p.argumenti >= len(p.arguments.args) { 105 | return nil 106 | } 107 | arg := p.arguments.args[p.argumenti] 108 | if !arg.consumesRemainder() { 109 | p.argumenti++ 110 | } 111 | return arg 112 | } 113 | 114 | func (p *ParseContext) next() { 115 | p.argi++ 116 | p.args = p.args[1:] 117 | } 118 | 119 | // HasTrailingArgs returns true if there are unparsed command-line arguments. 120 | // This can occur if the parser can not match remaining arguments. 121 | func (p *ParseContext) HasTrailingArgs() bool { 122 | return len(p.args) > 0 123 | } 124 | 125 | func tokenize(args []string, ignoreDefault bool) *ParseContext { 126 | return &ParseContext{ 127 | ignoreDefault: ignoreDefault, 128 | args: args, 129 | rawArgs: args, 130 | flags: newFlagGroup(), 131 | arguments: newArgGroup(), 132 | } 133 | } 134 | 135 | func (p *ParseContext) mergeFlags(flags *flagGroup) { 136 | for _, flag := range flags.flagOrder { 137 | if flag.shorthand != 0 { 138 | p.flags.short[string(flag.shorthand)] = flag 139 | } 140 | p.flags.long[flag.name] = flag 141 | p.flags.flagOrder = append(p.flags.flagOrder, flag) 142 | } 143 | } 144 | 145 | func (p *ParseContext) mergeArgs(args *argGroup) { 146 | for _, arg := range args.args { 147 | p.arguments.args = append(p.arguments.args, arg) 148 | } 149 | } 150 | 151 | func (p *ParseContext) EOL() bool { 152 | return p.Peek().Type == TokenEOL 153 | } 154 | 155 | // Next token in the parse context. 156 | func (p *ParseContext) Next() *Token { 157 | if len(p.peek) > 0 { 158 | return p.pop() 159 | } 160 | 161 | // End of tokens. 162 | if len(p.args) == 0 { 163 | return &Token{Index: p.argi, Type: TokenEOL} 164 | } 165 | 166 | arg := p.args[0] 167 | p.next() 168 | 169 | if p.argsOnly { 170 | return &Token{p.argi, TokenArg, arg} 171 | } 172 | 173 | // All remaining args are passed directly. 174 | if arg == "--" { 175 | p.argsOnly = true 176 | return p.Next() 177 | } 178 | 179 | if strings.HasPrefix(arg, "--") { 180 | parts := strings.SplitN(arg[2:], "=", 2) 181 | token := &Token{p.argi, TokenLong, parts[0]} 182 | if len(parts) == 2 { 183 | p.Push(&Token{p.argi, TokenArg, parts[1]}) 184 | } 185 | return token 186 | } 187 | 188 | if strings.HasPrefix(arg, "-") { 189 | if len(arg) == 1 { 190 | return &Token{Index: p.argi, Type: TokenShort} 191 | } 192 | short := arg[1:2] 193 | flag, ok := p.flags.short[short] 194 | // Not a known short flag, we'll just return it anyway. 195 | if !ok { 196 | } else if fb, ok := flag.value.(boolFlag); ok && fb.IsBoolFlag() { 197 | // Bool short flag. 198 | } else { 199 | // Short flag with combined argument: -fARG 200 | token := &Token{p.argi, TokenShort, short} 201 | if len(arg) > 2 { 202 | p.Push(&Token{p.argi, TokenArg, arg[2:]}) 203 | } 204 | return token 205 | } 206 | 207 | if len(arg) > 2 { 208 | p.args = append([]string{"-" + arg[2:]}, p.args...) 209 | } 210 | return &Token{p.argi, TokenShort, short} 211 | } else if strings.HasPrefix(arg, "@") { 212 | expanded, err := ExpandArgsFromFile(arg[1:]) 213 | if err != nil { 214 | return &Token{p.argi, TokenError, err.Error()} 215 | } 216 | if p.argi >= len(p.args) { 217 | p.args = append(p.args[:p.argi-1], expanded...) 218 | } else { 219 | p.args = append(p.args[:p.argi-1], append(expanded, p.args[p.argi+1:]...)...) 220 | } 221 | return p.Next() 222 | } 223 | 224 | return &Token{p.argi, TokenArg, arg} 225 | } 226 | 227 | func (p *ParseContext) Peek() *Token { 228 | if len(p.peek) == 0 { 229 | return p.Push(p.Next()) 230 | } 231 | return p.peek[len(p.peek)-1] 232 | } 233 | 234 | func (p *ParseContext) Push(token *Token) *Token { 235 | p.peek = append(p.peek, token) 236 | return token 237 | } 238 | 239 | func (p *ParseContext) pop() *Token { 240 | end := len(p.peek) - 1 241 | token := p.peek[end] 242 | p.peek = p.peek[0:end] 243 | return token 244 | } 245 | 246 | func (p *ParseContext) String() string { 247 | return p.SelectedCommand.FullCommand() 248 | } 249 | 250 | func (p *ParseContext) matchedFlag(flag *FlagClause, value string) { 251 | p.Elements = append(p.Elements, &ParseElement{Clause: flag, Value: &value}) 252 | } 253 | 254 | func (p *ParseContext) matchedArg(arg *ArgClause, value string) { 255 | p.Elements = append(p.Elements, &ParseElement{Clause: arg, Value: &value}) 256 | } 257 | 258 | func (p *ParseContext) matchedCmd(cmd *CmdClause) { 259 | p.Elements = append(p.Elements, &ParseElement{Clause: cmd}) 260 | p.mergeFlags(cmd.flagGroup) 261 | p.mergeArgs(cmd.argGroup) 262 | p.SelectedCommand = cmd 263 | } 264 | 265 | // Expand arguments from a file. Lines starting with # will be treated as comments. 266 | func ExpandArgsFromFile(filename string) (out []string, err error) { 267 | r, err := os.Open(filename) 268 | if err != nil { 269 | return nil, err 270 | } 271 | defer r.Close() 272 | scanner := bufio.NewScanner(r) 273 | for scanner.Scan() { 274 | line := scanner.Text() 275 | if strings.HasPrefix(line, "#") { 276 | continue 277 | } 278 | out = append(out, line) 279 | } 280 | err = scanner.Err() 281 | return 282 | } 283 | 284 | func parse(context *ParseContext, app *Application) (err error) { 285 | context.mergeFlags(app.flagGroup) 286 | context.mergeArgs(app.argGroup) 287 | 288 | cmds := app.cmdGroup 289 | ignoreDefault := context.ignoreDefault 290 | 291 | loop: 292 | for !context.EOL() { 293 | token := context.Peek() 294 | 295 | switch token.Type { 296 | case TokenLong, TokenShort: 297 | if flag, err := context.flags.parse(context); err != nil { 298 | if !ignoreDefault { 299 | if cmd := cmds.defaultSubcommand(); cmd != nil { 300 | cmd.completionAlts = cmds.cmdNames() 301 | context.matchedCmd(cmd) 302 | cmds = cmd.cmdGroup 303 | break 304 | } 305 | } 306 | return err 307 | } else if flag == HelpFlag { 308 | ignoreDefault = true 309 | } 310 | 311 | case TokenArg: 312 | if cmds.have() { 313 | selectedDefault := false 314 | cmd, ok := cmds.commands[token.String()] 315 | if !ok { 316 | if !ignoreDefault { 317 | if cmd = cmds.defaultSubcommand(); cmd != nil { 318 | cmd.completionAlts = cmds.cmdNames() 319 | selectedDefault = true 320 | } 321 | } 322 | if cmd == nil { 323 | return fmt.Errorf("expected command but got %q", token) 324 | } 325 | } 326 | if cmd == HelpCommand { 327 | ignoreDefault = true 328 | } 329 | cmd.completionAlts = nil 330 | context.matchedCmd(cmd) 331 | cmds = cmd.cmdGroup 332 | if !selectedDefault { 333 | context.Next() 334 | } 335 | } else if context.arguments.have() { 336 | if app.noInterspersed { 337 | // no more flags 338 | context.argsOnly = true 339 | } 340 | arg := context.nextArg() 341 | if arg == nil { 342 | break loop 343 | } 344 | context.matchedArg(arg, token.String()) 345 | context.Next() 346 | } else { 347 | break loop 348 | } 349 | 350 | case TokenEOL: 351 | break loop 352 | } 353 | } 354 | 355 | // Move to innermost default command. 356 | for !ignoreDefault { 357 | if cmd := cmds.defaultSubcommand(); cmd != nil { 358 | cmd.completionAlts = cmds.cmdNames() 359 | context.matchedCmd(cmd) 360 | cmds = cmd.cmdGroup 361 | } else { 362 | break 363 | } 364 | } 365 | 366 | if !context.EOL() { 367 | return fmt.Errorf("unexpected %s", context.Peek()) 368 | } 369 | 370 | // Set defaults for all remaining args. 371 | for arg := context.nextArg(); arg != nil && !arg.consumesRemainder(); arg = context.nextArg() { 372 | for _, defaultValue := range arg.defaultValues { 373 | if err := arg.value.Set(defaultValue); err != nil { 374 | return fmt.Errorf("invalid default value '%s' for argument '%s'", defaultValue, arg.name) 375 | } 376 | } 377 | } 378 | 379 | return 380 | } 381 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Andreas Koch 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /vendor/gopkg.in/alecthomas/kingpin.v2/values.go: -------------------------------------------------------------------------------- 1 | package kingpin 2 | 3 | //go:generate go run ./cmd/genvalues/main.go 4 | 5 | import ( 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "os" 10 | "reflect" 11 | "regexp" 12 | "strings" 13 | "time" 14 | 15 | "github.com/alecthomas/units" 16 | ) 17 | 18 | // NOTE: Most of the base type values were lifted from: 19 | // http://golang.org/src/pkg/flag/flag.go?s=20146:20222 20 | 21 | // Value is the interface to the dynamic value stored in a flag. 22 | // (The default value is represented as a string.) 23 | // 24 | // If a Value has an IsBoolFlag() bool method returning true, the command-line 25 | // parser makes --name equivalent to -name=true rather than using the next 26 | // command-line argument, and adds a --no-name counterpart for negating the 27 | // flag. 28 | type Value interface { 29 | String() string 30 | Set(string) error 31 | } 32 | 33 | // Getter is an interface that allows the contents of a Value to be retrieved. 34 | // It wraps the Value interface, rather than being part of it, because it 35 | // appeared after Go 1 and its compatibility rules. All Value types provided 36 | // by this package satisfy the Getter interface. 37 | type Getter interface { 38 | Value 39 | Get() interface{} 40 | } 41 | 42 | // Optional interface to indicate boolean flags that don't accept a value, and 43 | // implicitly have a --no- negation counterpart. 44 | type boolFlag interface { 45 | Value 46 | IsBoolFlag() bool 47 | } 48 | 49 | // Optional interface for arguments that cumulatively consume all remaining 50 | // input. 51 | type remainderArg interface { 52 | Value 53 | IsCumulative() bool 54 | } 55 | 56 | // Optional interface for flags that can be repeated. 57 | type repeatableFlag interface { 58 | Value 59 | IsCumulative() bool 60 | } 61 | 62 | type accumulator struct { 63 | element func(value interface{}) Value 64 | typ reflect.Type 65 | slice reflect.Value 66 | } 67 | 68 | // Use reflection to accumulate values into a slice. 69 | // 70 | // target := []string{} 71 | // newAccumulator(&target, func (value interface{}) Value { 72 | // return newStringValue(value.(*string)) 73 | // }) 74 | func newAccumulator(slice interface{}, element func(value interface{}) Value) *accumulator { 75 | typ := reflect.TypeOf(slice) 76 | if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Slice { 77 | panic("expected a pointer to a slice") 78 | } 79 | return &accumulator{ 80 | element: element, 81 | typ: typ.Elem().Elem(), 82 | slice: reflect.ValueOf(slice), 83 | } 84 | } 85 | 86 | func (a *accumulator) String() string { 87 | out := []string{} 88 | s := a.slice.Elem() 89 | for i := 0; i < s.Len(); i++ { 90 | out = append(out, a.element(s.Index(i).Addr().Interface()).String()) 91 | } 92 | return strings.Join(out, ",") 93 | } 94 | 95 | func (a *accumulator) Set(value string) error { 96 | e := reflect.New(a.typ) 97 | if err := a.element(e.Interface()).Set(value); err != nil { 98 | return err 99 | } 100 | slice := reflect.Append(a.slice.Elem(), e.Elem()) 101 | a.slice.Elem().Set(slice) 102 | return nil 103 | } 104 | 105 | func (a *accumulator) Get() interface{} { 106 | return a.slice.Interface() 107 | } 108 | 109 | func (a *accumulator) IsCumulative() bool { 110 | return true 111 | } 112 | 113 | func (b *boolValue) IsBoolFlag() bool { return true } 114 | 115 | // -- time.Duration Value 116 | type durationValue time.Duration 117 | 118 | func newDurationValue(p *time.Duration) *durationValue { 119 | return (*durationValue)(p) 120 | } 121 | 122 | func (d *durationValue) Set(s string) error { 123 | v, err := time.ParseDuration(s) 124 | *d = durationValue(v) 125 | return err 126 | } 127 | 128 | func (d *durationValue) Get() interface{} { return time.Duration(*d) } 129 | 130 | func (d *durationValue) String() string { return (*time.Duration)(d).String() } 131 | 132 | // -- map[string]string Value 133 | type stringMapValue map[string]string 134 | 135 | func newStringMapValue(p *map[string]string) *stringMapValue { 136 | return (*stringMapValue)(p) 137 | } 138 | 139 | var stringMapRegex = regexp.MustCompile("[:=]") 140 | 141 | func (s *stringMapValue) Set(value string) error { 142 | parts := stringMapRegex.Split(value, 2) 143 | if len(parts) != 2 { 144 | return fmt.Errorf("expected KEY=VALUE got '%s'", value) 145 | } 146 | (*s)[parts[0]] = parts[1] 147 | return nil 148 | } 149 | 150 | func (s *stringMapValue) Get() interface{} { 151 | return (map[string]string)(*s) 152 | } 153 | 154 | func (s *stringMapValue) String() string { 155 | return fmt.Sprintf("%s", map[string]string(*s)) 156 | } 157 | 158 | func (s *stringMapValue) IsCumulative() bool { 159 | return true 160 | } 161 | 162 | // -- net.IP Value 163 | type ipValue net.IP 164 | 165 | func newIPValue(p *net.IP) *ipValue { 166 | return (*ipValue)(p) 167 | } 168 | 169 | func (i *ipValue) Set(value string) error { 170 | if ip := net.ParseIP(value); ip == nil { 171 | return fmt.Errorf("'%s' is not an IP address", value) 172 | } else { 173 | *i = *(*ipValue)(&ip) 174 | return nil 175 | } 176 | } 177 | 178 | func (i *ipValue) Get() interface{} { 179 | return (net.IP)(*i) 180 | } 181 | 182 | func (i *ipValue) String() string { 183 | return (*net.IP)(i).String() 184 | } 185 | 186 | // -- *net.TCPAddr Value 187 | type tcpAddrValue struct { 188 | addr **net.TCPAddr 189 | } 190 | 191 | func newTCPAddrValue(p **net.TCPAddr) *tcpAddrValue { 192 | return &tcpAddrValue{p} 193 | } 194 | 195 | func (i *tcpAddrValue) Set(value string) error { 196 | if addr, err := net.ResolveTCPAddr("tcp", value); err != nil { 197 | return fmt.Errorf("'%s' is not a valid TCP address: %s", value, err) 198 | } else { 199 | *i.addr = addr 200 | return nil 201 | } 202 | } 203 | 204 | func (t *tcpAddrValue) Get() interface{} { 205 | return (*net.TCPAddr)(*t.addr) 206 | } 207 | 208 | func (i *tcpAddrValue) String() string { 209 | return (*i.addr).String() 210 | } 211 | 212 | // -- existingFile Value 213 | 214 | type fileStatValue struct { 215 | path *string 216 | predicate func(os.FileInfo) error 217 | } 218 | 219 | func newFileStatValue(p *string, predicate func(os.FileInfo) error) *fileStatValue { 220 | return &fileStatValue{ 221 | path: p, 222 | predicate: predicate, 223 | } 224 | } 225 | 226 | func (e *fileStatValue) Set(value string) error { 227 | if s, err := os.Stat(value); os.IsNotExist(err) { 228 | return fmt.Errorf("path '%s' does not exist", value) 229 | } else if err != nil { 230 | return err 231 | } else if err := e.predicate(s); err != nil { 232 | return err 233 | } 234 | *e.path = value 235 | return nil 236 | } 237 | 238 | func (f *fileStatValue) Get() interface{} { 239 | return (string)(*f.path) 240 | } 241 | 242 | func (e *fileStatValue) String() string { 243 | return *e.path 244 | } 245 | 246 | // -- os.File value 247 | 248 | type fileValue struct { 249 | f **os.File 250 | flag int 251 | perm os.FileMode 252 | } 253 | 254 | func newFileValue(p **os.File, flag int, perm os.FileMode) *fileValue { 255 | return &fileValue{p, flag, perm} 256 | } 257 | 258 | func (f *fileValue) Set(value string) error { 259 | if fd, err := os.OpenFile(value, f.flag, f.perm); err != nil { 260 | return err 261 | } else { 262 | *f.f = fd 263 | return nil 264 | } 265 | } 266 | 267 | func (f *fileValue) Get() interface{} { 268 | return (*os.File)(*f.f) 269 | } 270 | 271 | func (f *fileValue) String() string { 272 | if *f.f == nil { 273 | return "" 274 | } 275 | return (*f.f).Name() 276 | } 277 | 278 | // -- url.URL Value 279 | type urlValue struct { 280 | u **url.URL 281 | } 282 | 283 | func newURLValue(p **url.URL) *urlValue { 284 | return &urlValue{p} 285 | } 286 | 287 | func (u *urlValue) Set(value string) error { 288 | if url, err := url.Parse(value); err != nil { 289 | return fmt.Errorf("invalid URL: %s", err) 290 | } else { 291 | *u.u = url 292 | return nil 293 | } 294 | } 295 | 296 | func (u *urlValue) Get() interface{} { 297 | return (*url.URL)(*u.u) 298 | } 299 | 300 | func (u *urlValue) String() string { 301 | if *u.u == nil { 302 | return "" 303 | } 304 | return (*u.u).String() 305 | } 306 | 307 | // -- []*url.URL Value 308 | type urlListValue []*url.URL 309 | 310 | func newURLListValue(p *[]*url.URL) *urlListValue { 311 | return (*urlListValue)(p) 312 | } 313 | 314 | func (u *urlListValue) Set(value string) error { 315 | if url, err := url.Parse(value); err != nil { 316 | return fmt.Errorf("invalid URL: %s", err) 317 | } else { 318 | *u = append(*u, url) 319 | return nil 320 | } 321 | } 322 | 323 | func (u *urlListValue) Get() interface{} { 324 | return ([]*url.URL)(*u) 325 | } 326 | 327 | func (u *urlListValue) String() string { 328 | out := []string{} 329 | for _, url := range *u { 330 | out = append(out, url.String()) 331 | } 332 | return strings.Join(out, ",") 333 | } 334 | 335 | // A flag whose value must be in a set of options. 336 | type enumValue struct { 337 | value *string 338 | options []string 339 | } 340 | 341 | func newEnumFlag(target *string, options ...string) *enumValue { 342 | return &enumValue{ 343 | value: target, 344 | options: options, 345 | } 346 | } 347 | 348 | func (a *enumValue) String() string { 349 | return *a.value 350 | } 351 | 352 | func (a *enumValue) Set(value string) error { 353 | for _, v := range a.options { 354 | if v == value { 355 | *a.value = value 356 | return nil 357 | } 358 | } 359 | return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(a.options, ","), value) 360 | } 361 | 362 | func (e *enumValue) Get() interface{} { 363 | return (string)(*e.value) 364 | } 365 | 366 | // -- []string Enum Value 367 | type enumsValue struct { 368 | value *[]string 369 | options []string 370 | } 371 | 372 | func newEnumsFlag(target *[]string, options ...string) *enumsValue { 373 | return &enumsValue{ 374 | value: target, 375 | options: options, 376 | } 377 | } 378 | 379 | func (s *enumsValue) Set(value string) error { 380 | for _, v := range s.options { 381 | if v == value { 382 | *s.value = append(*s.value, value) 383 | return nil 384 | } 385 | } 386 | return fmt.Errorf("enum value must be one of %s, got '%s'", strings.Join(s.options, ","), value) 387 | } 388 | 389 | func (e *enumsValue) Get() interface{} { 390 | return ([]string)(*e.value) 391 | } 392 | 393 | func (s *enumsValue) String() string { 394 | return strings.Join(*s.value, ",") 395 | } 396 | 397 | func (s *enumsValue) IsCumulative() bool { 398 | return true 399 | } 400 | 401 | // -- units.Base2Bytes Value 402 | type bytesValue units.Base2Bytes 403 | 404 | func newBytesValue(p *units.Base2Bytes) *bytesValue { 405 | return (*bytesValue)(p) 406 | } 407 | 408 | func (d *bytesValue) Set(s string) error { 409 | v, err := units.ParseBase2Bytes(s) 410 | *d = bytesValue(v) 411 | return err 412 | } 413 | 414 | func (d *bytesValue) Get() interface{} { return units.Base2Bytes(*d) } 415 | 416 | func (d *bytesValue) String() string { return (*units.Base2Bytes)(d).String() } 417 | 418 | func newExistingFileValue(target *string) *fileStatValue { 419 | return newFileStatValue(target, func(s os.FileInfo) error { 420 | if s.IsDir() { 421 | return fmt.Errorf("'%s' is a directory", s.Name()) 422 | } 423 | return nil 424 | }) 425 | } 426 | 427 | func newExistingDirValue(target *string) *fileStatValue { 428 | return newFileStatValue(target, func(s os.FileInfo) error { 429 | if !s.IsDir() { 430 | return fmt.Errorf("'%s' is a file", s.Name()) 431 | } 432 | return nil 433 | }) 434 | } 435 | 436 | func newExistingFileOrDirValue(target *string) *fileStatValue { 437 | return newFileStatValue(target, func(s os.FileInfo) error { return nil }) 438 | } 439 | 440 | type counterValue int 441 | 442 | func newCounterValue(n *int) *counterValue { 443 | return (*counterValue)(n) 444 | } 445 | 446 | func (c *counterValue) Set(s string) error { 447 | *c++ 448 | return nil 449 | } 450 | 451 | func (c *counterValue) Get() interface{} { return (int)(*c) } 452 | func (c *counterValue) IsBoolFlag() bool { return true } 453 | func (c *counterValue) String() string { return fmt.Sprintf("%d", *c) } 454 | func (c *counterValue) IsCumulative() bool { return true } 455 | 456 | func resolveHost(value string) (net.IP, error) { 457 | if ip := net.ParseIP(value); ip != nil { 458 | return ip, nil 459 | } else { 460 | if addr, err := net.ResolveIPAddr("ip", value); err != nil { 461 | return nil, err 462 | } else { 463 | return addr.IP, nil 464 | } 465 | } 466 | } 467 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/template/parse/lex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package parse 6 | 7 | import ( 8 | "fmt" 9 | "strings" 10 | "unicode" 11 | "unicode/utf8" 12 | ) 13 | 14 | // item represents a token or text string returned from the scanner. 15 | type item struct { 16 | typ itemType // The type of this item. 17 | pos Pos // The starting position, in bytes, of this item in the input string. 18 | val string // The value of this item. 19 | } 20 | 21 | func (i item) String() string { 22 | switch { 23 | case i.typ == itemEOF: 24 | return "EOF" 25 | case i.typ == itemError: 26 | return i.val 27 | case i.typ > itemKeyword: 28 | return fmt.Sprintf("<%s>", i.val) 29 | case len(i.val) > 10: 30 | return fmt.Sprintf("%.10q...", i.val) 31 | } 32 | return fmt.Sprintf("%q", i.val) 33 | } 34 | 35 | // itemType identifies the type of lex items. 36 | type itemType int 37 | 38 | const ( 39 | itemError itemType = iota // error occurred; value is text of error 40 | itemBool // boolean constant 41 | itemChar // printable ASCII character; grab bag for comma etc. 42 | itemCharConstant // character constant 43 | itemComplex // complex constant (1+2i); imaginary is just a number 44 | itemColonEquals // colon-equals (':=') introducing a declaration 45 | itemEOF 46 | itemField // alphanumeric identifier starting with '.' 47 | itemIdentifier // alphanumeric identifier not starting with '.' 48 | itemLeftDelim // left action delimiter 49 | itemLeftParen // '(' inside action 50 | itemNumber // simple number, including imaginary 51 | itemPipe // pipe symbol 52 | itemRawString // raw quoted string (includes quotes) 53 | itemRightDelim // right action delimiter 54 | itemElideNewline // elide newline after right delim 55 | itemRightParen // ')' inside action 56 | itemSpace // run of spaces separating arguments 57 | itemString // quoted string (includes quotes) 58 | itemText // plain text 59 | itemVariable // variable starting with '$', such as '$' or '$1' or '$hello' 60 | // Keywords appear after all the rest. 61 | itemKeyword // used only to delimit the keywords 62 | itemDot // the cursor, spelled '.' 63 | itemDefine // define keyword 64 | itemElse // else keyword 65 | itemEnd // end keyword 66 | itemIf // if keyword 67 | itemNil // the untyped nil constant, easiest to treat as a keyword 68 | itemRange // range keyword 69 | itemTemplate // template keyword 70 | itemWith // with keyword 71 | ) 72 | 73 | var key = map[string]itemType{ 74 | ".": itemDot, 75 | "define": itemDefine, 76 | "else": itemElse, 77 | "end": itemEnd, 78 | "if": itemIf, 79 | "range": itemRange, 80 | "nil": itemNil, 81 | "template": itemTemplate, 82 | "with": itemWith, 83 | } 84 | 85 | const eof = -1 86 | 87 | // stateFn represents the state of the scanner as a function that returns the next state. 88 | type stateFn func(*lexer) stateFn 89 | 90 | // lexer holds the state of the scanner. 91 | type lexer struct { 92 | name string // the name of the input; used only for error reports 93 | input string // the string being scanned 94 | leftDelim string // start of action 95 | rightDelim string // end of action 96 | state stateFn // the next lexing function to enter 97 | pos Pos // current position in the input 98 | start Pos // start position of this item 99 | width Pos // width of last rune read from input 100 | lastPos Pos // position of most recent item returned by nextItem 101 | items chan item // channel of scanned items 102 | parenDepth int // nesting depth of ( ) exprs 103 | } 104 | 105 | // next returns the next rune in the input. 106 | func (l *lexer) next() rune { 107 | if int(l.pos) >= len(l.input) { 108 | l.width = 0 109 | return eof 110 | } 111 | r, w := utf8.DecodeRuneInString(l.input[l.pos:]) 112 | l.width = Pos(w) 113 | l.pos += l.width 114 | return r 115 | } 116 | 117 | // peek returns but does not consume the next rune in the input. 118 | func (l *lexer) peek() rune { 119 | r := l.next() 120 | l.backup() 121 | return r 122 | } 123 | 124 | // backup steps back one rune. Can only be called once per call of next. 125 | func (l *lexer) backup() { 126 | l.pos -= l.width 127 | } 128 | 129 | // emit passes an item back to the client. 130 | func (l *lexer) emit(t itemType) { 131 | l.items <- item{t, l.start, l.input[l.start:l.pos]} 132 | l.start = l.pos 133 | } 134 | 135 | // ignore skips over the pending input before this point. 136 | func (l *lexer) ignore() { 137 | l.start = l.pos 138 | } 139 | 140 | // accept consumes the next rune if it's from the valid set. 141 | func (l *lexer) accept(valid string) bool { 142 | if strings.IndexRune(valid, l.next()) >= 0 { 143 | return true 144 | } 145 | l.backup() 146 | return false 147 | } 148 | 149 | // acceptRun consumes a run of runes from the valid set. 150 | func (l *lexer) acceptRun(valid string) { 151 | for strings.IndexRune(valid, l.next()) >= 0 { 152 | } 153 | l.backup() 154 | } 155 | 156 | // lineNumber reports which line we're on, based on the position of 157 | // the previous item returned by nextItem. Doing it this way 158 | // means we don't have to worry about peek double counting. 159 | func (l *lexer) lineNumber() int { 160 | return 1 + strings.Count(l.input[:l.lastPos], "\n") 161 | } 162 | 163 | // errorf returns an error token and terminates the scan by passing 164 | // back a nil pointer that will be the next state, terminating l.nextItem. 165 | func (l *lexer) errorf(format string, args ...interface{}) stateFn { 166 | l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)} 167 | return nil 168 | } 169 | 170 | // nextItem returns the next item from the input. 171 | func (l *lexer) nextItem() item { 172 | item := <-l.items 173 | l.lastPos = item.pos 174 | return item 175 | } 176 | 177 | // lex creates a new scanner for the input string. 178 | func lex(name, input, left, right string) *lexer { 179 | if left == "" { 180 | left = leftDelim 181 | } 182 | if right == "" { 183 | right = rightDelim 184 | } 185 | l := &lexer{ 186 | name: name, 187 | input: input, 188 | leftDelim: left, 189 | rightDelim: right, 190 | items: make(chan item), 191 | } 192 | go l.run() 193 | return l 194 | } 195 | 196 | // run runs the state machine for the lexer. 197 | func (l *lexer) run() { 198 | for l.state = lexText; l.state != nil; { 199 | l.state = l.state(l) 200 | } 201 | } 202 | 203 | // state functions 204 | 205 | const ( 206 | leftDelim = "{{" 207 | rightDelim = "}}" 208 | leftComment = "/*" 209 | rightComment = "*/" 210 | ) 211 | 212 | // lexText scans until an opening action delimiter, "{{". 213 | func lexText(l *lexer) stateFn { 214 | for { 215 | if strings.HasPrefix(l.input[l.pos:], l.leftDelim) { 216 | if l.pos > l.start { 217 | l.emit(itemText) 218 | } 219 | return lexLeftDelim 220 | } 221 | if l.next() == eof { 222 | break 223 | } 224 | } 225 | // Correctly reached EOF. 226 | if l.pos > l.start { 227 | l.emit(itemText) 228 | } 229 | l.emit(itemEOF) 230 | return nil 231 | } 232 | 233 | // lexLeftDelim scans the left delimiter, which is known to be present. 234 | func lexLeftDelim(l *lexer) stateFn { 235 | l.pos += Pos(len(l.leftDelim)) 236 | if strings.HasPrefix(l.input[l.pos:], leftComment) { 237 | return lexComment 238 | } 239 | l.emit(itemLeftDelim) 240 | l.parenDepth = 0 241 | return lexInsideAction 242 | } 243 | 244 | // lexComment scans a comment. The left comment marker is known to be present. 245 | func lexComment(l *lexer) stateFn { 246 | l.pos += Pos(len(leftComment)) 247 | i := strings.Index(l.input[l.pos:], rightComment) 248 | if i < 0 { 249 | return l.errorf("unclosed comment") 250 | } 251 | l.pos += Pos(i + len(rightComment)) 252 | if !strings.HasPrefix(l.input[l.pos:], l.rightDelim) { 253 | return l.errorf("comment ends before closing delimiter") 254 | 255 | } 256 | l.pos += Pos(len(l.rightDelim)) 257 | l.ignore() 258 | return lexText 259 | } 260 | 261 | // lexRightDelim scans the right delimiter, which is known to be present. 262 | func lexRightDelim(l *lexer) stateFn { 263 | l.pos += Pos(len(l.rightDelim)) 264 | l.emit(itemRightDelim) 265 | if l.peek() == '\\' { 266 | l.pos++ 267 | l.emit(itemElideNewline) 268 | } 269 | return lexText 270 | } 271 | 272 | // lexInsideAction scans the elements inside action delimiters. 273 | func lexInsideAction(l *lexer) stateFn { 274 | // Either number, quoted string, or identifier. 275 | // Spaces separate arguments; runs of spaces turn into itemSpace. 276 | // Pipe symbols separate and are emitted. 277 | if strings.HasPrefix(l.input[l.pos:], l.rightDelim+"\\") || strings.HasPrefix(l.input[l.pos:], l.rightDelim) { 278 | if l.parenDepth == 0 { 279 | return lexRightDelim 280 | } 281 | return l.errorf("unclosed left paren") 282 | } 283 | switch r := l.next(); { 284 | case r == eof || isEndOfLine(r): 285 | return l.errorf("unclosed action") 286 | case isSpace(r): 287 | return lexSpace 288 | case r == ':': 289 | if l.next() != '=' { 290 | return l.errorf("expected :=") 291 | } 292 | l.emit(itemColonEquals) 293 | case r == '|': 294 | l.emit(itemPipe) 295 | case r == '"': 296 | return lexQuote 297 | case r == '`': 298 | return lexRawQuote 299 | case r == '$': 300 | return lexVariable 301 | case r == '\'': 302 | return lexChar 303 | case r == '.': 304 | // special look-ahead for ".field" so we don't break l.backup(). 305 | if l.pos < Pos(len(l.input)) { 306 | r := l.input[l.pos] 307 | if r < '0' || '9' < r { 308 | return lexField 309 | } 310 | } 311 | fallthrough // '.' can start a number. 312 | case r == '+' || r == '-' || ('0' <= r && r <= '9'): 313 | l.backup() 314 | return lexNumber 315 | case isAlphaNumeric(r): 316 | l.backup() 317 | return lexIdentifier 318 | case r == '(': 319 | l.emit(itemLeftParen) 320 | l.parenDepth++ 321 | return lexInsideAction 322 | case r == ')': 323 | l.emit(itemRightParen) 324 | l.parenDepth-- 325 | if l.parenDepth < 0 { 326 | return l.errorf("unexpected right paren %#U", r) 327 | } 328 | return lexInsideAction 329 | case r <= unicode.MaxASCII && unicode.IsPrint(r): 330 | l.emit(itemChar) 331 | return lexInsideAction 332 | default: 333 | return l.errorf("unrecognized character in action: %#U", r) 334 | } 335 | return lexInsideAction 336 | } 337 | 338 | // lexSpace scans a run of space characters. 339 | // One space has already been seen. 340 | func lexSpace(l *lexer) stateFn { 341 | for isSpace(l.peek()) { 342 | l.next() 343 | } 344 | l.emit(itemSpace) 345 | return lexInsideAction 346 | } 347 | 348 | // lexIdentifier scans an alphanumeric. 349 | func lexIdentifier(l *lexer) stateFn { 350 | Loop: 351 | for { 352 | switch r := l.next(); { 353 | case isAlphaNumeric(r): 354 | // absorb. 355 | default: 356 | l.backup() 357 | word := l.input[l.start:l.pos] 358 | if !l.atTerminator() { 359 | return l.errorf("bad character %#U", r) 360 | } 361 | switch { 362 | case key[word] > itemKeyword: 363 | l.emit(key[word]) 364 | case word[0] == '.': 365 | l.emit(itemField) 366 | case word == "true", word == "false": 367 | l.emit(itemBool) 368 | default: 369 | l.emit(itemIdentifier) 370 | } 371 | break Loop 372 | } 373 | } 374 | return lexInsideAction 375 | } 376 | 377 | // lexField scans a field: .Alphanumeric. 378 | // The . has been scanned. 379 | func lexField(l *lexer) stateFn { 380 | return lexFieldOrVariable(l, itemField) 381 | } 382 | 383 | // lexVariable scans a Variable: $Alphanumeric. 384 | // The $ has been scanned. 385 | func lexVariable(l *lexer) stateFn { 386 | if l.atTerminator() { // Nothing interesting follows -> "$". 387 | l.emit(itemVariable) 388 | return lexInsideAction 389 | } 390 | return lexFieldOrVariable(l, itemVariable) 391 | } 392 | 393 | // lexVariable scans a field or variable: [.$]Alphanumeric. 394 | // The . or $ has been scanned. 395 | func lexFieldOrVariable(l *lexer, typ itemType) stateFn { 396 | if l.atTerminator() { // Nothing interesting follows -> "." or "$". 397 | if typ == itemVariable { 398 | l.emit(itemVariable) 399 | } else { 400 | l.emit(itemDot) 401 | } 402 | return lexInsideAction 403 | } 404 | var r rune 405 | for { 406 | r = l.next() 407 | if !isAlphaNumeric(r) { 408 | l.backup() 409 | break 410 | } 411 | } 412 | if !l.atTerminator() { 413 | return l.errorf("bad character %#U", r) 414 | } 415 | l.emit(typ) 416 | return lexInsideAction 417 | } 418 | 419 | // atTerminator reports whether the input is at valid termination character to 420 | // appear after an identifier. Breaks .X.Y into two pieces. Also catches cases 421 | // like "$x+2" not being acceptable without a space, in case we decide one 422 | // day to implement arithmetic. 423 | func (l *lexer) atTerminator() bool { 424 | r := l.peek() 425 | if isSpace(r) || isEndOfLine(r) { 426 | return true 427 | } 428 | switch r { 429 | case eof, '.', ',', '|', ':', ')', '(': 430 | return true 431 | } 432 | // Does r start the delimiter? This can be ambiguous (with delim=="//", $x/2 will 433 | // succeed but should fail) but only in extremely rare cases caused by willfully 434 | // bad choice of delimiter. 435 | if rd, _ := utf8.DecodeRuneInString(l.rightDelim); rd == r { 436 | return true 437 | } 438 | return false 439 | } 440 | 441 | // lexChar scans a character constant. The initial quote is already 442 | // scanned. Syntax checking is done by the parser. 443 | func lexChar(l *lexer) stateFn { 444 | Loop: 445 | for { 446 | switch l.next() { 447 | case '\\': 448 | if r := l.next(); r != eof && r != '\n' { 449 | break 450 | } 451 | fallthrough 452 | case eof, '\n': 453 | return l.errorf("unterminated character constant") 454 | case '\'': 455 | break Loop 456 | } 457 | } 458 | l.emit(itemCharConstant) 459 | return lexInsideAction 460 | } 461 | 462 | // lexNumber scans a number: decimal, octal, hex, float, or imaginary. This 463 | // isn't a perfect number scanner - for instance it accepts "." and "0x0.2" 464 | // and "089" - but when it's wrong the input is invalid and the parser (via 465 | // strconv) will notice. 466 | func lexNumber(l *lexer) stateFn { 467 | if !l.scanNumber() { 468 | return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) 469 | } 470 | if sign := l.peek(); sign == '+' || sign == '-' { 471 | // Complex: 1+2i. No spaces, must end in 'i'. 472 | if !l.scanNumber() || l.input[l.pos-1] != 'i' { 473 | return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) 474 | } 475 | l.emit(itemComplex) 476 | } else { 477 | l.emit(itemNumber) 478 | } 479 | return lexInsideAction 480 | } 481 | 482 | func (l *lexer) scanNumber() bool { 483 | // Optional leading sign. 484 | l.accept("+-") 485 | // Is it hex? 486 | digits := "0123456789" 487 | if l.accept("0") && l.accept("xX") { 488 | digits = "0123456789abcdefABCDEF" 489 | } 490 | l.acceptRun(digits) 491 | if l.accept(".") { 492 | l.acceptRun(digits) 493 | } 494 | if l.accept("eE") { 495 | l.accept("+-") 496 | l.acceptRun("0123456789") 497 | } 498 | // Is it imaginary? 499 | l.accept("i") 500 | // Next thing mustn't be alphanumeric. 501 | if isAlphaNumeric(l.peek()) { 502 | l.next() 503 | return false 504 | } 505 | return true 506 | } 507 | 508 | // lexQuote scans a quoted string. 509 | func lexQuote(l *lexer) stateFn { 510 | Loop: 511 | for { 512 | switch l.next() { 513 | case '\\': 514 | if r := l.next(); r != eof && r != '\n' { 515 | break 516 | } 517 | fallthrough 518 | case eof, '\n': 519 | return l.errorf("unterminated quoted string") 520 | case '"': 521 | break Loop 522 | } 523 | } 524 | l.emit(itemString) 525 | return lexInsideAction 526 | } 527 | 528 | // lexRawQuote scans a raw quoted string. 529 | func lexRawQuote(l *lexer) stateFn { 530 | Loop: 531 | for { 532 | switch l.next() { 533 | case eof, '\n': 534 | return l.errorf("unterminated raw quoted string") 535 | case '`': 536 | break Loop 537 | } 538 | } 539 | l.emit(itemRawString) 540 | return lexInsideAction 541 | } 542 | 543 | // isSpace reports whether r is a space character. 544 | func isSpace(r rune) bool { 545 | return r == ' ' || r == '\t' 546 | } 547 | 548 | // isEndOfLine reports whether r is an end-of-line character. 549 | func isEndOfLine(r rune) bool { 550 | return r == '\r' || r == '\n' 551 | } 552 | 553 | // isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. 554 | func isAlphaNumeric(r rune) bool { 555 | return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) 556 | } 557 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/template/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package template implements data-driven templates for generating textual output. 7 | 8 | To generate HTML output, see package html/template, which has the same interface 9 | as this package but automatically secures HTML output against certain attacks. 10 | 11 | Templates are executed by applying them to a data structure. Annotations in the 12 | template refer to elements of the data structure (typically a field of a struct 13 | or a key in a map) to control execution and derive values to be displayed. 14 | Execution of the template walks the structure and sets the cursor, represented 15 | by a period '.' and called "dot", to the value at the current location in the 16 | structure as execution proceeds. 17 | 18 | The input text for a template is UTF-8-encoded text in any format. 19 | "Actions"--data evaluations or control structures--are delimited by 20 | "{{" and "}}"; all text outside actions is copied to the output unchanged. 21 | Actions may not span newlines, although comments can. 22 | 23 | Once parsed, a template may be executed safely in parallel. 24 | 25 | Here is a trivial example that prints "17 items are made of wool". 26 | 27 | type Inventory struct { 28 | Material string 29 | Count uint 30 | } 31 | sweaters := Inventory{"wool", 17} 32 | tmpl, err := template.New("test").Parse("{{.Count}} items are made of {{.Material}}") 33 | if err != nil { panic(err) } 34 | err = tmpl.Execute(os.Stdout, sweaters) 35 | if err != nil { panic(err) } 36 | 37 | More intricate examples appear below. 38 | 39 | Actions 40 | 41 | Here is the list of actions. "Arguments" and "pipelines" are evaluations of 42 | data, defined in detail below. 43 | 44 | */ 45 | // {{/* a comment */}} 46 | // A comment; discarded. May contain newlines. 47 | // Comments do not nest and must start and end at the 48 | // delimiters, as shown here. 49 | /* 50 | 51 | {{pipeline}} 52 | The default textual representation of the value of the pipeline 53 | is copied to the output. 54 | 55 | {{if pipeline}} T1 {{end}} 56 | If the value of the pipeline is empty, no output is generated; 57 | otherwise, T1 is executed. The empty values are false, 0, any 58 | nil pointer or interface value, and any array, slice, map, or 59 | string of length zero. 60 | Dot is unaffected. 61 | 62 | {{if pipeline}} T1 {{else}} T0 {{end}} 63 | If the value of the pipeline is empty, T0 is executed; 64 | otherwise, T1 is executed. Dot is unaffected. 65 | 66 | {{if pipeline}} T1 {{else if pipeline}} T0 {{end}} 67 | To simplify the appearance of if-else chains, the else action 68 | of an if may include another if directly; the effect is exactly 69 | the same as writing 70 | {{if pipeline}} T1 {{else}}{{if pipeline}} T0 {{end}}{{end}} 71 | 72 | {{range pipeline}} T1 {{end}} 73 | The value of the pipeline must be an array, slice, map, or channel. 74 | If the value of the pipeline has length zero, nothing is output; 75 | otherwise, dot is set to the successive elements of the array, 76 | slice, or map and T1 is executed. If the value is a map and the 77 | keys are of basic type with a defined order ("comparable"), the 78 | elements will be visited in sorted key order. 79 | 80 | {{range pipeline}} T1 {{else}} T0 {{end}} 81 | The value of the pipeline must be an array, slice, map, or channel. 82 | If the value of the pipeline has length zero, dot is unaffected and 83 | T0 is executed; otherwise, dot is set to the successive elements 84 | of the array, slice, or map and T1 is executed. 85 | 86 | {{template "name"}} 87 | The template with the specified name is executed with nil data. 88 | 89 | {{template "name" pipeline}} 90 | The template with the specified name is executed with dot set 91 | to the value of the pipeline. 92 | 93 | {{with pipeline}} T1 {{end}} 94 | If the value of the pipeline is empty, no output is generated; 95 | otherwise, dot is set to the value of the pipeline and T1 is 96 | executed. 97 | 98 | {{with pipeline}} T1 {{else}} T0 {{end}} 99 | If the value of the pipeline is empty, dot is unaffected and T0 100 | is executed; otherwise, dot is set to the value of the pipeline 101 | and T1 is executed. 102 | 103 | Arguments 104 | 105 | An argument is a simple value, denoted by one of the following. 106 | 107 | - A boolean, string, character, integer, floating-point, imaginary 108 | or complex constant in Go syntax. These behave like Go's untyped 109 | constants, although raw strings may not span newlines. 110 | - The keyword nil, representing an untyped Go nil. 111 | - The character '.' (period): 112 | . 113 | The result is the value of dot. 114 | - A variable name, which is a (possibly empty) alphanumeric string 115 | preceded by a dollar sign, such as 116 | $piOver2 117 | or 118 | $ 119 | The result is the value of the variable. 120 | Variables are described below. 121 | - The name of a field of the data, which must be a struct, preceded 122 | by a period, such as 123 | .Field 124 | The result is the value of the field. Field invocations may be 125 | chained: 126 | .Field1.Field2 127 | Fields can also be evaluated on variables, including chaining: 128 | $x.Field1.Field2 129 | - The name of a key of the data, which must be a map, preceded 130 | by a period, such as 131 | .Key 132 | The result is the map element value indexed by the key. 133 | Key invocations may be chained and combined with fields to any 134 | depth: 135 | .Field1.Key1.Field2.Key2 136 | Although the key must be an alphanumeric identifier, unlike with 137 | field names they do not need to start with an upper case letter. 138 | Keys can also be evaluated on variables, including chaining: 139 | $x.key1.key2 140 | - The name of a niladic method of the data, preceded by a period, 141 | such as 142 | .Method 143 | The result is the value of invoking the method with dot as the 144 | receiver, dot.Method(). Such a method must have one return value (of 145 | any type) or two return values, the second of which is an error. 146 | If it has two and the returned error is non-nil, execution terminates 147 | and an error is returned to the caller as the value of Execute. 148 | Method invocations may be chained and combined with fields and keys 149 | to any depth: 150 | .Field1.Key1.Method1.Field2.Key2.Method2 151 | Methods can also be evaluated on variables, including chaining: 152 | $x.Method1.Field 153 | - The name of a niladic function, such as 154 | fun 155 | The result is the value of invoking the function, fun(). The return 156 | types and values behave as in methods. Functions and function 157 | names are described below. 158 | - A parenthesized instance of one the above, for grouping. The result 159 | may be accessed by a field or map key invocation. 160 | print (.F1 arg1) (.F2 arg2) 161 | (.StructValuedMethod "arg").Field 162 | 163 | Arguments may evaluate to any type; if they are pointers the implementation 164 | automatically indirects to the base type when required. 165 | If an evaluation yields a function value, such as a function-valued 166 | field of a struct, the function is not invoked automatically, but it 167 | can be used as a truth value for an if action and the like. To invoke 168 | it, use the call function, defined below. 169 | 170 | A pipeline is a possibly chained sequence of "commands". A command is a simple 171 | value (argument) or a function or method call, possibly with multiple arguments: 172 | 173 | Argument 174 | The result is the value of evaluating the argument. 175 | .Method [Argument...] 176 | The method can be alone or the last element of a chain but, 177 | unlike methods in the middle of a chain, it can take arguments. 178 | The result is the value of calling the method with the 179 | arguments: 180 | dot.Method(Argument1, etc.) 181 | functionName [Argument...] 182 | The result is the value of calling the function associated 183 | with the name: 184 | function(Argument1, etc.) 185 | Functions and function names are described below. 186 | 187 | Pipelines 188 | 189 | A pipeline may be "chained" by separating a sequence of commands with pipeline 190 | characters '|'. In a chained pipeline, the result of the each command is 191 | passed as the last argument of the following command. The output of the final 192 | command in the pipeline is the value of the pipeline. 193 | 194 | The output of a command will be either one value or two values, the second of 195 | which has type error. If that second value is present and evaluates to 196 | non-nil, execution terminates and the error is returned to the caller of 197 | Execute. 198 | 199 | Variables 200 | 201 | A pipeline inside an action may initialize a variable to capture the result. 202 | The initialization has syntax 203 | 204 | $variable := pipeline 205 | 206 | where $variable is the name of the variable. An action that declares a 207 | variable produces no output. 208 | 209 | If a "range" action initializes a variable, the variable is set to the 210 | successive elements of the iteration. Also, a "range" may declare two 211 | variables, separated by a comma: 212 | 213 | range $index, $element := pipeline 214 | 215 | in which case $index and $element are set to the successive values of the 216 | array/slice index or map key and element, respectively. Note that if there is 217 | only one variable, it is assigned the element; this is opposite to the 218 | convention in Go range clauses. 219 | 220 | A variable's scope extends to the "end" action of the control structure ("if", 221 | "with", or "range") in which it is declared, or to the end of the template if 222 | there is no such control structure. A template invocation does not inherit 223 | variables from the point of its invocation. 224 | 225 | When execution begins, $ is set to the data argument passed to Execute, that is, 226 | to the starting value of dot. 227 | 228 | Examples 229 | 230 | Here are some example one-line templates demonstrating pipelines and variables. 231 | All produce the quoted word "output": 232 | 233 | {{"\"output\""}} 234 | A string constant. 235 | {{`"output"`}} 236 | A raw string constant. 237 | {{printf "%q" "output"}} 238 | A function call. 239 | {{"output" | printf "%q"}} 240 | A function call whose final argument comes from the previous 241 | command. 242 | {{printf "%q" (print "out" "put")}} 243 | A parenthesized argument. 244 | {{"put" | printf "%s%s" "out" | printf "%q"}} 245 | A more elaborate call. 246 | {{"output" | printf "%s" | printf "%q"}} 247 | A longer chain. 248 | {{with "output"}}{{printf "%q" .}}{{end}} 249 | A with action using dot. 250 | {{with $x := "output" | printf "%q"}}{{$x}}{{end}} 251 | A with action that creates and uses a variable. 252 | {{with $x := "output"}}{{printf "%q" $x}}{{end}} 253 | A with action that uses the variable in another action. 254 | {{with $x := "output"}}{{$x | printf "%q"}}{{end}} 255 | The same, but pipelined. 256 | 257 | Functions 258 | 259 | During execution functions are found in two function maps: first in the 260 | template, then in the global function map. By default, no functions are defined 261 | in the template but the Funcs method can be used to add them. 262 | 263 | Predefined global functions are named as follows. 264 | 265 | and 266 | Returns the boolean AND of its arguments by returning the 267 | first empty argument or the last argument, that is, 268 | "and x y" behaves as "if x then y else x". All the 269 | arguments are evaluated. 270 | call 271 | Returns the result of calling the first argument, which 272 | must be a function, with the remaining arguments as parameters. 273 | Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where 274 | Y is a func-valued field, map entry, or the like. 275 | The first argument must be the result of an evaluation 276 | that yields a value of function type (as distinct from 277 | a predefined function such as print). The function must 278 | return either one or two result values, the second of which 279 | is of type error. If the arguments don't match the function 280 | or the returned error value is non-nil, execution stops. 281 | html 282 | Returns the escaped HTML equivalent of the textual 283 | representation of its arguments. 284 | index 285 | Returns the result of indexing its first argument by the 286 | following arguments. Thus "index x 1 2 3" is, in Go syntax, 287 | x[1][2][3]. Each indexed item must be a map, slice, or array. 288 | js 289 | Returns the escaped JavaScript equivalent of the textual 290 | representation of its arguments. 291 | len 292 | Returns the integer length of its argument. 293 | not 294 | Returns the boolean negation of its single argument. 295 | or 296 | Returns the boolean OR of its arguments by returning the 297 | first non-empty argument or the last argument, that is, 298 | "or x y" behaves as "if x then x else y". All the 299 | arguments are evaluated. 300 | print 301 | An alias for fmt.Sprint 302 | printf 303 | An alias for fmt.Sprintf 304 | println 305 | An alias for fmt.Sprintln 306 | urlquery 307 | Returns the escaped value of the textual representation of 308 | its arguments in a form suitable for embedding in a URL query. 309 | 310 | The boolean functions take any zero value to be false and a non-zero 311 | value to be true. 312 | 313 | There is also a set of binary comparison operators defined as 314 | functions: 315 | 316 | eq 317 | Returns the boolean truth of arg1 == arg2 318 | ne 319 | Returns the boolean truth of arg1 != arg2 320 | lt 321 | Returns the boolean truth of arg1 < arg2 322 | le 323 | Returns the boolean truth of arg1 <= arg2 324 | gt 325 | Returns the boolean truth of arg1 > arg2 326 | ge 327 | Returns the boolean truth of arg1 >= arg2 328 | 329 | For simpler multi-way equality tests, eq (only) accepts two or more 330 | arguments and compares the second and subsequent to the first, 331 | returning in effect 332 | 333 | arg1==arg2 || arg1==arg3 || arg1==arg4 ... 334 | 335 | (Unlike with || in Go, however, eq is a function call and all the 336 | arguments will be evaluated.) 337 | 338 | The comparison functions work on basic types only (or named basic 339 | types, such as "type Celsius float32"). They implement the Go rules 340 | for comparison of values, except that size and exact type are 341 | ignored, so any integer value, signed or unsigned, may be compared 342 | with any other integer value. (The arithmetic value is compared, 343 | not the bit pattern, so all negative integers are less than all 344 | unsigned integers.) However, as usual, one may not compare an int 345 | with a float32 and so on. 346 | 347 | Associated templates 348 | 349 | Each template is named by a string specified when it is created. Also, each 350 | template is associated with zero or more other templates that it may invoke by 351 | name; such associations are transitive and form a name space of templates. 352 | 353 | A template may use a template invocation to instantiate another associated 354 | template; see the explanation of the "template" action above. The name must be 355 | that of a template associated with the template that contains the invocation. 356 | 357 | Nested template definitions 358 | 359 | When parsing a template, another template may be defined and associated with the 360 | template being parsed. Template definitions must appear at the top level of the 361 | template, much like global variables in a Go program. 362 | 363 | The syntax of such definitions is to surround each template declaration with a 364 | "define" and "end" action. 365 | 366 | The define action names the template being created by providing a string 367 | constant. Here is a simple example: 368 | 369 | `{{define "T1"}}ONE{{end}} 370 | {{define "T2"}}TWO{{end}} 371 | {{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}} 372 | {{template "T3"}}` 373 | 374 | This defines two templates, T1 and T2, and a third T3 that invokes the other two 375 | when it is executed. Finally it invokes T3. If executed this template will 376 | produce the text 377 | 378 | ONE TWO 379 | 380 | By construction, a template may reside in only one association. If it's 381 | necessary to have a template addressable from multiple associations, the 382 | template definition must be parsed multiple times to create distinct *Template 383 | values, or must be copied with the Clone or AddParseTree method. 384 | 385 | Parse may be called multiple times to assemble the various associated templates; 386 | see the ParseFiles and ParseGlob functions and methods for simple ways to parse 387 | related templates stored in files. 388 | 389 | A template may be executed directly or through ExecuteTemplate, which executes 390 | an associated template identified by name. To invoke our example above, we 391 | might write, 392 | 393 | err := tmpl.Execute(os.Stdout, "no data needed") 394 | if err != nil { 395 | log.Fatalf("execution failed: %s", err) 396 | } 397 | 398 | or to invoke a particular template explicitly by name, 399 | 400 | err := tmpl.ExecuteTemplate(os.Stdout, "T2", "no data needed") 401 | if err != nil { 402 | log.Fatalf("execution failed: %s", err) 403 | } 404 | 405 | */ 406 | package template 407 | -------------------------------------------------------------------------------- /vendor/github.com/alecthomas/template/funcs.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package template 6 | 7 | import ( 8 | "bytes" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "net/url" 13 | "reflect" 14 | "strings" 15 | "unicode" 16 | "unicode/utf8" 17 | ) 18 | 19 | // FuncMap is the type of the map defining the mapping from names to functions. 20 | // Each function must have either a single return value, or two return values of 21 | // which the second has type error. In that case, if the second (error) 22 | // return value evaluates to non-nil during execution, execution terminates and 23 | // Execute returns that error. 24 | type FuncMap map[string]interface{} 25 | 26 | var builtins = FuncMap{ 27 | "and": and, 28 | "call": call, 29 | "html": HTMLEscaper, 30 | "index": index, 31 | "js": JSEscaper, 32 | "len": length, 33 | "not": not, 34 | "or": or, 35 | "print": fmt.Sprint, 36 | "printf": fmt.Sprintf, 37 | "println": fmt.Sprintln, 38 | "urlquery": URLQueryEscaper, 39 | 40 | // Comparisons 41 | "eq": eq, // == 42 | "ge": ge, // >= 43 | "gt": gt, // > 44 | "le": le, // <= 45 | "lt": lt, // < 46 | "ne": ne, // != 47 | } 48 | 49 | var builtinFuncs = createValueFuncs(builtins) 50 | 51 | // createValueFuncs turns a FuncMap into a map[string]reflect.Value 52 | func createValueFuncs(funcMap FuncMap) map[string]reflect.Value { 53 | m := make(map[string]reflect.Value) 54 | addValueFuncs(m, funcMap) 55 | return m 56 | } 57 | 58 | // addValueFuncs adds to values the functions in funcs, converting them to reflect.Values. 59 | func addValueFuncs(out map[string]reflect.Value, in FuncMap) { 60 | for name, fn := range in { 61 | v := reflect.ValueOf(fn) 62 | if v.Kind() != reflect.Func { 63 | panic("value for " + name + " not a function") 64 | } 65 | if !goodFunc(v.Type()) { 66 | panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut())) 67 | } 68 | out[name] = v 69 | } 70 | } 71 | 72 | // addFuncs adds to values the functions in funcs. It does no checking of the input - 73 | // call addValueFuncs first. 74 | func addFuncs(out, in FuncMap) { 75 | for name, fn := range in { 76 | out[name] = fn 77 | } 78 | } 79 | 80 | // goodFunc checks that the function or method has the right result signature. 81 | func goodFunc(typ reflect.Type) bool { 82 | // We allow functions with 1 result or 2 results where the second is an error. 83 | switch { 84 | case typ.NumOut() == 1: 85 | return true 86 | case typ.NumOut() == 2 && typ.Out(1) == errorType: 87 | return true 88 | } 89 | return false 90 | } 91 | 92 | // findFunction looks for a function in the template, and global map. 93 | func findFunction(name string, tmpl *Template) (reflect.Value, bool) { 94 | if tmpl != nil && tmpl.common != nil { 95 | if fn := tmpl.execFuncs[name]; fn.IsValid() { 96 | return fn, true 97 | } 98 | } 99 | if fn := builtinFuncs[name]; fn.IsValid() { 100 | return fn, true 101 | } 102 | return reflect.Value{}, false 103 | } 104 | 105 | // Indexing. 106 | 107 | // index returns the result of indexing its first argument by the following 108 | // arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each 109 | // indexed item must be a map, slice, or array. 110 | func index(item interface{}, indices ...interface{}) (interface{}, error) { 111 | v := reflect.ValueOf(item) 112 | for _, i := range indices { 113 | index := reflect.ValueOf(i) 114 | var isNil bool 115 | if v, isNil = indirect(v); isNil { 116 | return nil, fmt.Errorf("index of nil pointer") 117 | } 118 | switch v.Kind() { 119 | case reflect.Array, reflect.Slice, reflect.String: 120 | var x int64 121 | switch index.Kind() { 122 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 123 | x = index.Int() 124 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 125 | x = int64(index.Uint()) 126 | default: 127 | return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type()) 128 | } 129 | if x < 0 || x >= int64(v.Len()) { 130 | return nil, fmt.Errorf("index out of range: %d", x) 131 | } 132 | v = v.Index(int(x)) 133 | case reflect.Map: 134 | if !index.IsValid() { 135 | index = reflect.Zero(v.Type().Key()) 136 | } 137 | if !index.Type().AssignableTo(v.Type().Key()) { 138 | return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type()) 139 | } 140 | if x := v.MapIndex(index); x.IsValid() { 141 | v = x 142 | } else { 143 | v = reflect.Zero(v.Type().Elem()) 144 | } 145 | default: 146 | return nil, fmt.Errorf("can't index item of type %s", v.Type()) 147 | } 148 | } 149 | return v.Interface(), nil 150 | } 151 | 152 | // Length 153 | 154 | // length returns the length of the item, with an error if it has no defined length. 155 | func length(item interface{}) (int, error) { 156 | v, isNil := indirect(reflect.ValueOf(item)) 157 | if isNil { 158 | return 0, fmt.Errorf("len of nil pointer") 159 | } 160 | switch v.Kind() { 161 | case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: 162 | return v.Len(), nil 163 | } 164 | return 0, fmt.Errorf("len of type %s", v.Type()) 165 | } 166 | 167 | // Function invocation 168 | 169 | // call returns the result of evaluating the first argument as a function. 170 | // The function must return 1 result, or 2 results, the second of which is an error. 171 | func call(fn interface{}, args ...interface{}) (interface{}, error) { 172 | v := reflect.ValueOf(fn) 173 | typ := v.Type() 174 | if typ.Kind() != reflect.Func { 175 | return nil, fmt.Errorf("non-function of type %s", typ) 176 | } 177 | if !goodFunc(typ) { 178 | return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut()) 179 | } 180 | numIn := typ.NumIn() 181 | var dddType reflect.Type 182 | if typ.IsVariadic() { 183 | if len(args) < numIn-1 { 184 | return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1) 185 | } 186 | dddType = typ.In(numIn - 1).Elem() 187 | } else { 188 | if len(args) != numIn { 189 | return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn) 190 | } 191 | } 192 | argv := make([]reflect.Value, len(args)) 193 | for i, arg := range args { 194 | value := reflect.ValueOf(arg) 195 | // Compute the expected type. Clumsy because of variadics. 196 | var argType reflect.Type 197 | if !typ.IsVariadic() || i < numIn-1 { 198 | argType = typ.In(i) 199 | } else { 200 | argType = dddType 201 | } 202 | if !value.IsValid() && canBeNil(argType) { 203 | value = reflect.Zero(argType) 204 | } 205 | if !value.Type().AssignableTo(argType) { 206 | return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType) 207 | } 208 | argv[i] = value 209 | } 210 | result := v.Call(argv) 211 | if len(result) == 2 && !result[1].IsNil() { 212 | return result[0].Interface(), result[1].Interface().(error) 213 | } 214 | return result[0].Interface(), nil 215 | } 216 | 217 | // Boolean logic. 218 | 219 | func truth(a interface{}) bool { 220 | t, _ := isTrue(reflect.ValueOf(a)) 221 | return t 222 | } 223 | 224 | // and computes the Boolean AND of its arguments, returning 225 | // the first false argument it encounters, or the last argument. 226 | func and(arg0 interface{}, args ...interface{}) interface{} { 227 | if !truth(arg0) { 228 | return arg0 229 | } 230 | for i := range args { 231 | arg0 = args[i] 232 | if !truth(arg0) { 233 | break 234 | } 235 | } 236 | return arg0 237 | } 238 | 239 | // or computes the Boolean OR of its arguments, returning 240 | // the first true argument it encounters, or the last argument. 241 | func or(arg0 interface{}, args ...interface{}) interface{} { 242 | if truth(arg0) { 243 | return arg0 244 | } 245 | for i := range args { 246 | arg0 = args[i] 247 | if truth(arg0) { 248 | break 249 | } 250 | } 251 | return arg0 252 | } 253 | 254 | // not returns the Boolean negation of its argument. 255 | func not(arg interface{}) (truth bool) { 256 | truth, _ = isTrue(reflect.ValueOf(arg)) 257 | return !truth 258 | } 259 | 260 | // Comparison. 261 | 262 | // TODO: Perhaps allow comparison between signed and unsigned integers. 263 | 264 | var ( 265 | errBadComparisonType = errors.New("invalid type for comparison") 266 | errBadComparison = errors.New("incompatible types for comparison") 267 | errNoComparison = errors.New("missing argument for comparison") 268 | ) 269 | 270 | type kind int 271 | 272 | const ( 273 | invalidKind kind = iota 274 | boolKind 275 | complexKind 276 | intKind 277 | floatKind 278 | integerKind 279 | stringKind 280 | uintKind 281 | ) 282 | 283 | func basicKind(v reflect.Value) (kind, error) { 284 | switch v.Kind() { 285 | case reflect.Bool: 286 | return boolKind, nil 287 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 288 | return intKind, nil 289 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 290 | return uintKind, nil 291 | case reflect.Float32, reflect.Float64: 292 | return floatKind, nil 293 | case reflect.Complex64, reflect.Complex128: 294 | return complexKind, nil 295 | case reflect.String: 296 | return stringKind, nil 297 | } 298 | return invalidKind, errBadComparisonType 299 | } 300 | 301 | // eq evaluates the comparison a == b || a == c || ... 302 | func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) { 303 | v1 := reflect.ValueOf(arg1) 304 | k1, err := basicKind(v1) 305 | if err != nil { 306 | return false, err 307 | } 308 | if len(arg2) == 0 { 309 | return false, errNoComparison 310 | } 311 | for _, arg := range arg2 { 312 | v2 := reflect.ValueOf(arg) 313 | k2, err := basicKind(v2) 314 | if err != nil { 315 | return false, err 316 | } 317 | truth := false 318 | if k1 != k2 { 319 | // Special case: Can compare integer values regardless of type's sign. 320 | switch { 321 | case k1 == intKind && k2 == uintKind: 322 | truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint() 323 | case k1 == uintKind && k2 == intKind: 324 | truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int()) 325 | default: 326 | return false, errBadComparison 327 | } 328 | } else { 329 | switch k1 { 330 | case boolKind: 331 | truth = v1.Bool() == v2.Bool() 332 | case complexKind: 333 | truth = v1.Complex() == v2.Complex() 334 | case floatKind: 335 | truth = v1.Float() == v2.Float() 336 | case intKind: 337 | truth = v1.Int() == v2.Int() 338 | case stringKind: 339 | truth = v1.String() == v2.String() 340 | case uintKind: 341 | truth = v1.Uint() == v2.Uint() 342 | default: 343 | panic("invalid kind") 344 | } 345 | } 346 | if truth { 347 | return true, nil 348 | } 349 | } 350 | return false, nil 351 | } 352 | 353 | // ne evaluates the comparison a != b. 354 | func ne(arg1, arg2 interface{}) (bool, error) { 355 | // != is the inverse of ==. 356 | equal, err := eq(arg1, arg2) 357 | return !equal, err 358 | } 359 | 360 | // lt evaluates the comparison a < b. 361 | func lt(arg1, arg2 interface{}) (bool, error) { 362 | v1 := reflect.ValueOf(arg1) 363 | k1, err := basicKind(v1) 364 | if err != nil { 365 | return false, err 366 | } 367 | v2 := reflect.ValueOf(arg2) 368 | k2, err := basicKind(v2) 369 | if err != nil { 370 | return false, err 371 | } 372 | truth := false 373 | if k1 != k2 { 374 | // Special case: Can compare integer values regardless of type's sign. 375 | switch { 376 | case k1 == intKind && k2 == uintKind: 377 | truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() 378 | case k1 == uintKind && k2 == intKind: 379 | truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int()) 380 | default: 381 | return false, errBadComparison 382 | } 383 | } else { 384 | switch k1 { 385 | case boolKind, complexKind: 386 | return false, errBadComparisonType 387 | case floatKind: 388 | truth = v1.Float() < v2.Float() 389 | case intKind: 390 | truth = v1.Int() < v2.Int() 391 | case stringKind: 392 | truth = v1.String() < v2.String() 393 | case uintKind: 394 | truth = v1.Uint() < v2.Uint() 395 | default: 396 | panic("invalid kind") 397 | } 398 | } 399 | return truth, nil 400 | } 401 | 402 | // le evaluates the comparison <= b. 403 | func le(arg1, arg2 interface{}) (bool, error) { 404 | // <= is < or ==. 405 | lessThan, err := lt(arg1, arg2) 406 | if lessThan || err != nil { 407 | return lessThan, err 408 | } 409 | return eq(arg1, arg2) 410 | } 411 | 412 | // gt evaluates the comparison a > b. 413 | func gt(arg1, arg2 interface{}) (bool, error) { 414 | // > is the inverse of <=. 415 | lessOrEqual, err := le(arg1, arg2) 416 | if err != nil { 417 | return false, err 418 | } 419 | return !lessOrEqual, nil 420 | } 421 | 422 | // ge evaluates the comparison a >= b. 423 | func ge(arg1, arg2 interface{}) (bool, error) { 424 | // >= is the inverse of <. 425 | lessThan, err := lt(arg1, arg2) 426 | if err != nil { 427 | return false, err 428 | } 429 | return !lessThan, nil 430 | } 431 | 432 | // HTML escaping. 433 | 434 | var ( 435 | htmlQuot = []byte(""") // shorter than """ 436 | htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5 437 | htmlAmp = []byte("&") 438 | htmlLt = []byte("<") 439 | htmlGt = []byte(">") 440 | ) 441 | 442 | // HTMLEscape writes to w the escaped HTML equivalent of the plain text data b. 443 | func HTMLEscape(w io.Writer, b []byte) { 444 | last := 0 445 | for i, c := range b { 446 | var html []byte 447 | switch c { 448 | case '"': 449 | html = htmlQuot 450 | case '\'': 451 | html = htmlApos 452 | case '&': 453 | html = htmlAmp 454 | case '<': 455 | html = htmlLt 456 | case '>': 457 | html = htmlGt 458 | default: 459 | continue 460 | } 461 | w.Write(b[last:i]) 462 | w.Write(html) 463 | last = i + 1 464 | } 465 | w.Write(b[last:]) 466 | } 467 | 468 | // HTMLEscapeString returns the escaped HTML equivalent of the plain text data s. 469 | func HTMLEscapeString(s string) string { 470 | // Avoid allocation if we can. 471 | if strings.IndexAny(s, `'"&<>`) < 0 { 472 | return s 473 | } 474 | var b bytes.Buffer 475 | HTMLEscape(&b, []byte(s)) 476 | return b.String() 477 | } 478 | 479 | // HTMLEscaper returns the escaped HTML equivalent of the textual 480 | // representation of its arguments. 481 | func HTMLEscaper(args ...interface{}) string { 482 | return HTMLEscapeString(evalArgs(args)) 483 | } 484 | 485 | // JavaScript escaping. 486 | 487 | var ( 488 | jsLowUni = []byte(`\u00`) 489 | hex = []byte("0123456789ABCDEF") 490 | 491 | jsBackslash = []byte(`\\`) 492 | jsApos = []byte(`\'`) 493 | jsQuot = []byte(`\"`) 494 | jsLt = []byte(`\x3C`) 495 | jsGt = []byte(`\x3E`) 496 | ) 497 | 498 | // JSEscape writes to w the escaped JavaScript equivalent of the plain text data b. 499 | func JSEscape(w io.Writer, b []byte) { 500 | last := 0 501 | for i := 0; i < len(b); i++ { 502 | c := b[i] 503 | 504 | if !jsIsSpecial(rune(c)) { 505 | // fast path: nothing to do 506 | continue 507 | } 508 | w.Write(b[last:i]) 509 | 510 | if c < utf8.RuneSelf { 511 | // Quotes, slashes and angle brackets get quoted. 512 | // Control characters get written as \u00XX. 513 | switch c { 514 | case '\\': 515 | w.Write(jsBackslash) 516 | case '\'': 517 | w.Write(jsApos) 518 | case '"': 519 | w.Write(jsQuot) 520 | case '<': 521 | w.Write(jsLt) 522 | case '>': 523 | w.Write(jsGt) 524 | default: 525 | w.Write(jsLowUni) 526 | t, b := c>>4, c&0x0f 527 | w.Write(hex[t : t+1]) 528 | w.Write(hex[b : b+1]) 529 | } 530 | } else { 531 | // Unicode rune. 532 | r, size := utf8.DecodeRune(b[i:]) 533 | if unicode.IsPrint(r) { 534 | w.Write(b[i : i+size]) 535 | } else { 536 | fmt.Fprintf(w, "\\u%04X", r) 537 | } 538 | i += size - 1 539 | } 540 | last = i + 1 541 | } 542 | w.Write(b[last:]) 543 | } 544 | 545 | // JSEscapeString returns the escaped JavaScript equivalent of the plain text data s. 546 | func JSEscapeString(s string) string { 547 | // Avoid allocation if we can. 548 | if strings.IndexFunc(s, jsIsSpecial) < 0 { 549 | return s 550 | } 551 | var b bytes.Buffer 552 | JSEscape(&b, []byte(s)) 553 | return b.String() 554 | } 555 | 556 | func jsIsSpecial(r rune) bool { 557 | switch r { 558 | case '\\', '\'', '"', '<', '>': 559 | return true 560 | } 561 | return r < ' ' || utf8.RuneSelf <= r 562 | } 563 | 564 | // JSEscaper returns the escaped JavaScript equivalent of the textual 565 | // representation of its arguments. 566 | func JSEscaper(args ...interface{}) string { 567 | return JSEscapeString(evalArgs(args)) 568 | } 569 | 570 | // URLQueryEscaper returns the escaped value of the textual representation of 571 | // its arguments in a form suitable for embedding in a URL query. 572 | func URLQueryEscaper(args ...interface{}) string { 573 | return url.QueryEscape(evalArgs(args)) 574 | } 575 | 576 | // evalArgs formats the list of arguments into a string. It is therefore equivalent to 577 | // fmt.Sprint(args...) 578 | // except that each argument is indirected (if a pointer), as required, 579 | // using the same rules as the default string evaluation during template 580 | // execution. 581 | func evalArgs(args []interface{}) string { 582 | ok := false 583 | var s string 584 | // Fast path for simple common case. 585 | if len(args) == 1 { 586 | s, ok = args[0].(string) 587 | } 588 | if !ok { 589 | for i, arg := range args { 590 | a, ok := printableValue(reflect.ValueOf(arg)) 591 | if ok { 592 | args[i] = a 593 | } // else left fmt do its thing 594 | } 595 | s = fmt.Sprint(args...) 596 | } 597 | return s 598 | } 599 | --------------------------------------------------------------------------------