diff --git a/.drone.sh b/.drone.sh new file mode 100644 index 0000000..591145e --- /dev/null +++ b/.drone.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +set -e +set -x + +# compile the main binary +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/amd64/drone-webhook +GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm64/drone-webhook +GOOS=linux GOARCH=arm CGO_ENABLED=0 GOARM=7 go build -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -tags netgo -o release/linux/arm/drone-webhook diff --git a/.drone.yml b/.drone.yml index 3730e81..26e1c7a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -3,32 +3,43 @@ workspace: path: src/github.com/drone-plugins/drone-webhook pipeline: - build: - image: golang:1.6 - environment: - - CGO_ENABLED=0 + test: + image: golang:1.9 commands: - - make deps - - make vet - - make build - - make test + - go vet + - go test -cover -coverprofile=coverage.out - docker: - repo: plugins/drone-webhook - storage_driver: overlay - tag: latest + build_linux_amd64: + image: golang:1.9 + commands: + - sh .drone.sh + + publish_linux_amd64: + image: plugins/docker + repo: plugins/webook + tags: [ latest, 1.0.0, 1.0, 1 ] + secrets: [ docker_username, docker_password ] + dockerfile: Dockerfile when: branch: master event: push -plugin: - name: Webhook - desc: Send build status notifications via Webhook - type: notify - image: plugins/drone-webhook - labels: - - notify - - webhook - - rest - - json - - hook + publish_linux_arm64: + image: plugins/docker + repo: plugins/webhook + tags: [ linux-arm64 ] + secrets: [ docker_username, docker_password ] + dockerfile: Dockerfile.arm64 + when: + branch: master + event: push + + publish_linux_arm: + image: plugins/docker + repo: plugins/webhook + tags: [ linux-arm ] + secrets: [ docker_username, docker_password ] + dockerfile: Dockerfile.arm + when: + branch: master + event: push diff --git a/.drone.yml.sig b/.drone.yml.sig deleted file mode 100644 index f35d038..0000000 --- a/.drone.yml.sig +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lLXBsdWdpbnMvZHJvbmUtd2ViaG9vawoKcGlwZWxpbmU6CiAgYnVpbGQ6CiAgICBpbWFnZTogZ29sYW5nOjEuNgogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gQ0dPX0VOQUJMRUQ9MAogICAgY29tbWFuZHM6CiAgICAgIC0gbWFrZSBkZXBzCiAgICAgIC0gbWFrZSB2ZXQKICAgICAgLSBtYWtlIGJ1aWxkCiAgICAgIC0gbWFrZSB0ZXN0CgogIGRvY2tlcjoKICAgIHJlcG86IHBsdWdpbnMvZHJvbmUtd2ViaG9vawogICAgc3RvcmFnZV9kcml2ZXI6IG92ZXJsYXkKICAgIHRhZzogbGF0ZXN0CiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBldmVudDogcHVzaAoKcGx1Z2luOgogIG5hbWU6IFdlYmhvb2sKICBkZXNjOiBTZW5kIGJ1aWxkIHN0YXR1cyBub3RpZmljYXRpb25zIHZpYSBXZWJob29rCiAgdHlwZTogbm90aWZ5CiAgaW1hZ2U6IHBsdWdpbnMvZHJvbmUtd2ViaG9vawogIGxhYmVsczoKICAgIC0gbm90aWZ5CiAgICAtIHdlYmhvb2sKICAgIC0gcmVzdAogICAgLSBqc29uCiAgICAtIGhvb2sK.XE_NdTdvCanuvvQR1DWo2i25DK8GUOYTS0X84UQVJds \ No newline at end of file diff --git a/.gitignore b/.gitignore index b22c5d9..2b8f76c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ _testmain.go *.exe *.test *.prof - +.idea/ +release/ coverage.out drone-webhook diff --git a/DOCS.md b/DOCS.md index 8cbab99..1e1dc0d 100644 --- a/DOCS.md +++ b/DOCS.md @@ -5,7 +5,9 @@ You can override the default configuration with the following parameters: * `urls` - JSON payloads are sent to each URL * `method` - HTTP request method. Defaults to `POST` -* `header` - HTTP request header map +* `headers` - HTTP request header map +* `username` - The username as a string for HTTP basic auth +* `password` - The password as a string for HTTP basic auth * `skip_verify` - Skip verification of TLS certificates, defaults to `false` ## Example @@ -18,8 +20,8 @@ notify: urls: - https://your.webhook/... - https://your.other.webhook/... - header: - Authorization: pa55word + headers: + - "Authorization=pa55word" ``` ### Custom Body @@ -32,18 +34,7 @@ For this usage the following additional parameters should be used: Example configuration that generate a custom Yaml payload: -```yaml -notify: - webhook: - urls: - - https://your.webhook/... - - https://your.other.webhook/... - content_type: application/yaml - template: > - repo: {{repo.full_name}} - build: {{build.number}} - commit: {{build.commit}} -``` +TBD ### Basic Authentication @@ -54,7 +45,7 @@ In some cases your webhook may need to authenticate with another service. You can set the basic `Authentication` header with a username and password. For these use cases we expose the following additional parameters: -* `auth` - Sets the request's `Authorization` header to use HTTP Basic Authentication with the provided username and password below +* Sets the request's `Authorization` header to use HTTP Basic Authentication with the provided username and password below * `username` - The username as a string * `password` - The password as a string @@ -64,19 +55,14 @@ Example configuration to include HTTP Basic Authentication: notify: webhook: method: POST - auth: - username: $$USERNAME - password: $$PASSWORD + username: myusername + password: mypassword urls: - https://tower.example.com/... ``` ### Debugging Webhooks -> If you have private variables that are encrypted and hidden in `.drone.sec`, -> remember that the `debug` flag may print out those sensitive values. Please -> use `debug: true` wisely. - In some cases complicated webhooks may need debugging to ensure `urls`, `template`, `auth` and more a properly configured. For these use cases we expose the following `debug` parameter: @@ -90,32 +76,12 @@ notify: webhook: debug: true method: POST - auth: - username: $$TOWER_USER - password: $$TOWER_PASS + username: myusername + password: mypassword urls: - http://tower.example.com/api/v1/job_templates/44/launch/ - http://tower.example.com/api/v1/job_templates/45/launch/ - content_type: application/json - template: '{"name": "project.deploy","extra_vars": "{\"env\": \"dev\",\"git_branch\": \"{{ build.branch }}\",\"hipchat_token\": \"$$HIPCHAT_TOKEN\"}"}' + content_type: application/json ``` -Example of a debug print result: -``` -[debug] Webhook 1 - URL: http://tower.example.com/api/v1/job_templates/44/launch/ - METHOD: POST - HEADERS: map[Content-Type:[application/json] Authorization:[Basic EMfNB3fakB8EMfNB3fakB8==]] - REQUEST BODY: {"name": "project.deploy","extra_vars": "{\"env\": \"dev\",\"git_branch\": \"develop\",\"hipchat_token\": \"h1pchatT0k3n\"}"} - RESPONSE STATUS: 202 ACCEPTED - RESPONSE BODY: {"job": 236} - -[debug] Webhook 2 - URL: http://tower.example.com/api/v1/job_templates/45/launch/ - METHOD: POST - HEADERS: map[Content-Type:[application/json] Authorization:[Basic EMfNB3fakB8EMfNB3fakB8==]] - REQUEST BODY: {"name": "project.deploy","extra_vars": "{\"env\": \"dev\",\"git_branch\": \"develop\",\"hipchat_token\": \"h1pchatT0k3n\"}"} - RESPONSE STATUS: 202 ACCEPTED - RESPONSE BODY: {"job": 406} -``` diff --git a/Dockerfile b/Dockerfile index ed0c0dd..e6471b4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,10 @@ -# Docker image for the Drone Webhook plugin -# -# cd $GOPATH/src/github.com/drone-plugins/drone-webhook -# make deps build docker - FROM alpine:3.3 +MAINTAINER Drone.IO Community RUN apk update && \ apk add \ ca-certificates && \ rm -rf /var/cache/apk/* -ADD drone-webhook /bin/ +ADD release/linux/amd64/drone-webhook /bin/ ENTRYPOINT ["/bin/drone-webhook"] diff --git a/Dockerfile.arm b/Dockerfile.arm new file mode 100644 index 0000000..b47e7b7 --- /dev/null +++ b/Dockerfile.arm @@ -0,0 +1,10 @@ +FROM alpine:3.3 +MAINTAINER Drone.IO Community + +RUN apk update && \ + apk add \ + ca-certificates && \ + rm -rf /var/cache/apk/* + +ADD release/linux/arm/drone-webhook /bin/ +ENTRYPOINT ["/bin/drone-webhook"] \ No newline at end of file diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 new file mode 100644 index 0000000..0c05341 --- /dev/null +++ b/Dockerfile.arm64 @@ -0,0 +1,10 @@ +FROM alpine:3.3 +MAINTAINER Drone.IO Community + +RUN apk update && \ + apk add \ + ca-certificates && \ + rm -rf /var/cache/apk/* + +ADD release/linux/arm64/drone-webhook /bin/ +ENTRYPOINT ["/bin/drone-webhook"] \ No newline at end of file diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index 0ecaf19..0000000 --- a/MAINTAINERS +++ /dev/null @@ -1,46 +0,0 @@ -[people] - [people.bradrydzewski] - name = "Brad Rydzewski" - email = "brad@drone.io" - login = "bradrydzewski" - [people.Bugagazavr] - name = "Kirill" - email = "" - login = "Bugagazavr" - [people.donny-dont] - name = "Don Olmstead" - email = "donny-dont@gmail.com" - login = "donny-dont" - [people.jackspirou] - name = "Jack Spirou" - email = "" - login = "jackspirou" - [people.msteinert] - name = "Mike Steinert" - email = "" - login = "msteinert" - [people.nlf] - name = "Nathan LaFreniere" - email = "" - login = "nlf" - [people.tboerger] - name = "Thomas Boerger" - email = "thomas@webhippie.de" - login = "tboerger" - [people.athieriot] - name = "Aurélien Thieriot" - email = "a.thieriot@gmail.com" - login = "athieriot" - -[org] - [org.core] - people = [ - "bradrydzewski", - "Bugagazavr", - "donny-dont", - "jackspirou", - "msteinert", - "nlf", - "tboerger", - "athieriot" - ] diff --git a/Makefile b/Makefile deleted file mode 100644 index 54eba0f..0000000 --- a/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -.PHONY: all clean deps fmt vet test docker - -EXECUTABLE ?= drone-webhook -IMAGE ?= plugins/$(EXECUTABLE) -COMMIT ?= $(shell git rev-parse --short HEAD) - -LDFLAGS = -X "main.buildCommit=$(COMMIT)" -PACKAGES = $(shell go list ./... | grep -v /vendor/) - -all: deps build test - -clean: - go clean -i ./... - -deps: - go get -t ./... - -fmt: - go fmt $(PACKAGES) - -vet: - go vet $(PACKAGES) - -test: - @for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done; - -docker: - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-s -w $(LDFLAGS)' - docker build --rm -t $(IMAGE) . - -$(EXECUTABLE): $(wildcard *.go) - go build -ldflags '-s -w $(LDFLAGS)' - -build: $(EXECUTABLE) diff --git a/README.md b/README.md index 1d98e58..ad7167e 100644 --- a/README.md +++ b/README.md @@ -8,112 +8,30 @@ Drone plugin to send build status notifications via Webhook. For the usage infor ## Binary -Build the binary using `make`: +Build the binary using `./.drone.sh`: + +### Usage ``` -make deps build + docker run --rm \ + -e PLUGIN_URLS=https://hooks.somplace.com/endpoing/... \ + -e PLUGIN_HEADERS="HEADER1=value1" \ + -e PLUGIN_USERNAME=drone \ + -e PLUGIN_PASSWORD=password \ + -e DRONE_REPO_OWNER=octocat \ + -e DRONE_REPO_NAME=hello-world \ + -e DRONE_COMMIT_SHA=7fd1a60b01f91b314f59955a4e4d4e80d8edf11d \ + -e DRONE_COMMIT_BRANCH=master \ + -e DRONE_COMMIT_AUTHOR=octocat \ + -e DRONE_BUILD_NUMBER=1 \ + -e DRONE_BUILD_STATUS=success \ + -e DRONE_BUILD_LINK=http://github.com/octocat/hello-world \ + -e DRONE_TAG=1.0.0 \ + plugins/webhook ``` -### Example -```sh -./drone-webhook <= http.StatusBadRequest { - body, err := ioutil.ReadAll(resp.Body) - - if err != nil { - fmt.Printf("Error: Failed to read the HTTP response body. %s\n", err) - } - - if vargs.Debug { - fmt.Printf( - debugRespFormat, - i+1, - req.URL, - req.Method, - req.Header, - string(b), - resp.Status, - string(body), - ) - } else { - fmt.Printf( - respFormat, - i+1, - req.URL, - resp.Status, - string(body), - ) - } - } + if err := app.Run(os.Args); err != nil { + log.Fatal(err) } } + +func run(c *cli.Context) error { + fmt.Printf("Drone Webhook Plugin built from %s\n", version) + + plugin := Plugin{ + Repo: Repo{ + Owner: c.String("repo.owner"), + Name: c.String("repo.name"), + }, + Build: Build{ + Tag: c.String("build.tag"), + Number: c.Int("build.number"), + Event: c.String("build.event"), + Status: c.String("build.status"), + Commit: c.String("commit.sha"), + Ref: c.String("commit.ref"), + Branch: c.String("commit.branch"), + Author: c.String("commit.author"), + Message: c.String("commit.message"), + Link: c.String("build.link"), + Started: c.Int64("build.started"), + Created: c.Int64("build.created"), + }, + Job: Job{ + Started: c.Int64("job.started"), + }, + Config: Config{ + Method: c.String("method"), + Username: c.String("username"), + Password: c.String("password"), + ContentType: c.String("content-type"), + Template: c.String("template"), + Headers: c.StringSlice("headers"), + URLs: c.StringSlice("urls"), + Debug: c.Bool("debug"), + SkipVerify: c.Bool("skip-verify"), + }, + } + return plugin.Exec() +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..778a8ce --- /dev/null +++ b/plugin.go @@ -0,0 +1,167 @@ +package main + +import ( + "fmt" + "os" + "io/ioutil" + "bytes" + "net/url" + "crypto/tls" + "net/http" + "encoding/json" + "strings" +) +type ( + Repo struct { + Owner string `json:"owner"` + Name string `json:"name"` + } + + Build struct { + Tag string `json:"tag"` + Event string `json:"event"` + Number int `json:"number"` + Commit string `json:"commit"` + Ref string `json:"ref"` + Branch string `json:"branch"` + Author string `json:"author"` + Message string `json:"message"` + Status string `json:"status"` + Link string `json:"link"` + Started int64 `json:"started"` + Created int64 `json:"created"` + } + + Config struct { + Method string + Username string + Password string + ContentType string + Template string + Headers []string + URLs []string + Debug bool + SkipVerify bool + } + + Job struct { + Started int64 `json:"started"` + } + + Plugin struct { + Repo Repo + Build Build + Config Config + Job Job + } +) + +func (p Plugin) Exec() error { + + var buf bytes.Buffer + var b []byte + + if p.Config.Template == "" { + data := struct { + Repo Repo `json:"repo"` + Build Build `json:"build"` + }{p.Repo, p.Build} + + if err := json.NewEncoder(&buf).Encode(&data); err != nil { + fmt.Printf("Error: Failed to encode JSON payload. %s\n", err) + return err + } + b = buf.Bytes() + } else { + txt, err := RenderTrim(p.Config.Template, p) + if err != nil { + return err + } + text := txt + b = []byte(text) + + } + + // build and execute a request for each url. + // all auth, headers, method, template (payload), + // and content_type values will be applied to + // every webhook request. + + for i, rawurl := range p.Config.URLs { + uri, err := url.Parse(rawurl) + + if err != nil { + fmt.Printf("Error: Failed to parse the hook URL. %s\n", err) + os.Exit(1) + } + + r := bytes.NewReader(b) + + req, err := http.NewRequest(p.Config.Method, uri.String(), r) + + if err != nil { + fmt.Printf("Error: Failed to create the HTTP request. %s\n", err) + return err + } + + req.Header.Set("Content-Type", p.Config.ContentType) + + for _, value := range p.Config.Headers { + header := strings.Split(value, "=") + req.Header.Set(header[0], header[1]) + } + + if p.Config.Username != "" && p.Config.Password != "" { + req.SetBasicAuth(p.Config.Username, p.Config.Password) + } + + client := http.DefaultClient + if p.Config.SkipVerify { + client = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + } + resp, err := client.Do(req) + + if err != nil { + fmt.Printf("Error: Failed to execute the HTTP request. %s\n", err) + return err + } + + defer resp.Body.Close() + + if p.Config.Debug || resp.StatusCode >= http.StatusBadRequest { + body, err := ioutil.ReadAll(resp.Body) + + if err != nil { + fmt.Printf("Error: Failed to read the HTTP response body. %s\n", err) + } + + if p.Config.Debug { + fmt.Printf( + debugRespFormat, + i+1, + req.URL, + req.Method, + req.Header, + string(b), + resp.Status, + string(body), + ) + } else { + fmt.Printf( + respFormat, + i+1, + req.URL, + resp.Status, + string(body), + ) + } + } + } + return nil +} + + diff --git a/template.go b/template.go new file mode 100644 index 0000000..1f9c0ec --- /dev/null +++ b/template.go @@ -0,0 +1,137 @@ +package main + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" + "unicode" + "unicode/utf8" + + "github.com/aymerick/raymond" +) + +func init() { + raymond.RegisterHelpers(funcs) +} + +// Render parses and executes a template, returning the results in string format. +func Render(template string, payload interface{}) (s string, err error) { + u, err := url.Parse(template) + if err == nil { + switch u.Scheme { + case "http", "https": + res, err := http.Get(template) + if err != nil { + return s, err + } + defer res.Body.Close() + out, err := ioutil.ReadAll(res.Body) + if err != nil { + return s, err + } + template = string(out) + + case "file": + out, err := ioutil.ReadFile(u.Path) + if err != nil { + return s, err + } + template = string(out) + } + } + + return raymond.Render(template, payload) +} + +// RenderTrim parses and executes a template, returning the results in string +// format. The result is trimmed to remove left and right padding and newlines +// that may be added unintentially in the template markup. +func RenderTrim(template string, playload interface{}) (string, error) { + out, err := Render(template, playload) + return strings.Trim(out, " \n"), err +} + +var funcs = map[string]interface{}{ + "uppercasefirst": uppercaseFirst, + "uppercase": strings.ToUpper, + "lowercase": strings.ToLower, + "duration": toDuration, + "datetime": toDatetime, + "success": isSuccess, + "failure": isFailure, + "truncate": truncate, + "urlencode": urlencode, + "since": since, +} + +func truncate(s string, len int) string { + if utf8.RuneCountInString(s) <= len { + return s + } + runes := []rune(s) + return string(runes[:len]) + +} + +func uppercaseFirst(s string) string { + a := []rune(s) + a[0] = unicode.ToUpper(a[0]) + s = string(a) + return s +} + +func toDuration(started, finished float64) string { + return fmt.Sprintln(time.Duration(finished-started) * time.Second) +} + +func toDatetime(timestamp float64, layout, zone string) string { + if len(zone) == 0 { + return time.Unix(int64(timestamp), 0).Format(layout) + } + loc, err := time.LoadLocation(zone) + if err != nil { + return time.Unix(int64(timestamp), 0).Local().Format(layout) + } + return time.Unix(int64(timestamp), 0).In(loc).Format(layout) +} + +func isSuccess(conditional bool, options *raymond.Options) string { + if !conditional { + return options.Inverse() + } + + switch options.ParamStr(0) { + case "success": + return options.Fn() + default: + return options.Inverse() + } +} + +func isFailure(conditional bool, options *raymond.Options) string { + if !conditional { + return options.Inverse() + } + + switch options.ParamStr(0) { + case "failure", "error", "killed": + return options.Fn() + default: + return options.Inverse() + } +} + +func urlencode(options *raymond.Options) string { + return url.QueryEscape(options.Fn()) +} + +func since(start int64) string { + // NOTE: not using `time.Since()` because the fractional second component + // will give us something like "40m12.917523438s" vs "40m12s". We lose + // some precision, but the format is much more readable. + now := time.Unix(time.Now().Unix(), 0) + return fmt.Sprintln(now.Sub(time.Unix(start, 0))) +} diff --git a/types.go b/types.go deleted file mode 100644 index a5a8682..0000000 --- a/types.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -// Params represents the valid paramenter options for the webhook plugin. -type Params struct { - URLs []string `json:"urls"` - SkipVerify bool `json:"skip_verify"` - Debug bool `json:"debug"` - Auth Auth `json:"auth"` - Headers map[string]string `json:"header"` - Method string `json:"method"` - Template string `json:"template"` - ContentType string `json:"content_type"` -} - -// Auth represents a basic HTTP authentication username and password. -type Auth struct { - Username string `json:"username"` - Password string `json:"password"` -} diff --git a/vendor/github.com/aymerick/raymond/BENCHMARKS.md b/vendor/github.com/aymerick/raymond/BENCHMARKS.md new file mode 100644 index 0000000..c3af56c --- /dev/null +++ b/vendor/github.com/aymerick/raymond/BENCHMARKS.md @@ -0,0 +1,46 @@ +# Benchmarks + +Hardware: MacBookPro11,1 - Intel Core i5 - 2,6 GHz - 8 Go RAM + +With: + + - handlebars.js #8cba84df119c317fcebc49fb285518542ca9c2d0 + - raymond #7bbaaf50ed03c96b56687d7fa6c6e04e02375a98 + + +## handlebars.js (ops/ms) + + arguments 198 ±4 (5) + array-each 568 ±23 (5) + array-mustache 522 ±18 (4) + complex 71 ±7 (3) + data 67 ±2 (3) + depth-1 47 ±2 (3) + depth-2 14 ±1 (2) + object-mustache 1099 ±47 (5) + object 907 ±58 (4) + partial-recursion 46 ±3 (4) + partial 68 ±3 (3) + paths 1650 ±50 (3) + string 2552 ±157 (3) + subexpression 141 ±2 (4) + variables 2671 ±83 (4) + + +## raymond + + BenchmarkArguments 200000 6642 ns/op 151 ops/ms + BenchmarkArrayEach 100000 19584 ns/op 51 ops/ms + BenchmarkArrayMustache 100000 17305 ns/op 58 ops/ms + BenchmarkComplex 30000 50270 ns/op 20 ops/ms + BenchmarkData 50000 25551 ns/op 39 ops/ms + BenchmarkDepth1 100000 20162 ns/op 50 ops/ms + BenchmarkDepth2 30000 47782 ns/op 21 ops/ms + BenchmarkObjectMustache 200000 7668 ns/op 130 ops/ms + BenchmarkObject 200000 8843 ns/op 113 ops/ms + BenchmarkPartialRecursion 50000 23139 ns/op 43 ops/ms + BenchmarkPartial 50000 31015 ns/op 32 ops/ms + BenchmarkPath 200000 8997 ns/op 111 ops/ms + BenchmarkString 1000000 1879 ns/op 532 ops/ms + BenchmarkSubExpression 300000 4935 ns/op 203 ops/ms + BenchmarkVariables 200000 6478 ns/op 154 ops/ms diff --git a/vendor/github.com/aymerick/raymond/CHANGELOG.md b/vendor/github.com/aymerick/raymond/CHANGELOG.md new file mode 100644 index 0000000..c438a5c --- /dev/null +++ b/vendor/github.com/aymerick/raymond/CHANGELOG.md @@ -0,0 +1,33 @@ +# Raymond Changelog + +### Raymond 2.0.1 _(June 01, 2016)_ + +- [BUGFIX] Removes data races [#3](https://github.com/aymerick/raymond/issues/3) - Thanks [@markbates](https://github.com/markbates) + +### Raymond 2.0.0 _(May 01, 2016)_ + +- [BUGFIX] Fixes passing of context in helper options [#2](https://github.com/aymerick/raymond/issues/2) - Thanks [@GhostRussia](https://github.com/GhostRussia) +- [BREAKING] Renames and unexports constants: + + - `handlebars.DUMP_TPL` + - `lexer.ESCAPED_ESCAPED_OPEN_MUSTACHE` + - `lexer.ESCAPED_OPEN_MUSTACHE` + - `lexer.OPEN_MUSTACHE` + - `lexer.CLOSE_MUSTACHE` + - `lexer.CLOSE_STRIP_MUSTACHE` + - `lexer.CLOSE_UNESCAPED_STRIP_MUSTACHE` + - `lexer.DUMP_TOKEN_POS` + - `lexer.DUMP_ALL_TOKENS_VAL` + + +### Raymond 1.1.0 _(June 15, 2015)_ + +- Permits templates references with lowercase versions of struct fields. +- Adds `ParseFile()` function. +- Adds `RegisterPartialFile()`, `RegisterPartialFiles()` and `Clone()` methods on `Template`. +- Helpers can now be struct methods. +- Ensures safe concurrent access to helpers and partials. + +### Raymond 1.0.0 _(June 09, 2015)_ + +- This is the first release. Raymond supports almost all handlebars features. See https://github.com/aymerick/raymond#limitations for a list of differences with the javascript implementation. diff --git a/vendor/github.com/aymerick/raymond/LICENSE b/vendor/github.com/aymerick/raymond/LICENSE new file mode 100644 index 0000000..6ce87cd --- /dev/null +++ b/vendor/github.com/aymerick/raymond/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Aymerick JEHANNE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/aymerick/raymond/README.md b/vendor/github.com/aymerick/raymond/README.md new file mode 100644 index 0000000..d9a6d9a --- /dev/null +++ b/vendor/github.com/aymerick/raymond/README.md @@ -0,0 +1,1417 @@ +# raymond [![Build Status](https://secure.travis-ci.org/aymerick/raymond.svg?branch=master)](http://travis-ci.org/aymerick/raymond) [![GoDoc](https://godoc.org/github.com/aymerick/raymond?status.svg)](http://godoc.org/github.com/aymerick/raymond) + +Handlebars for [golang](https://golang.org) with the same features as [handlebars.js](http://handlebarsjs.com) `3.0`. + +The full API documentation is available here: . + +![Raymond Logo](https://github.com/aymerick/raymond/blob/master/raymond.png?raw=true "Raymond") + + +# Table of Contents + +- [Quick Start](#quick-start) +- [Correct Usage](#correct-usage) +- [Context](#context) +- [HTML Escaping](#html-escaping) +- [Helpers](#helpers) + - [Template Helpers](#template-helpers) + - [Built-In Helpers](#built-in-helpers) + - [The `if` block helper](#the-if-block-helper) + - [The `unless` block helper](#the-unless-block-helper) + - [The `each` block helper](#the-each-block-helper) + - [The `with` block helper](#the-with-block-helper) + - [The `lookup` helper](#the-lookup-helper) + - [The `log` helper](#the-log-helper) + - [The `equal` helper](#the-equal-helper) + - [Block Helpers](#block-helpers) + - [Block Evaluation](#block-evaluation) + - [Conditional](#conditional) + - [Else Block Evaluation](#else-block-evaluation) + - [Block Parameters](#block-parameters) + - [Helper Parameters](#helper-parameters) + - [Automatic conversion](#automatic-conversion) + - [Options Argument](#options-argument) + - [Context Values](#context-values) + - [Helper Hash Arguments](#helper-hash-arguments) + - [Private Data](#private-data) + - [Utilites](#utilites) + - [`Str()`](#str) + - [`IsTrue()`](#istrue) +- [Context Functions](#context-functions) +- [Partials](#partials) + - [Template Partials](#template-partials) + - [Global Partials](#global-partials) + - [Dynamic Partials](#dynamic-partials) + - [Partial Contexts](#partial-contexts) + - [Partial Parameters](#partial-parameters) +- [Utility Functions](#utility-functions) +- [Mustache](#mustache) +- [Limitations](#limitations) +- [Handlebars Lexer](#handlebars-lexer) +- [Handlebars Parser](#handlebars-parser) +- [Test](#test) +- [References](#references) +- [Others Implementations](#others-implementations) + + +## Quick Start + + $ go get github.com/aymerick/raymond + +The quick and dirty way of rendering a handlebars template: + +```go +package main + +import ( + "fmt" + + "github.com/aymerick/raymond" +) + +func main() { + tpl := `
+

{{title}}

+
+ {{body}} +
+
+` + + ctx := map[string]string{ + "title": "My New Post", + "body": "This is my first post!", + } + + result, err := raymond.Render(tpl, ctx) + if err != nil { + panic("Please report a bug :)") + } + + fmt.Print(result) +} +``` + +Displays: + +```html +
+

My New Post

+
+ This is my first post! +
+
+``` + +Please note that the template will be parsed everytime you call `Render()` function. So you probably want to read the next section. + + +## Correct Usage + +To avoid parsing a template several times, use the `Parse()` and `Exec()` functions: + +```go +package main + +import ( + "fmt" + + "github.com/aymerick/raymond" +) + +func main() { + source := `
+

{{title}}

+
+ {{body}} +
+
+` + + ctxList := []map[string]string{ + { + "title": "My New Post", + "body": "This is my first post!", + }, + { + "title": "Here is another post", + "body": "This is my second post!", + }, + } + + // parse template + tpl, err := raymond.Parse(source) + if err != nil { + panic(err) + } + + for _, ctx := range ctxList { + // render template + result, err := tpl.Exec(ctx) + if err != nil { + panic(err) + } + + fmt.Print(result) + } +} + +``` + +Displays: + +```html +
+

My New Post

+
+ This is my first post! +
+
+
+

Here is another post

+
+ This is my second post! +
+
+``` + +You can use `MustParse()` and `MustExec()` functions if you don't want to deal with errors: + +```go +// parse template +tpl := raymond.MustParse(source) + +// render template +result := tpl.MustExec(ctx) +``` + + +## Context + +The rendering context can contain any type of values, including `array`, `slice`, `map`, `struct` and `func`. + +When using structs, be warned that only exported fields are accessible. However you can access exported fields in template with their lowercase names. For example, both `{{author.firstName}}` and `{{Author.FirstName}}` references give the same result, as long as `Author` and `FirstName` are exported struct fields. + +More, you can use the `handlebars` struct tag to specify a template variable name different from the struct field name. + +```go +package main + +import ( + "fmt" + + "github.com/aymerick/raymond" +) + +func main() { + source := `
+

By {{author.firstName}} {{author.lastName}}

+
{{body}}
+ +

Comments

+ + {{#each comments}} +

By {{author.firstName}} {{author.lastName}}

+
{{content}}
+ {{/each}} +
` + + type Person struct { + FirstName string + LastName string + } + + type Comment struct { + Author Person + Body string `handlebars:"content"` + } + + type Post struct { + Author Person + Body string + Comments []Comment + } + + ctx := Post{ + Person{"Jean", "Valjean"}, + "Life is difficult", + []Comment{ + Comment{ + Person{"Marcel", "Beliveau"}, + "LOL!", + }, + }, + } + + output := raymond.MustRender(source, ctx) + + fmt.Print(output) +} +``` + +Output: + +```html +
+

By Jean Valjean

+
Life is difficult
+ +

Comments

+ +

By Marcel Beliveau

+
LOL!
+
+``` + +## HTML Escaping + +By default, the result of a mustache expression is HTML escaped. Use the triple mustache `{{{` to output unescaped values. + +```go +source := `
+

{{title}}

+
+ {{{body}}} +
+
+` + +ctx := map[string]string{ + "title": "All about

Tags", + "body": "

This is a post about <p> tags

", +} + +tpl := raymond.MustParse(source) +result := tpl.MustExec(ctx) + +fmt.Print(result) +``` + +Output: + +```html +
+

All about <p> Tags

+
+

This is a post about <p> tags

+
+
+``` + +When returning HTML from a helper, you should return a `SafeString` if you don't want it to be escaped by default. When using `SafeString` all unknown or unsafe data should be manually escaped with the `Escape` method. + +```go +raymond.RegisterHelper("link", func(url, text string) raymond.SafeString { + return raymond.SafeString("" + raymond.Escape(text) + "") +}) + +tpl := raymond.MustParse("{{link url text}}") + +ctx := map[string]string{ + "url": "http://www.aymerick.com/", + "text": "This is a cool website", +} + +result := tpl.MustExec(ctx) +fmt.Print(result) +``` + +Output: + +```html +This is a <em>cool</em> website +``` + + +## Helpers + +Helpers can be accessed from any context in a template. You can register a helper with the `RegisterHelper` function. + +For example: + +```html +
+

By {{fullName author}}

+
{{body}}
+ +

Comments

+ + {{#each comments}} +

By {{fullName author}}

+
{{body}}
+ {{/each}} +
+``` + +With this context and helper: + +```go +ctx := map[string]interface{}{ + "author": map[string]string{"firstName": "Jean", "lastName": "Valjean"}, + "body": "Life is difficult", + "comments": []map[string]interface{}{{ + "author": map[string]string{"firstName": "Marcel", "lastName": "Beliveau"}, + "body": "LOL!", + }}, +} + +raymond.RegisterHelper("fullName", func(person map[string]string) string { + return person["firstName"] + " " + person["lastName"] +}) +``` + +Outputs: + +```html +
+

By Jean Valjean

+
Life is difficult
+ +

Comments

+ +

By Marcel Beliveau

+
LOL!
+
+``` + +Helper arguments can be any type. + +The following example uses structs instead of maps and produces the same output as the previous one: + +```html +
+

By {{fullName author}}

+
{{body}}
+ +

Comments

+ + {{#each comments}} +

By {{fullName author}}

+
{{body}}
+ {{/each}} +
+``` + +With this context and helper: + +```go +type Post struct { + Author Person + Body string + Comments []Comment +} + +type Person struct { + FirstName string + LastName string +} + +type Comment struct { + Author Person + Body string +} + +ctx := Post{ + Person{"Jean", "Valjean"}, + "Life is difficult", + []Comment{ + Comment{ + Person{"Marcel", "Beliveau"}, + "LOL!", + }, + }, +} + +RegisterHelper("fullName", func(person Person) string { + return person.FirstName + " " + person.LastName +}) +``` + + +### Template Helpers + +You can register a helper on a specific template, and in that case that helper will be available to that template only: + +```go +tpl := raymond.MustParse("User: {{fullName user.firstName user.lastName}}") + +tpl.RegisterHelper("fullName", func(firstName, lastName string) string { + return firstName + " " + lastName +}) +``` + + +### Built-In Helpers + +Those built-in helpers are available to all templates. + + +#### The `if` block helper + +You can use the `if` helper to conditionally render a block. If its argument returns `false`, `nil`, `0`, `""`, an empty array, an empty slice or an empty map, then raymond will not render the block. + +```html +
+ {{#if author}} +

{{firstName}} {{lastName}}

+ {{/if}} +
+``` + +When using a block expression, you can specify a template section to run if the expression returns a falsy value. That section, marked by `{{else}}` is called an "else section". + +```html +
+ {{#if author}} +

{{firstName}} {{lastName}}

+ {{else}} +

Unknown Author

+ {{/if}} +
+``` + +You can chain several blocks. For example that template: + +```html +{{#if isActive}} + Active +{{else if isInactive}} + Inactive +{{else}} + Unknown +{{/if}} +``` + +With that context: + +```go +ctx := map[string]interface{}{ + "isActive": false, + "isInactive": false, +} +``` + +Outputs: + +```html + Unknown +``` + + +#### The `unless` block helper + +You can use the `unless` helper as the inverse of the `if` helper. Its block will be rendered if the expression returns a falsy value. + +```html +
+ {{#unless license}} +

WARNING: This entry does not have a license!

+ {{/unless}} +
+``` + + +#### The `each` block helper + +You can iterate over an array, a slice, a map or a struct instance using this built-in `each` helper. Inside the block, you can use `this` to reference the element being iterated over. + +For example: + +```html +
    + {{#each people}} +
  • {{this}}
  • + {{/each}} +
+``` + +With this context: + +```go +map[string]interface{}{ + "people": []string{ + "Marcel", "Jean-Claude", "Yvette", + }, +} +``` + +Outputs: + +```html +
    +
  • Marcel
  • +
  • Jean-Claude
  • +
  • Yvette
  • +
+``` + +You can optionally provide an `{{else}}` section which will display only when the passed argument is an empty array, an empty slice or an empty map (a `struct` instance is never considered empty). + +```html +{{#each paragraphs}} +

{{this}}

+{{else}} +

No content

+{{/each}} +``` + +When looping through items in `each`, you can optionally reference the current loop index via `{{@index}}`. + +```html +{{#each array}} + {{@index}}: {{this}} +{{/each}} +``` + +Additionally for map and struct instance iteration, `{{@key}}` references the current map key or struct field name: + +```html +{{#each map}} + {{@key}}: {{this}} +{{/each}} +``` + +The first and last steps of iteration are noted via the `@first` and `@last` variables. + + +#### The `with` block helper + +You can shift the context for a section of a template by using the built-in `with` block helper. + +```html +
+

{{title}}

+ + {{#with author}} +

By {{firstName}} {{lastName}}

+ {{/with}} +
+``` + +With this context: + +```go +map[string]interface{}{ + "title": "My first post!", + "author": map[string]string{ + "firstName": "Jean", + "lastName": "Valjean", + }, +} +``` + +Outputs: + +```html +
+

My first post!

+ +

By Jean Valjean

+
+``` + +You can optionally provide an `{{else}}` section which will display only when the passed argument is falsy. + +```html +{{#with author}} +

{{name}}

+{{else}} +

No content

+{{/with}} +``` + + +#### The `lookup` helper + +The `lookup` helper allows for dynamic parameter resolution using handlebars variables. + +```html +{{#each bar}} + {{lookup ../foo @index}} +{{/each}} +``` + + +#### The `log` helper + +The `log` helper allows for logging while rendering a template. + +```html +{{log "Look at me!"}} +``` + +Note that the handlebars.js `@level` variable is not supported. + + +#### The `equal` helper + +The `equal` helper renders a block if the string version of both arguments are equals. + +For example that template: + +```html +{{#equal foo "bar"}}foo is bar{{/equal}} +{{#equal foo baz}}foo is the same as baz{{/equal}} +{{#equal nb 0}}nothing{{/equal}} +{{#equal nb 1}}there is one{{/equal}} +{{#equal nb "1"}}everything is stringified before comparison{{/equal}} +``` + +With that context: + +```go +ctx := map[string]interface{}{ + "foo": "bar", + "baz": "bar", + "nb": 1, +} +``` + +Outputs: + +```html +foo is bar +foo is the same as baz + +there is one +everything is stringified before comparison +``` + + +### Block Helpers + +Block helpers make it possible to define custom iterators and other functionality that can invoke the passed block with a new context. + + +#### Block Evaluation + +As an example, let's define a block helper that adds some markup to the wrapped text. + +```html +
+

{{title}}

+
+ {{#bold}}{{body}}{{/bold}} +
+
+``` + +The `bold` helper will add markup to make its text bold. + +```go +raymond.RegisterHelper("bold", func(options *raymond.Options) raymond.SafeString { + return raymond.SafeString(`
` + options.Fn() + "
") +}) +``` + +A helper evaluates the block content with current context by calling `options.Fn()`. + +If you want to evaluate the block with another context, then use `options.FnWith(ctx)`, like this french version of built-in `with` block helper: + +```go +raymond.RegisterHelper("avec", func(context interface{}, options *raymond.Options) string { + return options.FnWith(context) +}) +``` + +With that template: + +```html +{{#avec obj.text}}{{this}}{{/avec}} +``` + + +#### Conditional + +Let's write a french version of `if` block helper: + +```go +source := `{{#si yep}}YEP !{{/si}}` + +ctx := map[string]interface{}{"yep": true} + +raymond.RegisterHelper("si", func(conditional bool, options *raymond.Options) string { + if conditional { + return options.Fn() + } + return "" +}) +``` + +Note that as the first parameter of the helper is typed as `bool` an automatic conversion is made if corresponding context value is not a boolean. So this helper works with that context too: + +```go +ctx := map[string]interface{}{"yep": "message"} +``` + +Here, `"message"` is converted to `true` because it is an non-empty string. See `IsTrue()` function for more informations on boolean conversion. + + +#### Else Block Evaluation + +We can enhance the `si` block helper to evaluate the `else block` by calling `options.Inverse()` if conditional is false: + +```go +source := `{{#si yep}}YEP !{{else}}NOP !{{/si}}` + +ctx := map[string]interface{}{"yep": false} + +raymond.RegisterHelper("si", func(conditional bool, options *raymond.Options) string { + if conditional { + return options.Fn() + } + return options.Inverse() +}) +``` + +Outputs: +``` +NOP ! +``` + + +#### Block Parameters + +It's possible to receive named parameters from supporting helpers. + +```html +{{#each users as |user userId|}} + Id: {{userId}} Name: {{user.name}} +{{/each}} +``` + +In this particular example, `user` will have the same value as the current context and `userId` will have the index/key value for the iteration. + +This allows for nested helpers to avoid name conflicts. + +For example: + +```html +{{#each users as |user userId|}} + {{#each user.books as |book bookId|}} + User: {{userId}} Book: {{bookId}} + {{/each}} +{{/each}} +``` + +With this context: + +```go +ctx := map[string]interface{}{ + "users": map[string]interface{}{ + "marcel": map[string]interface{}{ + "books": map[string]interface{}{ + "book1": "My first book", + "book2": "My second book", + }, + }, + "didier": map[string]interface{}{ + "books": map[string]interface{}{ + "bookA": "Good book", + "bookB": "Bad book", + }, + }, + }, +} +``` + +Outputs: + +```html + User: marcel Book: book1 + User: marcel Book: book2 + User: didier Book: bookA + User: didier Book: bookB +``` + +As you can see, the second block parameter is the map key. When using structs, it is the struct field name. + +When using arrays and slices, the second parameter is element index: + +```go +ctx := map[string]interface{}{ + "users": []map[string]interface{}{ + { + "id": "marcel", + "books": []map[string]interface{}{ + {"id": "book1", "title": "My first book"}, + {"id": "book2", "title": "My second book"}, + }, + }, + { + "id": "didier", + "books": []map[string]interface{}{ + {"id": "bookA", "title": "Good book"}, + {"id": "bookB", "title": "Bad book"}, + }, + }, + }, +} +``` + +Outputs: + +```html + User: 0 Book: 0 + User: 0 Book: 1 + User: 1 Book: 0 + User: 1 Book: 1 +``` + + +### Helper Parameters + +When calling a helper in a template, raymond expects the same number of arguments as the number of helper function parameters. + +So this template: + +```html +{{add a}} +``` + +With this helper: + +```go +raymond.RegisterHelper("add", func(val1, val2 int) string { + return strconv.Itoa(val1 + val2) +}) +``` + +Will simply panics, because we call the helper with one argument whereas it expects two. + + +#### Automatic conversion + +Let's create a `concat` helper that expects two strings and concat them: + +```go +source := `{{concat a b}}` + +ctx := map[string]interface{}{ + "a": "Jean", + "b": "Valjean", +} + +raymond.RegisterHelper("concat", func(val1, val2 string) string { + return val1 + " " + val2 +}) +``` + +Everything goes well, two strings are passed as arguments to the helper that outputs: + +```html +Jean VALJEAN +``` + +But what happens if there is another type than `string` in the context ? For example: + +```go +ctx := map[string]interface{}{ + "a": 10, + "b": "Valjean", +} +``` + +Actually, raymond perfoms automatic string conversion. So because the first parameter of the helper is typed as `string`, the first argument will be converted from the `10` integer to `"10"`, and the helper outputs: + +```html +10 VALJEAN +``` + +Note that this kind of automatic conversion is done with `bool` type too, thanks to the `IsTrue()` function. + + +### Options Argument + +If a helper needs the `Options` argument, just add it at the end of helper parameters: + +```go +raymond.RegisterHelper("add", func(val1, val2 int, options *raymond.Options) string { + return strconv.Itoa(val1 + val2) + " " + options.ValueStr("bananas") +}) +``` + +Thanks to the `options` argument, helpers have access to the current evaluation context, to the `Hash` arguments, and they can manipulate the private data variables. + +The `Options` argument is even necessary for Block Helpers to evaluate block and "else block". + + +#### Context Values + +Helpers fetch current context values with `options.Value()` and `options.ValuesStr()`. + +`Value()` returns an `interface{}` and lets the helper do the type assertions whereas `ValueStr()` automatically converts the value to a `string`. + +For example: + +```go +source := `{{concat a b}}` + +ctx := map[string]interface{}{ + "a": "Marcel", + "b": "Beliveau", + "suffix": "FOREVER !", +} + +raymond.RegisterHelper("concat", func(val1, val2 string, options *raymond.Options) string { + return val1 + " " + val2 + " " + options.ValueStr("suffix") +}) +``` + +Outputs: + +```html +Marcel Beliveau FOREVER ! +``` + +Helpers can get the entire current context with `options.Ctx()` that returns an `interface{}`. + + +#### Helper Hash Arguments + +Helpers access hash arguments with `options.HashProp()` and `options.HashStr()`. + +`HashProp()` returns an `interface{}` and lets the helper do the type assertions whereas `HashStr()` automatically converts the value to a `string`. + +For example: + +```go +source := `{{concat suffix first=a second=b}}` + +ctx := map[string]interface{}{ + "a": "Marcel", + "b": "Beliveau", + "suffix": "FOREVER !", +} + +raymond.RegisterHelper("concat", func(suffix string, options *raymond.Options) string { + return options.HashStr("first") + " " + options.HashStr("second") + " " + suffix +}) +``` + +Outputs: + +```html +Marcel Beliveau FOREVER ! +``` + +Helpers can get the full hash with `options.Hash()` that returns a `map[string]interface{}`. + + +#### Private Data + +Helpers access private data variables with `options.Data()` and `options.DataStr()`. + +`Data()` returns an `interface{}` and lets the helper do the type assertions whereas `DataStr()` automatically converts the value to a `string`. + +Helpers can get the entire current data frame with `options.DataFrame()` that returns a `*DataFrame`. + +For helpers that need to inject their own private data frame, use `options.NewDataFrame()` to create the frame and `options.FnData()` to evaluate the block with that frame. + +For example: + +```go +source := `{{#voodoo kind=a}}Voodoo is {{@magix}}{{/voodoo}}` + +ctx := map[string]interface{}{ + "a": "awesome", +} + +raymond.RegisterHelper("voodoo", func(options *raymond.Options) string { + // create data frame with @magix data + frame := options.NewDataFrame() + frame.Set("magix", options.HashProp("kind")) + + // evaluates block with new data frame + return options.FnData(frame) +}) +``` + +Helpers that need to evaluate the block with a private data frame and a new context can call `options.FnCtxData()`. + + +### Utilites + +In addition to `Escape()`, raymond provides utility functions that can be usefull for helpers. + + +#### `Str()` + +`Str()` converts its parameter to a `string`. + +Booleans: + +```go +raymond.Str(3) + " foos and " + raymond.Str(-1.25) + " bars" +// Outputs: "3 foos and -1.25 bars" +``` + +Numbers: + +``` go +"everything is " + raymond.Str(true) + " and nothing is " + raymond.Str(false) +// Outputs: "everything is true and nothing is false" +``` + +Maps: + +```go +raymond.Str(map[string]string{"foo": "bar"}) +// Outputs: "map[foo:bar]" +``` + +Arrays and Slices: + +```go +raymond.Str([]interface{}{true, 10, "foo", 5, "bar"}) +// Outputs: "true10foo5bar" +``` + + +#### `IsTrue()` + +`IsTrue()` returns the truthy version of its parameter. + +It returns `false` when parameter is either: + + - an empty array + - an empty slice + - an empty map + - `""` + - `nil` + - `0` + - `false` + +For all others values, `IsTrue()` returns `true`. + + +## Context Functions + +In addition to helpers, lambdas found in context are evaluated. + +For example, that template and context: + +```go +source := "I {{feeling}} you" + +ctx := map[string]interface{}{ + "feeling": func() string { + rand.Seed(time.Now().UTC().UnixNano()) + + feelings := []string{"hate", "love"} + return feelings[rand.Intn(len(feelings))] + }, +} +``` + +Randomly renders `I hate you` or `I love you`. + +Those context functions behave like helper functions: they can be called with parameters and they can have an `Options` argument. + + +## Partials + +### Template Partials + +You can register template partials before execution: + +```go +tpl := raymond.MustParse("{{> foo}} baz") +tpl.RegisterPartial("foo", "bar") + +result := tpl.MustExec(nil) +fmt.Print(result) +``` + +Output: + +```html +bar baz +``` + +You can register several partials at once: + +```go +tpl := raymond.MustParse("{{> foo}} and {{> baz}}") +tpl.RegisterPartials(map[string]string{ + "foo": "bar", + "baz": "bat", +}) + +result := tpl.MustExec(nil) +fmt.Print(result) +``` + +Output: + +```html +bar and bat +``` + + +### Global Partials + +You can registers global partials that will be accessible by all templates: + +```go +raymond.RegisterPartial("foo", "bar") + +tpl := raymond.MustParse("{{> foo}} baz") +result := tpl.MustExec(nil) +fmt.Print(result) +``` + +Or: + +```go +raymond.RegisterPartials(map[string]string{ + "foo": "bar", + "baz": "bat", +}) + +tpl := raymond.MustParse("{{> foo}} and {{> baz}}") +result := tpl.MustExec(nil) +fmt.Print(result) +``` + + +### Dynamic Partials + +It's possible to dynamically select the partial to be executed by using sub expression syntax. + +For example, that template randomly evaluates the `foo` or `baz` partial: + +```go +tpl := raymond.MustParse("{{> (whichPartial) }}") +tpl.RegisterPartials(map[string]string{ + "foo": "bar", + "baz": "bat", +}) + +ctx := map[string]interface{}{ + "whichPartial": func() string { + rand.Seed(time.Now().UTC().UnixNano()) + + names := []string{"foo", "baz"} + return names[rand.Intn(len(names))] + }, +} + +result := tpl.MustExec(ctx) +fmt.Print(result) +``` + + +### Partial Contexts + +It's possible to execute partials on a custom context by passing in the context to the partial call. + +For example: + +```go +tpl := raymond.MustParse("User: {{> userDetails user }}") +tpl.RegisterPartial("userDetails", "{{firstname}} {{lastname}}") + +ctx := map[string]interface{}{ + "user": map[string]string{ + "firstname": "Jean", + "lastname": "Valjean", + }, +} + +result := tpl.MustExec(ctx) +fmt.Print(result) +``` + +Displays: + +```html +User: Jean Valjean +``` + + +### Partial Parameters + +Custom data can be passed to partials through hash parameters. + +For example: + +```go +tpl := raymond.MustParse("{{> myPartial name=hero }}") +tpl.RegisterPartial("myPartial", "My hero is {{name}}") + +ctx := map[string]interface{}{ + "hero": "Goldorak", +} + +result := tpl.MustExec(ctx) +fmt.Print(result) +``` + +Displays: + +```html +My hero is Goldorak +``` + + +## Utility Functions + +You can use following utility fuctions to parse and register partials from files: + +- `ParseFile()` - reads a file and return parsed template +- `Template.RegisterPartialFile()` - reads a file and registers its content as a partial with given name +- `Template.RegisterPartialFiles()` - reads several files and registers them as partials, the filename base is used as the partial name + + +## Mustache + +Handlebars is a superset of [mustache](https://mustache.github.io) but it differs on those points: + +- Alternative delimiters are not supported +- There is no recursive lookup + + +## Limitations + +These handlebars options are currently NOT implemented: + +- `compat` - enables recursive field lookup +- `knownHelpers` - list of helpers that are known to exist (truthy) at template execution time +- `knownHelpersOnly` - allows further optimizations based on the known helpers list +- `trackIds` - include the id names used to resolve parameters for helpers +- `noEscape` - disables HTML escaping globally +- `strict` - templates will throw rather than silently ignore missing fields +- `assumeObjects` - removes object existence checks when traversing paths +- `preventIndent` - disables the auto-indententation of nested partials +- `stringParams` - resolves a parameter to it's name if the value isn't present in the context stack + +These handlebars features are currently NOT implemented: + +- raw block content is not passed as a parameter to helper +- `blockHelperMissing` - helper called when a helper can not be directly resolved +- `helperMissing` - helper called when a potential helper expression was not found +- `@contextPath` - value set in `trackIds` mode that records the lookup path for the current context +- `@level` - log level + + +## Handlebars Lexer + +You should not use the lexer directly, but for your information here is an example: + +```go +package main + +import ( + "fmt" + + "github.com/aymerick/raymond/lexer" +) + +func main() { + source := "You know {{nothing}} John Snow" + + output := "" + + lex := lexer.Scan(source) + for { + // consume next token + token := lex.NextToken() + + output += fmt.Sprintf(" %s", token) + + // stops when all tokens have been consumed, or on error + if token.Kind == lexer.TokenEOF || token.Kind == lexer.TokenError { + break + } + } + + fmt.Print(output) +} +``` + +Outputs: + +``` +Content{"You know "} Open{"{{"} ID{"nothing"} Close{"}}"} Content{" John Snow"} EOF +``` + + +## Handlebars Parser + +You should not use the parser directly, but for your information here is an example: + +```go +package main + +import ( + "fmt" + + "github.com/aymerick/raymond/ast" + "github.com/aymerick/raymond/parser" +) + +fu nc main() { + source := "You know {{nothing}} John Snow" + + // parse template + program, err := parser.Parse(source) + if err != nil { + panic(err) + } + + // print AST + output := ast.Print(program) + + fmt.Print(output) +} +``` + +Outputs: + +``` +CONTENT[ 'You know ' ] +{{ PATH:nothing [] }} +CONTENT[ ' John Snow' ] +``` + + +## Test + +First, fetch mustache tests: + + $ git submodule update --init + +To run all tests: + + $ go test ./... + +To filter tests: + + $ go test -run="Partials" + +To run all test and all benchmarks: + + $ go test -bench . ./... + +To test with race detection: + + $ go test -race ./... + + +## References + + - + - + - + - + + +## Others Implementations + +- [handlebars.js](http://handlebarsjs.com) - javascript +- [handlebars.java](https://github.com/jknack/handlebars.java) - java +- [handlebars.rb](https://github.com/cowboyd/handlebars.rb) - ruby +- [handlebars.php](https://github.com/XaminProject/handlebars.php) - php +- [handlebars-objc](https://github.com/Bertrand/handlebars-objc) - Objective C +- [rumblebars](https://github.com/nicolas-cherel/rumblebars) - rust diff --git a/vendor/github.com/aymerick/raymond/VERSION b/vendor/github.com/aymerick/raymond/VERSION new file mode 100644 index 0000000..38f77a6 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/VERSION @@ -0,0 +1 @@ +2.0.1 diff --git a/vendor/github.com/aymerick/raymond/ast/node.go b/vendor/github.com/aymerick/raymond/ast/node.go new file mode 100644 index 0000000..aaef066 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/ast/node.go @@ -0,0 +1,785 @@ +// Package ast provides structures to represent a handlebars Abstract Syntax Tree, and a Visitor interface to visit that tree. +package ast + +import ( + "fmt" + "strconv" +) + +// References: +// - https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/ast.js +// - https://github.com/wycats/handlebars.js/blob/master/docs/compiler-api.md +// - https://github.com/golang/go/blob/master/src/text/template/parse/node.go + +// Node is an element in the AST. +type Node interface { + // node type + Type() NodeType + + // location of node in original input string + Location() Loc + + // string representation, used for debugging + String() string + + // accepts visitor + Accept(Visitor) interface{} +} + +// Visitor is the interface to visit an AST. +type Visitor interface { + VisitProgram(*Program) interface{} + + // statements + VisitMustache(*MustacheStatement) interface{} + VisitBlock(*BlockStatement) interface{} + VisitPartial(*PartialStatement) interface{} + VisitContent(*ContentStatement) interface{} + VisitComment(*CommentStatement) interface{} + + // expressions + VisitExpression(*Expression) interface{} + VisitSubExpression(*SubExpression) interface{} + VisitPath(*PathExpression) interface{} + + // literals + VisitString(*StringLiteral) interface{} + VisitBoolean(*BooleanLiteral) interface{} + VisitNumber(*NumberLiteral) interface{} + + // miscellaneous + VisitHash(*Hash) interface{} + VisitHashPair(*HashPair) interface{} +} + +// NodeType represents an AST Node type. +type NodeType int + +// Type returns itself, and permits struct includers to satisfy that part of Node interface. +func (t NodeType) Type() NodeType { + return t +} + +const ( + // NodeProgram is the program node + NodeProgram NodeType = iota + + // NodeMustache is the mustache statement node + NodeMustache + + // NodeBlock is the block statement node + NodeBlock + + // NodePartial is the partial statement node + NodePartial + + // NodeContent is the content statement node + NodeContent + + // NodeComment is the comment statement node + NodeComment + + // NodeExpression is the expression node + NodeExpression + + // NodeSubExpression is the subexpression node + NodeSubExpression + + // NodePath is the expression path node + NodePath + + // NodeBoolean is the literal boolean node + NodeBoolean + + // NodeNumber is the literal number node + NodeNumber + + // NodeString is the literal string node + NodeString + + // NodeHash is the hash node + NodeHash + + // NodeHashPair is the hash pair node + NodeHashPair +) + +// Loc represents the position of a parsed node in source file. +type Loc struct { + Pos int // Byte position + Line int // Line number +} + +// Location returns itself, and permits struct includers to satisfy that part of Node interface. +func (l Loc) Location() Loc { + return l +} + +// Strip describes node whitespace management. +type Strip struct { + Open bool + Close bool + + OpenStandalone bool + CloseStandalone bool + InlineStandalone bool +} + +// NewStrip instanciates a Strip for given open and close mustaches. +func NewStrip(openStr, closeStr string) *Strip { + return &Strip{ + Open: (len(openStr) > 2) && openStr[2] == '~', + Close: (len(closeStr) > 2) && closeStr[len(closeStr)-3] == '~', + } +} + +// NewStripForStr instanciates a Strip for given tag. +func NewStripForStr(str string) *Strip { + return &Strip{ + Open: (len(str) > 2) && str[2] == '~', + Close: (len(str) > 2) && str[len(str)-3] == '~', + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (s *Strip) String() string { + return fmt.Sprintf("Open: %t, Close: %t, OpenStandalone: %t, CloseStandalone: %t, InlineStandalone: %t", s.Open, s.Close, s.OpenStandalone, s.CloseStandalone, s.InlineStandalone) +} + +// +// Program +// + +// Program represents a program node. +type Program struct { + NodeType + Loc + + Body []Node // [ Statement ... ] + BlockParams []string + Chained bool + + // whitespace management + Strip *Strip +} + +// NewProgram instanciates a new program node. +func NewProgram(pos int, line int) *Program { + return &Program{ + NodeType: NodeProgram, + Loc: Loc{pos, line}, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *Program) String() string { + return fmt.Sprintf("Program{Pos: %d}", node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *Program) Accept(visitor Visitor) interface{} { + return visitor.VisitProgram(node) +} + +// AddStatement adds given statement to program. +func (node *Program) AddStatement(statement Node) { + node.Body = append(node.Body, statement) +} + +// +// Mustache Statement +// + +// MustacheStatement represents a mustache node. +type MustacheStatement struct { + NodeType + Loc + + Unescaped bool + Expression *Expression + + // whitespace management + Strip *Strip +} + +// NewMustacheStatement instanciates a new mustache node. +func NewMustacheStatement(pos int, line int, unescaped bool) *MustacheStatement { + return &MustacheStatement{ + NodeType: NodeMustache, + Loc: Loc{pos, line}, + Unescaped: unescaped, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *MustacheStatement) String() string { + return fmt.Sprintf("Mustache{Pos: %d}", node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *MustacheStatement) Accept(visitor Visitor) interface{} { + return visitor.VisitMustache(node) +} + +// +// Block Statement +// + +// BlockStatement represents a block node. +type BlockStatement struct { + NodeType + Loc + + Expression *Expression + + Program *Program + Inverse *Program + + // whitespace management + OpenStrip *Strip + InverseStrip *Strip + CloseStrip *Strip +} + +// NewBlockStatement instanciates a new block node. +func NewBlockStatement(pos int, line int) *BlockStatement { + return &BlockStatement{ + NodeType: NodeBlock, + Loc: Loc{pos, line}, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *BlockStatement) String() string { + return fmt.Sprintf("Block{Pos: %d}", node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *BlockStatement) Accept(visitor Visitor) interface{} { + return visitor.VisitBlock(node) +} + +// +// Partial Statement +// + +// PartialStatement represents a partial node. +type PartialStatement struct { + NodeType + Loc + + Name Node // PathExpression | SubExpression + Params []Node // [ Expression ... ] + Hash *Hash + + // whitespace management + Strip *Strip + Indent string +} + +// NewPartialStatement instanciates a new partial node. +func NewPartialStatement(pos int, line int) *PartialStatement { + return &PartialStatement{ + NodeType: NodePartial, + Loc: Loc{pos, line}, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *PartialStatement) String() string { + return fmt.Sprintf("Partial{Name:%s, Pos:%d}", node.Name, node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *PartialStatement) Accept(visitor Visitor) interface{} { + return visitor.VisitPartial(node) +} + +// +// Content Statement +// + +// ContentStatement represents a content node. +type ContentStatement struct { + NodeType + Loc + + Value string + Original string + + // whitespace management + RightStripped bool + LeftStripped bool +} + +// NewContentStatement instanciates a new content node. +func NewContentStatement(pos int, line int, val string) *ContentStatement { + return &ContentStatement{ + NodeType: NodeContent, + Loc: Loc{pos, line}, + + Value: val, + Original: val, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *ContentStatement) String() string { + return fmt.Sprintf("Content{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *ContentStatement) Accept(visitor Visitor) interface{} { + return visitor.VisitContent(node) +} + +// +// Comment Statement +// + +// CommentStatement represents a comment node. +type CommentStatement struct { + NodeType + Loc + + Value string + + // whitespace management + Strip *Strip +} + +// NewCommentStatement instanciates a new comment node. +func NewCommentStatement(pos int, line int, val string) *CommentStatement { + return &CommentStatement{ + NodeType: NodeComment, + Loc: Loc{pos, line}, + + Value: val, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *CommentStatement) String() string { + return fmt.Sprintf("Comment{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *CommentStatement) Accept(visitor Visitor) interface{} { + return visitor.VisitComment(node) +} + +// +// Expression +// + +// Expression represents an expression node. +type Expression struct { + NodeType + Loc + + Path Node // PathExpression | StringLiteral | BooleanLiteral | NumberLiteral + Params []Node // [ Expression ... ] + Hash *Hash +} + +// NewExpression instanciates a new expression node. +func NewExpression(pos int, line int) *Expression { + return &Expression{ + NodeType: NodeExpression, + Loc: Loc{pos, line}, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *Expression) String() string { + return fmt.Sprintf("Expr{Path:%s, Pos:%d}", node.Path, node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *Expression) Accept(visitor Visitor) interface{} { + return visitor.VisitExpression(node) +} + +// HelperName returns helper name, or an empty string if this expression can't be a helper. +func (node *Expression) HelperName() string { + path, ok := node.Path.(*PathExpression) + if !ok { + return "" + } + + if path.Data || (len(path.Parts) != 1) || (path.Depth > 0) || path.Scoped { + return "" + } + + return path.Parts[0] +} + +// FieldPath returns path expression representing a field path, or nil if this is not a field path. +func (node *Expression) FieldPath() *PathExpression { + path, ok := node.Path.(*PathExpression) + if !ok { + return nil + } + + return path +} + +// LiteralStr returns the string representation of literal value, with a boolean set to false if this is not a literal. +func (node *Expression) LiteralStr() (string, bool) { + return LiteralStr(node.Path) +} + +// Canonical returns the canonical form of expression node as a string. +func (node *Expression) Canonical() string { + if str, ok := HelperNameStr(node.Path); ok { + return str + } + + return "" +} + +// HelperNameStr returns the string representation of a helper name, with a boolean set to false if this is not a valid helper name. +// +// helperName : path | dataName | STRING | NUMBER | BOOLEAN | UNDEFINED | NULL +func HelperNameStr(node Node) (string, bool) { + // PathExpression + if str, ok := PathExpressionStr(node); ok { + return str, ok + } + + // Literal + if str, ok := LiteralStr(node); ok { + return str, ok + } + + return "", false +} + +// PathExpressionStr returns the string representation of path expression value, with a boolean set to false if this is not a path expression. +func PathExpressionStr(node Node) (string, bool) { + if path, ok := node.(*PathExpression); ok { + result := path.Original + + // "[foo bar]"" => "foo bar" + if (len(result) >= 2) && (result[0] == '[') && (result[len(result)-1] == ']') { + result = result[1 : len(result)-1] + } + + return result, true + } + + return "", false +} + +// LiteralStr returns the string representation of literal value, with a boolean set to false if this is not a literal. +func LiteralStr(node Node) (string, bool) { + if lit, ok := node.(*StringLiteral); ok { + return lit.Value, true + } + + if lit, ok := node.(*BooleanLiteral); ok { + return lit.Canonical(), true + } + + if lit, ok := node.(*NumberLiteral); ok { + return lit.Canonical(), true + } + + return "", false +} + +// +// SubExpression +// + +// SubExpression represents a subexpression node. +type SubExpression struct { + NodeType + Loc + + Expression *Expression +} + +// NewSubExpression instanciates a new subexpression node. +func NewSubExpression(pos int, line int) *SubExpression { + return &SubExpression{ + NodeType: NodeSubExpression, + Loc: Loc{pos, line}, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *SubExpression) String() string { + return fmt.Sprintf("Sexp{Path:%s, Pos:%d}", node.Expression.Path, node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *SubExpression) Accept(visitor Visitor) interface{} { + return visitor.VisitSubExpression(node) +} + +// +// Path Expression +// + +// PathExpression represents a path expression node. +type PathExpression struct { + NodeType + Loc + + Original string + Depth int + Parts []string + Data bool + Scoped bool +} + +// NewPathExpression instanciates a new path expression node. +func NewPathExpression(pos int, line int, data bool) *PathExpression { + result := &PathExpression{ + NodeType: NodePath, + Loc: Loc{pos, line}, + + Data: data, + } + + if data { + result.Original = "@" + } + + return result +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *PathExpression) String() string { + return fmt.Sprintf("Path{Original:'%s', Pos:%d}", node.Original, node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *PathExpression) Accept(visitor Visitor) interface{} { + return visitor.VisitPath(node) +} + +// Part adds path part. +func (node *PathExpression) Part(part string) { + node.Original += part + + switch part { + case "..": + node.Depth++ + node.Scoped = true + case ".", "this": + node.Scoped = true + default: + node.Parts = append(node.Parts, part) + } +} + +// Sep adds path separator. +func (node *PathExpression) Sep(separator string) { + node.Original += separator +} + +// IsDataRoot returns true if path expression is @root. +func (node *PathExpression) IsDataRoot() bool { + return node.Data && (node.Parts[0] == "root") +} + +// +// String Literal +// + +// StringLiteral represents a string node. +type StringLiteral struct { + NodeType + Loc + + Value string +} + +// NewStringLiteral instanciates a new string node. +func NewStringLiteral(pos int, line int, val string) *StringLiteral { + return &StringLiteral{ + NodeType: NodeString, + Loc: Loc{pos, line}, + + Value: val, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *StringLiteral) String() string { + return fmt.Sprintf("String{Value:'%s', Pos:%d}", node.Value, node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *StringLiteral) Accept(visitor Visitor) interface{} { + return visitor.VisitString(node) +} + +// +// Boolean Literal +// + +// BooleanLiteral represents a boolean node. +type BooleanLiteral struct { + NodeType + Loc + + Value bool + Original string +} + +// NewBooleanLiteral instanciates a new boolean node. +func NewBooleanLiteral(pos int, line int, val bool, original string) *BooleanLiteral { + return &BooleanLiteral{ + NodeType: NodeBoolean, + Loc: Loc{pos, line}, + + Value: val, + Original: original, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *BooleanLiteral) String() string { + return fmt.Sprintf("Boolean{Value:%s, Pos:%d}", node.Canonical(), node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *BooleanLiteral) Accept(visitor Visitor) interface{} { + return visitor.VisitBoolean(node) +} + +// Canonical returns the canonical form of boolean node as a string (ie. "true" | "false"). +func (node *BooleanLiteral) Canonical() string { + if node.Value { + return "true" + } + + return "false" +} + +// +// Number Literal +// + +// NumberLiteral represents a number node. +type NumberLiteral struct { + NodeType + Loc + + Value float64 + IsInt bool + Original string +} + +// NewNumberLiteral instanciates a new number node. +func NewNumberLiteral(pos int, line int, val float64, isInt bool, original string) *NumberLiteral { + return &NumberLiteral{ + NodeType: NodeNumber, + Loc: Loc{pos, line}, + + Value: val, + IsInt: isInt, + Original: original, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *NumberLiteral) String() string { + return fmt.Sprintf("Number{Value:%s, Pos:%d}", node.Canonical(), node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *NumberLiteral) Accept(visitor Visitor) interface{} { + return visitor.VisitNumber(node) +} + +// Canonical returns the canonical form of number node as a string (eg: "12", "-1.51"). +func (node *NumberLiteral) Canonical() string { + prec := -1 + if node.IsInt { + prec = 0 + } + return strconv.FormatFloat(node.Value, 'f', prec, 64) +} + +// Number returns an integer or a float. +func (node *NumberLiteral) Number() interface{} { + if node.IsInt { + return int(node.Value) + } + + return node.Value +} + +// +// Hash +// + +// Hash represents a hash node. +type Hash struct { + NodeType + Loc + + Pairs []*HashPair +} + +// NewHash instanciates a new hash node. +func NewHash(pos int, line int) *Hash { + return &Hash{ + NodeType: NodeHash, + Loc: Loc{pos, line}, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *Hash) String() string { + result := fmt.Sprintf("Hash{[%d", node.Loc.Pos) + + for i, p := range node.Pairs { + if i > 0 { + result += ", " + } + result += p.String() + } + + return result + fmt.Sprintf("], Pos:%d}", node.Loc.Pos) +} + +// Accept is the receiver entry point for visitors. +func (node *Hash) Accept(visitor Visitor) interface{} { + return visitor.VisitHash(node) +} + +// +// HashPair +// + +// HashPair represents a hash pair node. +type HashPair struct { + NodeType + Loc + + Key string + Val Node // Expression +} + +// NewHashPair instanciates a new hash pair node. +func NewHashPair(pos int, line int) *HashPair { + return &HashPair{ + NodeType: NodeHashPair, + Loc: Loc{pos, line}, + } +} + +// String returns a string representation of receiver that can be used for debugging. +func (node *HashPair) String() string { + return node.Key + "=" + node.Val.String() +} + +// Accept is the receiver entry point for visitors. +func (node *HashPair) Accept(visitor Visitor) interface{} { + return visitor.VisitHashPair(node) +} diff --git a/vendor/github.com/aymerick/raymond/ast/print.go b/vendor/github.com/aymerick/raymond/ast/print.go new file mode 100644 index 0000000..133ae6e --- /dev/null +++ b/vendor/github.com/aymerick/raymond/ast/print.go @@ -0,0 +1,279 @@ +package ast + +import ( + "fmt" + "strings" +) + +// printVisitor implements the Visitor interface to print a AST. +type printVisitor struct { + buf string + depth int + + original bool + inBlock bool +} + +func newPrintVisitor() *printVisitor { + return &printVisitor{} +} + +// Print returns a string representation of given AST, that can be used for debugging purpose. +func Print(node Node) string { + visitor := newPrintVisitor() + node.Accept(visitor) + return visitor.output() +} + +func (v *printVisitor) output() string { + return v.buf +} + +func (v *printVisitor) indent() { + for i := 0; i < v.depth; { + v.buf += " " + i++ + } +} + +func (v *printVisitor) str(val string) { + v.buf += val +} + +func (v *printVisitor) nl() { + v.str("\n") +} + +func (v *printVisitor) line(val string) { + v.indent() + v.str(val) + v.nl() +} + +// +// Visitor interface +// + +// Statements + +// VisitProgram implements corresponding Visitor interface method +func (v *printVisitor) VisitProgram(node *Program) interface{} { + if len(node.BlockParams) > 0 { + v.line("BLOCK PARAMS: [ " + strings.Join(node.BlockParams, " ") + " ]") + } + + for _, n := range node.Body { + n.Accept(v) + } + + return nil +} + +// VisitMustache implements corresponding Visitor interface method +func (v *printVisitor) VisitMustache(node *MustacheStatement) interface{} { + v.indent() + v.str("{{ ") + + node.Expression.Accept(v) + + v.str(" }}") + v.nl() + + return nil +} + +// VisitBlock implements corresponding Visitor interface method +func (v *printVisitor) VisitBlock(node *BlockStatement) interface{} { + v.inBlock = true + + v.line("BLOCK:") + v.depth++ + + node.Expression.Accept(v) + + if node.Program != nil { + v.line("PROGRAM:") + v.depth++ + node.Program.Accept(v) + v.depth-- + } + + if node.Inverse != nil { + // if node.Program != nil { + // v.depth++ + // } + + v.line("{{^}}") + v.depth++ + node.Inverse.Accept(v) + v.depth-- + + // if node.Program != nil { + // v.depth-- + // } + } + + v.inBlock = false + + return nil +} + +// VisitPartial implements corresponding Visitor interface method +func (v *printVisitor) VisitPartial(node *PartialStatement) interface{} { + v.indent() + v.str("{{> PARTIAL:") + + v.original = true + node.Name.Accept(v) + v.original = false + + if len(node.Params) > 0 { + v.str(" ") + node.Params[0].Accept(v) + } + + // hash + if node.Hash != nil { + v.str(" ") + node.Hash.Accept(v) + } + + v.str(" }}") + v.nl() + + return nil +} + +// VisitContent implements corresponding Visitor interface method +func (v *printVisitor) VisitContent(node *ContentStatement) interface{} { + v.line("CONTENT[ '" + node.Value + "' ]") + + return nil +} + +// VisitComment implements corresponding Visitor interface method +func (v *printVisitor) VisitComment(node *CommentStatement) interface{} { + v.line("{{! '" + node.Value + "' }}") + + return nil +} + +// Expressions + +// VisitExpression implements corresponding Visitor interface method +func (v *printVisitor) VisitExpression(node *Expression) interface{} { + if v.inBlock { + v.indent() + } + + // path + node.Path.Accept(v) + + // params + v.str(" [") + for i, n := range node.Params { + if i > 0 { + v.str(", ") + } + n.Accept(v) + } + v.str("]") + + // hash + if node.Hash != nil { + v.str(" ") + node.Hash.Accept(v) + } + + if v.inBlock { + v.nl() + } + + return nil +} + +// VisitSubExpression implements corresponding Visitor interface method +func (v *printVisitor) VisitSubExpression(node *SubExpression) interface{} { + node.Expression.Accept(v) + + return nil +} + +// VisitPath implements corresponding Visitor interface method +func (v *printVisitor) VisitPath(node *PathExpression) interface{} { + if v.original { + v.str(node.Original) + } else { + path := strings.Join(node.Parts, "/") + + result := "" + if node.Data { + result += "@" + } + + v.str(result + "PATH:" + path) + } + + return nil +} + +// Literals + +// VisitString implements corresponding Visitor interface method +func (v *printVisitor) VisitString(node *StringLiteral) interface{} { + if v.original { + v.str(node.Value) + } else { + v.str("\"" + node.Value + "\"") + } + + return nil +} + +// VisitBoolean implements corresponding Visitor interface method +func (v *printVisitor) VisitBoolean(node *BooleanLiteral) interface{} { + if v.original { + v.str(node.Original) + } else { + v.str(fmt.Sprintf("BOOLEAN{%s}", node.Canonical())) + } + + return nil +} + +// VisitNumber implements corresponding Visitor interface method +func (v *printVisitor) VisitNumber(node *NumberLiteral) interface{} { + if v.original { + v.str(node.Original) + } else { + v.str(fmt.Sprintf("NUMBER{%s}", node.Canonical())) + } + + return nil +} + +// Miscellaneous + +// VisitHash implements corresponding Visitor interface method +func (v *printVisitor) VisitHash(node *Hash) interface{} { + v.str("HASH{") + + for i, p := range node.Pairs { + if i > 0 { + v.str(", ") + } + p.Accept(v) + } + + v.str("}") + + return nil +} + +// VisitHashPair implements corresponding Visitor interface method +func (v *printVisitor) VisitHashPair(node *HashPair) interface{} { + v.str(node.Key + "=") + node.Val.Accept(v) + + return nil +} diff --git a/vendor/github.com/aymerick/raymond/data_frame.go b/vendor/github.com/aymerick/raymond/data_frame.go new file mode 100644 index 0000000..ce63218 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/data_frame.go @@ -0,0 +1,95 @@ +package raymond + +import "reflect" + +// DataFrame represents a private data frame. +// +// Cf. private variables documentation at: http://handlebarsjs.com/block_helpers.html +type DataFrame struct { + parent *DataFrame + data map[string]interface{} +} + +// NewDataFrame instanciates a new private data frame. +func NewDataFrame() *DataFrame { + return &DataFrame{ + data: make(map[string]interface{}), + } +} + +// Copy instanciates a new private data frame with receiver as parent. +func (p *DataFrame) Copy() *DataFrame { + result := NewDataFrame() + + for k, v := range p.data { + result.data[k] = v + } + + result.parent = p + + return result +} + +// newIterDataFrame instanciates a new private data frame with receiver as parent and with iteration data set (@index, @key, @first, @last) +func (p *DataFrame) newIterDataFrame(length int, i int, key interface{}) *DataFrame { + result := p.Copy() + + result.Set("index", i) + result.Set("key", key) + result.Set("first", i == 0) + result.Set("last", i == length-1) + + return result +} + +// Set sets a data value. +func (p *DataFrame) Set(key string, val interface{}) { + p.data[key] = val +} + +// Get gets a data value. +func (p *DataFrame) Get(key string) interface{} { + return p.find([]string{key}) +} + +// find gets a deep data value +// +// @todo This is NOT consistent with the way we resolve data in template (cf. `evalDataPathExpression()`) ! FIX THAT ! +func (p *DataFrame) find(parts []string) interface{} { + data := p.data + + for i, part := range parts { + val := data[part] + if val == nil { + return nil + } + + if i == len(parts)-1 { + // found + return val + } + + valValue := reflect.ValueOf(val) + if valValue.Kind() != reflect.Map { + // not found + return nil + } + + // continue + data = mapStringInterface(valValue) + } + + // not found + return nil +} + +// mapStringInterface converts any `map` to `map[string]interface{}` +func mapStringInterface(value reflect.Value) map[string]interface{} { + result := make(map[string]interface{}) + + for _, key := range value.MapKeys() { + result[strValue(key)] = value.MapIndex(key).Interface() + } + + return result +} diff --git a/vendor/github.com/aymerick/raymond/escape.go b/vendor/github.com/aymerick/raymond/escape.go new file mode 100644 index 0000000..6a0363c --- /dev/null +++ b/vendor/github.com/aymerick/raymond/escape.go @@ -0,0 +1,65 @@ +package raymond + +import ( + "bytes" + "strings" +) + +// +// That whole file is borrowed from https://github.com/golang/go/tree/master/src/html/escape.go +// +// With changes: +// ' => ' +// " => " +// +// To stay in sync with JS implementation, and make mustache tests pass. +// + +type writer interface { + WriteString(string) (int, error) +} + +const escapedChars = `&'<>"` + +func escape(w writer, s string) error { + i := strings.IndexAny(s, escapedChars) + for i != -1 { + if _, err := w.WriteString(s[:i]); err != nil { + return err + } + var esc string + switch s[i] { + case '&': + esc = "&" + case '\'': + esc = "'" + case '<': + esc = "<" + case '>': + esc = ">" + case '"': + esc = """ + default: + panic("unrecognized escape character") + } + s = s[i+1:] + if _, err := w.WriteString(esc); err != nil { + return err + } + i = strings.IndexAny(s, escapedChars) + } + _, err := w.WriteString(s) + return err +} + +// Escape escapes special HTML characters. +// +// It can be used by helpers that return a SafeString and that need to escape some content by themselves. +func Escape(s string) string { + if strings.IndexAny(s, escapedChars) == -1 { + return s + } + var buf bytes.Buffer + escape(&buf, s) + return buf.String() +} diff --git a/vendor/github.com/aymerick/raymond/eval.go b/vendor/github.com/aymerick/raymond/eval.go new file mode 100644 index 0000000..7683f4e --- /dev/null +++ b/vendor/github.com/aymerick/raymond/eval.go @@ -0,0 +1,1005 @@ +package raymond + +import ( + "bytes" + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/aymerick/raymond/ast" +) + +var ( + // @note borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go + errorType = reflect.TypeOf((*error)(nil)).Elem() + fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem() + + zero reflect.Value +) + +// evalVisitor evaluates a handlebars template with context +type evalVisitor struct { + tpl *Template + + // contexts stack + ctx []reflect.Value + + // current data frame (chained with parent) + dataFrame *DataFrame + + // block parameters stack + blockParams []map[string]interface{} + + // block statements stack + blocks []*ast.BlockStatement + + // expressions stack + exprs []*ast.Expression + + // memoize expressions that were function calls + exprFunc map[*ast.Expression]bool + + // used for info on panic + curNode ast.Node +} + +// NewEvalVisitor instanciate a new evaluation visitor with given context and initial private data frame +// +// If privData is nil, then a default data frame is created +func newEvalVisitor(tpl *Template, ctx interface{}, privData *DataFrame) *evalVisitor { + frame := privData + if frame == nil { + frame = NewDataFrame() + } + + return &evalVisitor{ + tpl: tpl, + ctx: []reflect.Value{reflect.ValueOf(ctx)}, + dataFrame: frame, + exprFunc: make(map[*ast.Expression]bool), + } +} + +// at sets current node +func (v *evalVisitor) at(node ast.Node) { + v.curNode = node +} + +// +// Contexts stack +// + +// pushCtx pushes new context to the stack +func (v *evalVisitor) pushCtx(ctx reflect.Value) { + v.ctx = append(v.ctx, ctx) +} + +// popCtx pops last context from stack +func (v *evalVisitor) popCtx() reflect.Value { + if len(v.ctx) == 0 { + return zero + } + + var result reflect.Value + result, v.ctx = v.ctx[len(v.ctx)-1], v.ctx[:len(v.ctx)-1] + + return result +} + +// rootCtx returns root context +func (v *evalVisitor) rootCtx() reflect.Value { + return v.ctx[0] +} + +// curCtx returns current context +func (v *evalVisitor) curCtx() reflect.Value { + return v.ancestorCtx(0) +} + +// ancestorCtx returns ancestor context +func (v *evalVisitor) ancestorCtx(depth int) reflect.Value { + index := len(v.ctx) - 1 - depth + if index < 0 { + return zero + } + + return v.ctx[index] +} + +// +// Private data frame +// + +// setDataFrame sets new data frame +func (v *evalVisitor) setDataFrame(frame *DataFrame) { + v.dataFrame = frame +} + +// popDataFrame sets back parent data frame +func (v *evalVisitor) popDataFrame() { + v.dataFrame = v.dataFrame.parent +} + +// +// Block Parameters stack +// + +// pushBlockParams pushes new block params to the stack +func (v *evalVisitor) pushBlockParams(params map[string]interface{}) { + v.blockParams = append(v.blockParams, params) +} + +// popBlockParams pops last block params from stack +func (v *evalVisitor) popBlockParams() map[string]interface{} { + var result map[string]interface{} + + if len(v.blockParams) == 0 { + return result + } + + result, v.blockParams = v.blockParams[len(v.blockParams)-1], v.blockParams[:len(v.blockParams)-1] + return result +} + +// blockParam iterates on stack to find given block parameter, and returns its value or nil if not founc +func (v *evalVisitor) blockParam(name string) interface{} { + for i := len(v.blockParams) - 1; i >= 0; i-- { + for k, v := range v.blockParams[i] { + if name == k { + return v + } + } + } + + return nil +} + +// +// Blocks stack +// + +// pushBlock pushes new block statement to stack +func (v *evalVisitor) pushBlock(block *ast.BlockStatement) { + v.blocks = append(v.blocks, block) +} + +// popBlock pops last block statement from stack +func (v *evalVisitor) popBlock() *ast.BlockStatement { + if len(v.blocks) == 0 { + return nil + } + + var result *ast.BlockStatement + result, v.blocks = v.blocks[len(v.blocks)-1], v.blocks[:len(v.blocks)-1] + + return result +} + +// curBlock returns current block statement +func (v *evalVisitor) curBlock() *ast.BlockStatement { + if len(v.blocks) == 0 { + return nil + } + + return v.blocks[len(v.blocks)-1] +} + +// +// Expressions stack +// + +// pushExpr pushes new expression to stack +func (v *evalVisitor) pushExpr(expression *ast.Expression) { + v.exprs = append(v.exprs, expression) +} + +// popExpr pops last expression from stack +func (v *evalVisitor) popExpr() *ast.Expression { + if len(v.exprs) == 0 { + return nil + } + + var result *ast.Expression + result, v.exprs = v.exprs[len(v.exprs)-1], v.exprs[:len(v.exprs)-1] + + return result +} + +// curExpr returns current expression +func (v *evalVisitor) curExpr() *ast.Expression { + if len(v.exprs) == 0 { + return nil + } + + return v.exprs[len(v.exprs)-1] +} + +// +// Error functions +// + +// errPanic panics +func (v *evalVisitor) errPanic(err error) { + panic(fmt.Errorf("Evaluation error: %s\nCurrent node:\n\t%s", err, v.curNode)) +} + +// errorf panics with a custom message +func (v *evalVisitor) errorf(format string, args ...interface{}) { + v.errPanic(fmt.Errorf(format, args...)) +} + +// +// Evaluation +// + +// evalProgram eEvaluates program with given context and returns string result +func (v *evalVisitor) evalProgram(program *ast.Program, ctx interface{}, data *DataFrame, key interface{}) string { + blockParams := make(map[string]interface{}) + + // compute block params + if len(program.BlockParams) > 0 { + blockParams[program.BlockParams[0]] = ctx + } + + if (len(program.BlockParams) > 1) && (key != nil) { + blockParams[program.BlockParams[1]] = key + } + + // push contexts + if len(blockParams) > 0 { + v.pushBlockParams(blockParams) + } + + ctxVal := reflect.ValueOf(ctx) + if ctxVal.IsValid() { + v.pushCtx(ctxVal) + } + + if data != nil { + v.setDataFrame(data) + } + + // evaluate program + result, _ := program.Accept(v).(string) + + // pop contexts + if data != nil { + v.popDataFrame() + } + + if ctxVal.IsValid() { + v.popCtx() + } + + if len(blockParams) > 0 { + v.popBlockParams() + } + + return result +} + +// evalPath evaluates all path parts with given context +func (v *evalVisitor) evalPath(ctx reflect.Value, parts []string, exprRoot bool) (reflect.Value, bool) { + partResolved := false + + for i := 0; i < len(parts); i++ { + part := parts[i] + + // "[foo bar]"" => "foo bar" + if (len(part) >= 2) && (part[0] == '[') && (part[len(part)-1] == ']') { + part = part[1 : len(part)-1] + } + + ctx = v.evalField(ctx, part, exprRoot) + if !ctx.IsValid() { + break + } + + // we resolved at least one part of path + partResolved = true + } + + return ctx, partResolved +} + +// evalField evaluates field with given context +func (v *evalVisitor) evalField(ctx reflect.Value, fieldName string, exprRoot bool) reflect.Value { + result := zero + + ctx, _ = indirect(ctx) + if !ctx.IsValid() { + return result + } + + // check if this is a method call + result, isMeth := v.evalMethod(ctx, fieldName, exprRoot) + if !isMeth { + switch ctx.Kind() { + case reflect.Struct: + // example: firstName => FirstName + expFieldName := strings.Title(fieldName) + + // check if struct have this field and that it is exported + if tField, ok := ctx.Type().FieldByName(expFieldName); ok && (tField.PkgPath == "") { + // struct field + result = ctx.FieldByIndex(tField.Index) + break + } + + // attempts to find template variable name as a struct tag + result = v.evalStructTag(ctx, fieldName) + case reflect.Map: + nameVal := reflect.ValueOf(fieldName) + if nameVal.Type().AssignableTo(ctx.Type().Key()) { + // map key + result = ctx.MapIndex(nameVal) + } + case reflect.Array, reflect.Slice: + if i, err := strconv.Atoi(fieldName); (err == nil) && (i < ctx.Len()) { + result = ctx.Index(i) + } + } + } + + // check if result is a function + result, _ = indirect(result) + if result.Kind() == reflect.Func { + result = v.evalFieldFunc(fieldName, result, exprRoot) + } + + return result +} + +// evalFieldFunc tries to evaluate given method name, and a boolean to indicate if this was a method call +func (v *evalVisitor) evalMethod(ctx reflect.Value, name string, exprRoot bool) (reflect.Value, bool) { + if ctx.Kind() != reflect.Interface && ctx.CanAddr() { + ctx = ctx.Addr() + } + + method := ctx.MethodByName(name) + if !method.IsValid() { + // example: subject() => Subject() + method = ctx.MethodByName(strings.Title(name)) + } + + if !method.IsValid() { + return zero, false + } + + return v.evalFieldFunc(name, method, exprRoot), true +} + +// evalFieldFunc evaluates given function +func (v *evalVisitor) evalFieldFunc(name string, funcVal reflect.Value, exprRoot bool) reflect.Value { + ensureValidHelper(name, funcVal) + + var options *Options + if exprRoot { + // create function arg with all params/hash + expr := v.curExpr() + options = v.helperOptions(expr) + + // ok, that expression was a function call + v.exprFunc[expr] = true + } else { + // we are not at root of expression, so we are a parameter... and we don't like + // infinite loops caused by trying to parse ourself forever + options = newEmptyOptions(v) + } + + return v.callFunc(name, funcVal, options) +} + +// evalStructTag checks for the existence of a struct tag containing the +// name of the variable in the template. This allows for a template variable to +// be separated from the field in the struct. +func (v *evalVisitor) evalStructTag(ctx reflect.Value, name string) reflect.Value { + val := reflect.ValueOf(ctx.Interface()) + + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + tag := field.Tag.Get("handlebars") + if tag == name { + return val.Field(i) + } + } + + return zero +} + +// findBlockParam returns node's block parameter +func (v *evalVisitor) findBlockParam(node *ast.PathExpression) (string, interface{}) { + if len(node.Parts) > 0 { + name := node.Parts[0] + if value := v.blockParam(name); value != nil { + return name, value + } + } + + return "", nil +} + +// evalPathExpression evaluates a path expression +func (v *evalVisitor) evalPathExpression(node *ast.PathExpression, exprRoot bool) interface{} { + var result interface{} + + if name, value := v.findBlockParam(node); value != nil { + // block parameter value + + // We push a new context so we can evaluate the path expression (note: this may be a bad idea). + // + // Example: + // {{#foo as |bar|}} + // {{bar.baz}} + // {{/foo}} + // + // With data: + // {"foo": {"baz": "bat"}} + newCtx := map[string]interface{}{name: value} + + v.pushCtx(reflect.ValueOf(newCtx)) + result = v.evalCtxPathExpression(node, exprRoot) + v.popCtx() + } else { + ctxTried := false + + if node.IsDataRoot() { + // context path + result = v.evalCtxPathExpression(node, exprRoot) + + ctxTried = true + } + + if (result == nil) && node.Data { + // if it is @root, then we tried to evaluate with root context but nothing was found + // so let's try with private data + + // private data + result = v.evalDataPathExpression(node, exprRoot) + } + + if (result == nil) && !ctxTried { + // context path + result = v.evalCtxPathExpression(node, exprRoot) + } + } + + return result +} + +// evalDataPathExpression evaluates a private data path expression +func (v *evalVisitor) evalDataPathExpression(node *ast.PathExpression, exprRoot bool) interface{} { + // find data frame + frame := v.dataFrame + for i := node.Depth; i > 0; i-- { + if frame.parent == nil { + return nil + } + frame = frame.parent + } + + // resolve data + // @note Can be changed to v.evalCtx() as context can't be an array + result, _ := v.evalCtxPath(reflect.ValueOf(frame.data), node.Parts, exprRoot) + return result +} + +// evalCtxPathExpression evaluates a context path expression +func (v *evalVisitor) evalCtxPathExpression(node *ast.PathExpression, exprRoot bool) interface{} { + v.at(node) + + if node.IsDataRoot() { + // `@root` - remove the first part + parts := node.Parts[1:len(node.Parts)] + + result, _ := v.evalCtxPath(v.rootCtx(), parts, exprRoot) + return result + } + + return v.evalDepthPath(node.Depth, node.Parts, exprRoot) +} + +// evalDepthPath iterates on contexts, starting at given depth, until there is one that resolve given path parts +func (v *evalVisitor) evalDepthPath(depth int, parts []string, exprRoot bool) interface{} { + var result interface{} + partResolved := false + + ctx := v.ancestorCtx(depth) + + for (result == nil) && ctx.IsValid() && (depth <= len(v.ctx) && !partResolved) { + // try with context + result, partResolved = v.evalCtxPath(ctx, parts, exprRoot) + + // As soon as we find the first part of a path, we must not try to resolve with parent context if result is finally `nil` + // Reference: "Dotted Names - Context Precedence" mustache test + if !partResolved && (result == nil) { + // try with previous context + depth++ + ctx = v.ancestorCtx(depth) + } + } + + return result +} + +// evalCtxPath evaluates path with given context +func (v *evalVisitor) evalCtxPath(ctx reflect.Value, parts []string, exprRoot bool) (interface{}, bool) { + var result interface{} + partResolved := false + + switch ctx.Kind() { + case reflect.Array, reflect.Slice: + // Array context + var results []interface{} + + for i := 0; i < ctx.Len(); i++ { + value, _ := v.evalPath(ctx.Index(i), parts, exprRoot) + if value.IsValid() { + results = append(results, value.Interface()) + } + } + + result = results + default: + // NOT array context + var value reflect.Value + + value, partResolved = v.evalPath(ctx, parts, exprRoot) + if value.IsValid() { + result = value.Interface() + } + } + + return result, partResolved +} + +// +// Helpers +// + +// isHelperCall returns true if given expression is a helper call +func (v *evalVisitor) isHelperCall(node *ast.Expression) bool { + if helperName := node.HelperName(); helperName != "" { + return v.findHelper(helperName) != zero + } + return false +} + +// findHelper finds given helper +func (v *evalVisitor) findHelper(name string) reflect.Value { + // check template helpers + if h := v.tpl.findHelper(name); h != zero { + return h + } + + // check global helpers + return findHelper(name) +} + +// callFunc calls function with given options +func (v *evalVisitor) callFunc(name string, funcVal reflect.Value, options *Options) reflect.Value { + params := options.Params() + + funcType := funcVal.Type() + + // @todo Is there a better way to do that ? + strType := reflect.TypeOf("") + boolType := reflect.TypeOf(true) + + // check parameters number + addOptions := false + numIn := funcType.NumIn() + + if numIn == len(params)+1 { + lastArgType := funcType.In(numIn - 1) + if reflect.TypeOf(options).AssignableTo(lastArgType) { + addOptions = true + } + } + + if !addOptions && (len(params) != numIn) { + v.errorf("Helper '%s' called with wrong number of arguments, needed %d but got %d", name, numIn, len(params)) + } + + // check and collect arguments + args := make([]reflect.Value, numIn) + for i, param := range params { + arg := reflect.ValueOf(param) + argType := funcType.In(i) + + if !arg.IsValid() { + if canBeNil(argType) { + arg = reflect.Zero(argType) + } else if argType.Kind() == reflect.String { + arg = reflect.ValueOf("") + } else { + // @todo Maybe we can panic on that + return reflect.Zero(strType) + } + } + + if !arg.Type().AssignableTo(argType) { + if strType.AssignableTo(argType) { + // convert parameter to string + arg = reflect.ValueOf(strValue(arg)) + } else if boolType.AssignableTo(argType) { + // convert parameter to bool + val, _ := isTrueValue(arg) + arg = reflect.ValueOf(val) + } else { + v.errorf("Helper %s called with argument %d with type %s but it should be %s", name, i, arg.Type(), argType) + } + } + + args[i] = arg + } + + if addOptions { + args[numIn-1] = reflect.ValueOf(options) + } + + result := funcVal.Call(args) + + return result[0] +} + +// callHelper invoqs helper function for given expression node +func (v *evalVisitor) callHelper(name string, helper reflect.Value, node *ast.Expression) interface{} { + result := v.callFunc(name, helper, v.helperOptions(node)) + if !result.IsValid() { + return nil + } + + // @todo We maybe want to ensure here that helper returned a string or a SafeString + return result.Interface() +} + +// helperOptions computes helper options argument from an expression +func (v *evalVisitor) helperOptions(node *ast.Expression) *Options { + var params []interface{} + var hash map[string]interface{} + + for _, paramNode := range node.Params { + param := paramNode.Accept(v) + params = append(params, param) + } + + if node.Hash != nil { + hash, _ = node.Hash.Accept(v).(map[string]interface{}) + } + + return newOptions(v, params, hash) +} + +// +// Partials +// + +// findPartial finds given partial +func (v *evalVisitor) findPartial(name string) *partial { + // check template partials + if p := v.tpl.findPartial(name); p != nil { + return p + } + + // check global partials + return findPartial(name) +} + +// partialContext computes partial context +func (v *evalVisitor) partialContext(node *ast.PartialStatement) reflect.Value { + if nb := len(node.Params); nb > 1 { + v.errorf("Unsupported number of partial arguments: %d", nb) + } + + if (len(node.Params) > 0) && (node.Hash != nil) { + v.errorf("Passing both context and named parameters to a partial is not allowed") + } + + if len(node.Params) == 1 { + return reflect.ValueOf(node.Params[0].Accept(v)) + } + + if node.Hash != nil { + hash, _ := node.Hash.Accept(v).(map[string]interface{}) + return reflect.ValueOf(hash) + } + + return zero +} + +// evalPartial evaluates a partial +func (v *evalVisitor) evalPartial(p *partial, node *ast.PartialStatement) string { + // get partial template + partialTpl, err := p.template() + if err != nil { + v.errPanic(err) + } + + // push partial context + ctx := v.partialContext(node) + if ctx.IsValid() { + v.pushCtx(ctx) + } + + // evaluate partial template + result, _ := partialTpl.program.Accept(v).(string) + + // ident partial + result = indentLines(result, node.Indent) + + if ctx.IsValid() { + v.popCtx() + } + + return result +} + +// indentLines indents all lines of given string +func indentLines(str string, indent string) string { + if indent == "" { + return str + } + + var indented []string + + lines := strings.Split(str, "\n") + for i, line := range lines { + if (i == (len(lines) - 1)) && (line == "") { + // input string ends with a new line + indented = append(indented, line) + } else { + indented = append(indented, indent+line) + } + } + + return strings.Join(indented, "\n") +} + +// +// Functions +// + +// wasFuncCall returns true if given expression was a function call +func (v *evalVisitor) wasFuncCall(node *ast.Expression) bool { + // check if expression was tagged as a function call + return v.exprFunc[node] +} + +// +// Visitor interface +// + +// Statements + +// VisitProgram implements corresponding Visitor interface method +func (v *evalVisitor) VisitProgram(node *ast.Program) interface{} { + v.at(node) + + buf := new(bytes.Buffer) + + for _, n := range node.Body { + if str := Str(n.Accept(v)); str != "" { + if _, err := buf.Write([]byte(str)); err != nil { + v.errPanic(err) + } + } + } + + return buf.String() +} + +// VisitMustache implements corresponding Visitor interface method +func (v *evalVisitor) VisitMustache(node *ast.MustacheStatement) interface{} { + v.at(node) + + // evaluate expression + expr := node.Expression.Accept(v) + + // check if this is a safe string + isSafe := isSafeString(expr) + + // get string value + str := Str(expr) + if !isSafe && !node.Unescaped { + // escape html + str = Escape(str) + } + + return str +} + +// VisitBlock implements corresponding Visitor interface method +func (v *evalVisitor) VisitBlock(node *ast.BlockStatement) interface{} { + v.at(node) + + v.pushBlock(node) + + var result interface{} + + // evaluate expression + expr := node.Expression.Accept(v) + + if v.isHelperCall(node.Expression) || v.wasFuncCall(node.Expression) { + // it is the responsability of the helper/function to evaluate block + result = expr + } else { + val := reflect.ValueOf(expr) + + truth, _ := isTrueValue(val) + if truth { + if node.Program != nil { + switch val.Kind() { + case reflect.Array, reflect.Slice: + concat := "" + + // Array context + for i := 0; i < val.Len(); i++ { + // Computes new private data frame + frame := v.dataFrame.newIterDataFrame(val.Len(), i, nil) + + // Evaluate program + concat += v.evalProgram(node.Program, val.Index(i).Interface(), frame, i) + } + + result = concat + default: + // NOT array + result = v.evalProgram(node.Program, expr, nil, nil) + } + } + } else if node.Inverse != nil { + result, _ = node.Inverse.Accept(v).(string) + } + } + + v.popBlock() + + return result +} + +// VisitPartial implements corresponding Visitor interface method +func (v *evalVisitor) VisitPartial(node *ast.PartialStatement) interface{} { + v.at(node) + + // partialName: helperName | sexpr + name, ok := ast.HelperNameStr(node.Name) + if !ok { + if subExpr, ok := node.Name.(*ast.SubExpression); ok { + name, _ = subExpr.Accept(v).(string) + } + } + + if name == "" { + v.errorf("Unexpected partial name: %q", node.Name) + } + + partial := v.findPartial(name) + if partial == nil { + v.errorf("Partial not found: %s", name) + } + + return v.evalPartial(partial, node) +} + +// VisitContent implements corresponding Visitor interface method +func (v *evalVisitor) VisitContent(node *ast.ContentStatement) interface{} { + v.at(node) + + // write content as is + return node.Value +} + +// VisitComment implements corresponding Visitor interface method +func (v *evalVisitor) VisitComment(node *ast.CommentStatement) interface{} { + v.at(node) + + // ignore comments + return "" +} + +// Expressions + +// VisitExpression implements corresponding Visitor interface method +func (v *evalVisitor) VisitExpression(node *ast.Expression) interface{} { + v.at(node) + + var result interface{} + done := false + + v.pushExpr(node) + + // helper call + if helperName := node.HelperName(); helperName != "" { + if helper := v.findHelper(helperName); helper != zero { + result = v.callHelper(helperName, helper, node) + done = true + } + } + + if !done { + // literal + if literal, ok := node.LiteralStr(); ok { + if val := v.evalField(v.curCtx(), literal, true); val.IsValid() { + result = val.Interface() + done = true + } + } + } + + if !done { + // field path + if path := node.FieldPath(); path != nil { + // @todo Find a cleaner way ! Don't break the pattern ! + // this is an exception to visitor pattern, because we need to pass the info + // that this path is at root of current expression + if val := v.evalPathExpression(path, true); val != nil { + result = val + } + } + } + + v.popExpr() + + return result +} + +// VisitSubExpression implements corresponding Visitor interface method +func (v *evalVisitor) VisitSubExpression(node *ast.SubExpression) interface{} { + v.at(node) + + return node.Expression.Accept(v) +} + +// VisitPath implements corresponding Visitor interface method +func (v *evalVisitor) VisitPath(node *ast.PathExpression) interface{} { + return v.evalPathExpression(node, false) +} + +// Literals + +// VisitString implements corresponding Visitor interface method +func (v *evalVisitor) VisitString(node *ast.StringLiteral) interface{} { + v.at(node) + + return node.Value +} + +// VisitBoolean implements corresponding Visitor interface method +func (v *evalVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} { + v.at(node) + + return node.Value +} + +// VisitNumber implements corresponding Visitor interface method +func (v *evalVisitor) VisitNumber(node *ast.NumberLiteral) interface{} { + v.at(node) + + return node.Number() +} + +// Miscellaneous + +// VisitHash implements corresponding Visitor interface method +func (v *evalVisitor) VisitHash(node *ast.Hash) interface{} { + v.at(node) + + result := make(map[string]interface{}) + + for _, pair := range node.Pairs { + if value := pair.Accept(v); value != nil { + result[pair.Key] = value + } + } + + return result +} + +// VisitHashPair implements corresponding Visitor interface method +func (v *evalVisitor) VisitHashPair(node *ast.HashPair) interface{} { + v.at(node) + + return node.Val.Accept(v) +} diff --git a/vendor/github.com/aymerick/raymond/helper.go b/vendor/github.com/aymerick/raymond/helper.go new file mode 100644 index 0000000..15c8309 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/helper.go @@ -0,0 +1,382 @@ +package raymond + +import ( + "fmt" + "log" + "reflect" + "sync" +) + +// Options represents the options argument provided to helpers and context functions. +type Options struct { + // evaluation visitor + eval *evalVisitor + + // params + params []interface{} + hash map[string]interface{} +} + +// helpers stores all globally registered helpers +var helpers = make(map[string]reflect.Value) + +// protects global helpers +var helpersMutex sync.RWMutex + +func init() { + // register builtin helpers + RegisterHelper("if", ifHelper) + RegisterHelper("unless", unlessHelper) + RegisterHelper("with", withHelper) + RegisterHelper("each", eachHelper) + RegisterHelper("log", logHelper) + RegisterHelper("lookup", lookupHelper) + RegisterHelper("equal", equalHelper) +} + +// RegisterHelper registers a global helper. That helper will be available to all templates. +func RegisterHelper(name string, helper interface{}) { + helpersMutex.Lock() + defer helpersMutex.Unlock() + + if helpers[name] != zero { + panic(fmt.Errorf("Helper already registered: %s", name)) + } + + val := reflect.ValueOf(helper) + ensureValidHelper(name, val) + + helpers[name] = val +} + +// RegisterHelpers registers several global helpers. Those helpers will be available to all templates. +func RegisterHelpers(helpers map[string]interface{}) { + for name, helper := range helpers { + RegisterHelper(name, helper) + } +} + +// ensureValidHelper panics if given helper is not valid +func ensureValidHelper(name string, funcValue reflect.Value) { + if funcValue.Kind() != reflect.Func { + panic(fmt.Errorf("Helper must be a function: %s", name)) + } + + funcType := funcValue.Type() + + if funcType.NumOut() != 1 { + panic(fmt.Errorf("Helper function must return a string or a SafeString: %s", name)) + } + + // @todo Check if first returned value is a string, SafeString or interface{} ? +} + +// findHelper finds a globally registered helper +func findHelper(name string) reflect.Value { + helpersMutex.RLock() + defer helpersMutex.RUnlock() + + return helpers[name] +} + +// newOptions instanciates a new Options +func newOptions(eval *evalVisitor, params []interface{}, hash map[string]interface{}) *Options { + return &Options{ + eval: eval, + params: params, + hash: hash, + } +} + +// newEmptyOptions instanciates a new empty Options +func newEmptyOptions(eval *evalVisitor) *Options { + return &Options{ + eval: eval, + hash: make(map[string]interface{}), + } +} + +// +// Context Values +// + +// Value returns field value from current context. +func (options *Options) Value(name string) interface{} { + value := options.eval.evalField(options.eval.curCtx(), name, false) + if !value.IsValid() { + return nil + } + + return value.Interface() +} + +// ValueStr returns string representation of field value from current context. +func (options *Options) ValueStr(name string) string { + return Str(options.Value(name)) +} + +// Ctx returns current evaluation context. +func (options *Options) Ctx() interface{} { + return options.eval.curCtx().Interface() +} + +// +// Hash Arguments +// + +// HashProp returns hash property. +func (options *Options) HashProp(name string) interface{} { + return options.hash[name] +} + +// HashStr returns string representation of hash property. +func (options *Options) HashStr(name string) string { + return Str(options.hash[name]) +} + +// Hash returns entire hash. +func (options *Options) Hash() map[string]interface{} { + return options.hash +} + +// +// Parameters +// + +// Param returns parameter at given position. +func (options *Options) Param(pos int) interface{} { + if len(options.params) > pos { + return options.params[pos] + } + + return nil +} + +// ParamStr returns string representation of parameter at given position. +func (options *Options) ParamStr(pos int) string { + return Str(options.Param(pos)) +} + +// Params returns all parameters. +func (options *Options) Params() []interface{} { + return options.params +} + +// +// Private data +// + +// Data returns private data value. +func (options *Options) Data(name string) interface{} { + return options.eval.dataFrame.Get(name) +} + +// DataStr returns string representation of private data value. +func (options *Options) DataStr(name string) string { + return Str(options.eval.dataFrame.Get(name)) +} + +// DataFrame returns current private data frame. +func (options *Options) DataFrame() *DataFrame { + return options.eval.dataFrame +} + +// NewDataFrame instanciates a new data frame that is a copy of current evaluation data frame. +// +// Parent of returned data frame is set to current evaluation data frame. +func (options *Options) NewDataFrame() *DataFrame { + return options.eval.dataFrame.Copy() +} + +// newIterDataFrame instanciates a new data frame and set iteration specific vars +func (options *Options) newIterDataFrame(length int, i int, key interface{}) *DataFrame { + return options.eval.dataFrame.newIterDataFrame(length, i, key) +} + +// +// Evaluation +// + +// evalBlock evaluates block with given context, private data and iteration key +func (options *Options) evalBlock(ctx interface{}, data *DataFrame, key interface{}) string { + result := "" + + if block := options.eval.curBlock(); (block != nil) && (block.Program != nil) { + result = options.eval.evalProgram(block.Program, ctx, data, key) + } + + return result +} + +// Fn evaluates block with current evaluation context. +func (options *Options) Fn() string { + return options.evalBlock(nil, nil, nil) +} + +// FnCtxData evaluates block with given context and private data frame. +func (options *Options) FnCtxData(ctx interface{}, data *DataFrame) string { + return options.evalBlock(ctx, data, nil) +} + +// FnWith evaluates block with given context. +func (options *Options) FnWith(ctx interface{}) string { + return options.evalBlock(ctx, nil, nil) +} + +// FnData evaluates block with given private data frame. +func (options *Options) FnData(data *DataFrame) string { + return options.evalBlock(nil, data, nil) +} + +// Inverse evaluates "else block". +func (options *Options) Inverse() string { + result := "" + if block := options.eval.curBlock(); (block != nil) && (block.Inverse != nil) { + result, _ = block.Inverse.Accept(options.eval).(string) + } + + return result +} + +// Eval evaluates field for given context. +func (options *Options) Eval(ctx interface{}, field string) interface{} { + if ctx == nil { + return nil + } + + if field == "" { + return nil + } + + val := options.eval.evalField(reflect.ValueOf(ctx), field, false) + if !val.IsValid() { + return nil + } + + return val.Interface() +} + +// +// Misc +// + +// isIncludableZero returns true if 'includeZero' option is set and first param is the number 0 +func (options *Options) isIncludableZero() bool { + b, ok := options.HashProp("includeZero").(bool) + if ok && b { + nb, ok := options.Param(0).(int) + if ok && nb == 0 { + return true + } + } + + return false +} + +// +// Builtin helpers +// + +// #if block helper +func ifHelper(conditional interface{}, options *Options) interface{} { + if options.isIncludableZero() || IsTrue(conditional) { + return options.Fn() + } + + return options.Inverse() +} + +// #unless block helper +func unlessHelper(conditional interface{}, options *Options) interface{} { + if options.isIncludableZero() || IsTrue(conditional) { + return options.Inverse() + } + + return options.Fn() +} + +// #with block helper +func withHelper(context interface{}, options *Options) interface{} { + if IsTrue(context) { + return options.FnWith(context) + } + + return options.Inverse() +} + +// #each block helper +func eachHelper(context interface{}, options *Options) interface{} { + if !IsTrue(context) { + return options.Inverse() + } + + result := "" + + val := reflect.ValueOf(context) + switch val.Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < val.Len(); i++ { + // computes private data + data := options.newIterDataFrame(val.Len(), i, nil) + + // evaluates block + result += options.evalBlock(val.Index(i).Interface(), data, i) + } + case reflect.Map: + // note: a go hash is not ordered, so result may vary, this behaviour differs from the JS implementation + keys := val.MapKeys() + for i := 0; i < len(keys); i++ { + key := keys[i].Interface() + ctx := val.MapIndex(keys[i]).Interface() + + // computes private data + data := options.newIterDataFrame(len(keys), i, key) + + // evaluates block + result += options.evalBlock(ctx, data, key) + } + case reflect.Struct: + var exportedFields []int + + // collect exported fields only + for i := 0; i < val.NumField(); i++ { + if tField := val.Type().Field(i); tField.PkgPath == "" { + exportedFields = append(exportedFields, i) + } + } + + for i, fieldIndex := range exportedFields { + key := val.Type().Field(fieldIndex).Name + ctx := val.Field(fieldIndex).Interface() + + // computes private data + data := options.newIterDataFrame(len(exportedFields), i, key) + + // evaluates block + result += options.evalBlock(ctx, data, key) + } + } + + return result +} + +// #log helper +func logHelper(message string) interface{} { + log.Print(message) + return "" +} + +// #lookup helper +func lookupHelper(obj interface{}, field string, options *Options) interface{} { + return Str(options.Eval(obj, field)) +} + +// #equal helper +// Ref: https://github.com/aymerick/raymond/issues/7 +func equalHelper(a interface{}, b interface{}, options *Options) interface{} { + if Str(a) == Str(b) { + return options.Fn() + } + + return "" +} diff --git a/vendor/github.com/aymerick/raymond/lexer/lexer.go b/vendor/github.com/aymerick/raymond/lexer/lexer.go new file mode 100644 index 0000000..48899f8 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/lexer/lexer.go @@ -0,0 +1,639 @@ +// Package lexer provides a handlebars tokenizer. +package lexer + +import ( + "fmt" + "regexp" + "strings" + "unicode" + "unicode/utf8" +) + +// References: +// - https://github.com/wycats/handlebars.js/blob/master/src/handlebars.l +// - https://github.com/golang/go/blob/master/src/text/template/parse/lex.go + +const ( + // Mustaches detection + escapedEscapedOpenMustache = "\\\\{{" + escapedOpenMustache = "\\{{" + openMustache = "{{" + closeMustache = "}}" + closeStripMustache = "~}}" + closeUnescapedStripMustache = "}~}}" +) + +const eof = -1 + +// lexFunc represents a function that returns the next lexer function. +type lexFunc func(*Lexer) lexFunc + +// Lexer is a lexical analyzer. +type Lexer struct { + input string // input to scan + name string // lexer name, used for testing purpose + tokens chan Token // channel of scanned tokens + nextFunc lexFunc // the next function to execute + + pos int // current byte position in input string + line int // current line position in input string + width int // size of last rune scanned from input string + start int // start position of the token we are scanning + + // the shameful contextual properties needed because `nextFunc` is not enough + closeComment *regexp.Regexp // regexp to scan close of current comment + rawBlock bool // are we parsing a raw block content ? +} + +var ( + lookheadChars = `[\s` + regexp.QuoteMeta("=~}/)|") + `]` + literalLookheadChars = `[\s` + regexp.QuoteMeta("~})") + `]` + + // characters not allowed in an identifier + unallowedIDChars = " \n\t!\"#%&'()*+,./;<=>@[\\]^`{|}~" + + // regular expressions + rID = regexp.MustCompile(`^[^` + regexp.QuoteMeta(unallowedIDChars) + `]+`) + rDotID = regexp.MustCompile(`^\.` + lookheadChars) + rTrue = regexp.MustCompile(`^true` + literalLookheadChars) + rFalse = regexp.MustCompile(`^false` + literalLookheadChars) + rOpenRaw = regexp.MustCompile(`^\{\{\{\{`) + rCloseRaw = regexp.MustCompile(`^\}\}\}\}`) + rOpenEndRaw = regexp.MustCompile(`^\{\{\{\{/`) + rOpenEndRawLookAhead = regexp.MustCompile(`\{\{\{\{/`) + rOpenUnescaped = regexp.MustCompile(`^\{\{~?\{`) + rCloseUnescaped = regexp.MustCompile(`^\}~?\}\}`) + rOpenBlock = regexp.MustCompile(`^\{\{~?#`) + rOpenEndBlock = regexp.MustCompile(`^\{\{~?/`) + rOpenPartial = regexp.MustCompile(`^\{\{~?>`) + // {{^}} or {{else}} + rInverse = regexp.MustCompile(`^(\{\{~?\^\s*~?\}\}|\{\{~?\s*else\s*~?\}\})`) + rOpenInverse = regexp.MustCompile(`^\{\{~?\^`) + rOpenInverseChain = regexp.MustCompile(`^\{\{~?\s*else`) + // {{ or {{& + rOpen = regexp.MustCompile(`^\{\{~?&?`) + rClose = regexp.MustCompile(`^~?\}\}`) + rOpenBlockParams = regexp.MustCompile(`^as\s+\|`) + // {{!-- ... --}} + rOpenCommentDash = regexp.MustCompile(`^\{\{~?!--\s*`) + rCloseCommentDash = regexp.MustCompile(`^\s*--~?\}\}`) + // {{! ... }} + rOpenComment = regexp.MustCompile(`^\{\{~?!\s*`) + rCloseComment = regexp.MustCompile(`^\s*~?\}\}`) +) + +// Scan scans given input. +// +// Tokens can then be fetched sequentially thanks to NextToken() function on returned lexer. +func Scan(input string) *Lexer { + return scanWithName(input, "") +} + +// scanWithName scans given input, with a name used for testing +// +// Tokens can then be fetched sequentially thanks to NextToken() function on returned lexer. +func scanWithName(input string, name string) *Lexer { + result := &Lexer{ + input: input, + name: name, + tokens: make(chan Token), + line: 1, + } + + go result.run() + + return result +} + +// Collect scans and collect all tokens. +// +// This should be used for debugging purpose only. You should use Scan() and lexer.NextToken() functions instead. +func Collect(input string) []Token { + var result []Token + + l := Scan(input) + for { + token := l.NextToken() + result = append(result, token) + + if token.Kind == TokenEOF || token.Kind == TokenError { + break + } + } + + return result +} + +// NextToken returns the next scanned token. +func (l *Lexer) NextToken() Token { + result := <-l.tokens + + return result +} + +// run starts lexical analysis +func (l *Lexer) run() { + for l.nextFunc = lexContent; l.nextFunc != nil; { + l.nextFunc = l.nextFunc(l) + } +} + +// next returns next character from input, or eof of there is nothing left to scan +func (l *Lexer) next() rune { + if l.pos >= len(l.input) { + l.width = 0 + return eof + } + + r, w := utf8.DecodeRuneInString(l.input[l.pos:]) + l.width = w + l.pos += l.width + + return r +} + +func (l *Lexer) produce(kind TokenKind, val string) { + l.tokens <- Token{kind, val, l.start, l.line} + + // scanning a new token + l.start = l.pos + + // update line number + l.line += strings.Count(val, "\n") +} + +// emit emits a new scanned token +func (l *Lexer) emit(kind TokenKind) { + l.produce(kind, l.input[l.start:l.pos]) +} + +// emitContent emits scanned content +func (l *Lexer) emitContent() { + if l.pos > l.start { + l.emit(TokenContent) + } +} + +// emitString emits a scanned string +func (l *Lexer) emitString(delimiter rune) { + str := l.input[l.start:l.pos] + + // replace escaped delimiters + str = strings.Replace(str, "\\"+string(delimiter), string(delimiter), -1) + + l.produce(TokenString, str) +} + +// peek returns but does not consume the next character in the input +func (l *Lexer) peek() rune { + r := l.next() + l.backup() + return r +} + +// backup steps back one character +// +// WARNING: Can only be called once per call of next +func (l *Lexer) backup() { + l.pos -= l.width +} + +// ignoreskips all characters that have been scanned up to current position +func (l *Lexer) ignore() { + l.start = l.pos +} + +// accept scans the next character if it is included in given string +func (l *Lexer) accept(valid string) bool { + if strings.IndexRune(valid, l.next()) >= 0 { + return true + } + + l.backup() + + return false +} + +// acceptRun scans all following characters that are part of given string +func (l *Lexer) acceptRun(valid string) { + for strings.IndexRune(valid, l.next()) >= 0 { + } + + l.backup() +} + +// errorf emits an error token +func (l *Lexer) errorf(format string, args ...interface{}) lexFunc { + l.tokens <- Token{TokenError, fmt.Sprintf(format, args...), l.start, l.line} + return nil +} + +// isString returns true if content at current scanning position starts with given string +func (l *Lexer) isString(str string) bool { + return strings.HasPrefix(l.input[l.pos:], str) +} + +// findRegexp returns the first string from current scanning position that matches given regular expression +func (l *Lexer) findRegexp(r *regexp.Regexp) string { + return r.FindString(l.input[l.pos:]) +} + +// indexRegexp returns the index of the first string from current scanning position that matches given regular expression +// +// It returns -1 if not found +func (l *Lexer) indexRegexp(r *regexp.Regexp) int { + loc := r.FindStringIndex(l.input[l.pos:]) + if loc == nil { + return -1 + } + return loc[0] +} + +// lexContent scans content (ie: not between mustaches) +func lexContent(l *Lexer) lexFunc { + var next lexFunc + + if l.rawBlock { + if i := l.indexRegexp(rOpenEndRawLookAhead); i != -1 { + // {{{{/ + l.rawBlock = false + l.pos += i + + next = lexOpenMustache + } else { + return l.errorf("Unclosed raw block") + } + } else if l.isString(escapedEscapedOpenMustache) { + // \\{{ + + // emit content with only one escaped escape + l.next() + l.emitContent() + + // ignore second escaped escape + l.next() + l.ignore() + + next = lexContent + } else if l.isString(escapedOpenMustache) { + // \{{ + next = lexEscapedOpenMustache + } else if str := l.findRegexp(rOpenCommentDash); str != "" { + // {{!-- + l.closeComment = rCloseCommentDash + + next = lexComment + } else if str := l.findRegexp(rOpenComment); str != "" { + // {{! + l.closeComment = rCloseComment + + next = lexComment + } else if l.isString(openMustache) { + // {{ + next = lexOpenMustache + } + + if next != nil { + // emit scanned content + l.emitContent() + + // scan next token + return next + } + + // scan next rune + if l.next() == eof { + // emit scanned content + l.emitContent() + + // this is over + l.emit(TokenEOF) + return nil + } + + // continue content scanning + return lexContent +} + +// lexEscapedOpenMustache scans \{{ +func lexEscapedOpenMustache(l *Lexer) lexFunc { + // ignore escape character + l.next() + l.ignore() + + // scan mustaches + for l.peek() == '{' { + l.next() + } + + return lexContent +} + +// lexOpenMustache scans {{ +func lexOpenMustache(l *Lexer) lexFunc { + var str string + var tok TokenKind + + nextFunc := lexExpression + + if str = l.findRegexp(rOpenEndRaw); str != "" { + tok = TokenOpenEndRawBlock + } else if str = l.findRegexp(rOpenRaw); str != "" { + tok = TokenOpenRawBlock + l.rawBlock = true + } else if str = l.findRegexp(rOpenUnescaped); str != "" { + tok = TokenOpenUnescaped + } else if str = l.findRegexp(rOpenBlock); str != "" { + tok = TokenOpenBlock + } else if str = l.findRegexp(rOpenEndBlock); str != "" { + tok = TokenOpenEndBlock + } else if str = l.findRegexp(rOpenPartial); str != "" { + tok = TokenOpenPartial + } else if str = l.findRegexp(rInverse); str != "" { + tok = TokenInverse + nextFunc = lexContent + } else if str = l.findRegexp(rOpenInverse); str != "" { + tok = TokenOpenInverse + } else if str = l.findRegexp(rOpenInverseChain); str != "" { + tok = TokenOpenInverseChain + } else if str = l.findRegexp(rOpen); str != "" { + tok = TokenOpen + } else { + // this is rotten + panic("Current pos MUST be an opening mustache") + } + + l.pos += len(str) + l.emit(tok) + + return nextFunc +} + +// lexCloseMustache scans }} or ~}} +func lexCloseMustache(l *Lexer) lexFunc { + var str string + var tok TokenKind + + if str = l.findRegexp(rCloseRaw); str != "" { + // }}}} + tok = TokenCloseRawBlock + } else if str = l.findRegexp(rCloseUnescaped); str != "" { + // }}} + tok = TokenCloseUnescaped + } else if str = l.findRegexp(rClose); str != "" { + // }} + tok = TokenClose + } else { + // this is rotten + panic("Current pos MUST be a closing mustache") + } + + l.pos += len(str) + l.emit(tok) + + return lexContent +} + +// lexExpression scans inside mustaches +func lexExpression(l *Lexer) lexFunc { + // search close mustache delimiter + if l.isString(closeMustache) || l.isString(closeStripMustache) || l.isString(closeUnescapedStripMustache) { + return lexCloseMustache + } + + // search some patterns before advancing scanning position + + // "as |" + if str := l.findRegexp(rOpenBlockParams); str != "" { + l.pos += len(str) + l.emit(TokenOpenBlockParams) + return lexExpression + } + + // .. + if l.isString("..") { + l.pos += len("..") + l.emit(TokenID) + return lexExpression + } + + // . + if str := l.findRegexp(rDotID); str != "" { + l.pos += len(".") + l.emit(TokenID) + return lexExpression + } + + // true + if str := l.findRegexp(rTrue); str != "" { + l.pos += len("true") + l.emit(TokenBoolean) + return lexExpression + } + + // false + if str := l.findRegexp(rFalse); str != "" { + l.pos += len("false") + l.emit(TokenBoolean) + return lexExpression + } + + // let's scan next character + switch r := l.next(); { + case r == eof: + return l.errorf("Unclosed expression") + case isIgnorable(r): + return lexIgnorable + case r == '(': + l.emit(TokenOpenSexpr) + case r == ')': + l.emit(TokenCloseSexpr) + case r == '=': + l.emit(TokenEquals) + case r == '@': + l.emit(TokenData) + case r == '"' || r == '\'': + l.backup() + return lexString + case r == '/' || r == '.': + l.emit(TokenSep) + case r == '|': + l.emit(TokenCloseBlockParams) + case r == '+' || r == '-' || (r >= '0' && r <= '9'): + l.backup() + return lexNumber + case r == '[': + return lexPathLiteral + case strings.IndexRune(unallowedIDChars, r) < 0: + l.backup() + return lexIdentifier + default: + return l.errorf("Unexpected character in expression: '%c'", r) + } + + return lexExpression +} + +// lexComment scans {{!-- or {{! +func lexComment(l *Lexer) lexFunc { + if str := l.findRegexp(l.closeComment); str != "" { + l.pos += len(str) + l.emit(TokenComment) + + return lexContent + } + + if r := l.next(); r == eof { + return l.errorf("Unclosed comment") + } + + return lexComment +} + +// lexIgnorable scans all following ignorable characters +func lexIgnorable(l *Lexer) lexFunc { + for isIgnorable(l.peek()) { + l.next() + } + l.ignore() + + return lexExpression +} + +// lexString scans a string +func lexString(l *Lexer) lexFunc { + // get string delimiter + delim := l.next() + var prev rune + + // ignore delimiter + l.ignore() + + for { + r := l.next() + if r == eof || r == '\n' { + return l.errorf("Unterminated string") + } + + if (r == delim) && (prev != '\\') { + break + } + + prev = r + } + + // remove end delimiter + l.backup() + + // emit string + l.emitString(delim) + + // skip end delimiter + l.next() + l.ignore() + + return lexExpression +} + +// lexNumber scans a number: decimal, octal, hex, float, or imaginary. This +// isn't a perfect number scanner - for instance it accepts "." and "0x0.2" +// and "089" - but when it's wrong the input is invalid and the parser (via +// strconv) will notice. +// +// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go +func lexNumber(l *Lexer) lexFunc { + if !l.scanNumber() { + return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) + } + if sign := l.peek(); sign == '+' || sign == '-' { + // Complex: 1+2i. No spaces, must end in 'i'. + if !l.scanNumber() || l.input[l.pos-1] != 'i' { + return l.errorf("bad number syntax: %q", l.input[l.start:l.pos]) + } + l.emit(TokenNumber) + } else { + l.emit(TokenNumber) + } + return lexExpression +} + +// scanNumber scans a number +// +// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go +func (l *Lexer) scanNumber() bool { + // Optional leading sign. + l.accept("+-") + + // Is it hex? + digits := "0123456789" + + if l.accept("0") && l.accept("xX") { + digits = "0123456789abcdefABCDEF" + } + + l.acceptRun(digits) + + if l.accept(".") { + l.acceptRun(digits) + } + + if l.accept("eE") { + l.accept("+-") + l.acceptRun("0123456789") + } + + // Is it imaginary? + l.accept("i") + + // Next thing mustn't be alphanumeric. + if isAlphaNumeric(l.peek()) { + l.next() + return false + } + + return true +} + +// lexIdentifier scans an ID +func lexIdentifier(l *Lexer) lexFunc { + str := l.findRegexp(rID) + if len(str) == 0 { + // this is rotten + panic("Identifier expected") + } + + l.pos += len(str) + l.emit(TokenID) + + return lexExpression +} + +// lexPathLiteral scans an [ID] +func lexPathLiteral(l *Lexer) lexFunc { + for { + r := l.next() + if r == eof || r == '\n' { + return l.errorf("Unterminated path literal") + } + + if r == ']' { + break + } + } + + l.emit(TokenID) + + return lexExpression +} + +// isIgnorable returns true if given character is ignorable (ie. whitespace of line feed) +func isIgnorable(r rune) bool { + return r == ' ' || r == '\t' || r == '\n' +} + +// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore. +// +// NOTE borrowed from https://github.com/golang/go/tree/master/src/text/template/parse/lex.go +func isAlphaNumeric(r rune) bool { + return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r) +} diff --git a/vendor/github.com/aymerick/raymond/lexer/token.go b/vendor/github.com/aymerick/raymond/lexer/token.go new file mode 100644 index 0000000..13cf2e6 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/lexer/token.go @@ -0,0 +1,183 @@ +package lexer + +import "fmt" + +const ( + // TokenError represents an error + TokenError TokenKind = iota + + // TokenEOF represents an End Of File + TokenEOF + + // + // Mustache delimiters + // + + // TokenOpen is the OPEN token + TokenOpen + + // TokenClose is the CLOSE token + TokenClose + + // TokenOpenRawBlock is the OPEN_RAW_BLOCK token + TokenOpenRawBlock + + // TokenCloseRawBlock is the CLOSE_RAW_BLOCK token + TokenCloseRawBlock + + // TokenOpenEndRawBlock is the END_RAW_BLOCK token + TokenOpenEndRawBlock + + // TokenOpenUnescaped is the OPEN_UNESCAPED token + TokenOpenUnescaped + + // TokenCloseUnescaped is the CLOSE_UNESCAPED token + TokenCloseUnescaped + + // TokenOpenBlock is the OPEN_BLOCK token + TokenOpenBlock + + // TokenOpenEndBlock is the OPEN_ENDBLOCK token + TokenOpenEndBlock + + // TokenInverse is the INVERSE token + TokenInverse + + // TokenOpenInverse is the OPEN_INVERSE token + TokenOpenInverse + + // TokenOpenInverseChain is the OPEN_INVERSE_CHAIN token + TokenOpenInverseChain + + // TokenOpenPartial is the OPEN_PARTIAL token + TokenOpenPartial + + // TokenComment is the COMMENT token + TokenComment + + // + // Inside mustaches + // + + // TokenOpenSexpr is the OPEN_SEXPR token + TokenOpenSexpr + + // TokenCloseSexpr is the CLOSE_SEXPR token + TokenCloseSexpr + + // TokenEquals is the EQUALS token + TokenEquals + + // TokenData is the DATA token + TokenData + + // TokenSep is the SEP token + TokenSep + + // TokenOpenBlockParams is the OPEN_BLOCK_PARAMS token + TokenOpenBlockParams + + // TokenCloseBlockParams is the CLOSE_BLOCK_PARAMS token + TokenCloseBlockParams + + // + // Tokens with content + // + + // TokenContent is the CONTENT token + TokenContent + + // TokenID is the ID token + TokenID + + // TokenString is the STRING token + TokenString + + // TokenNumber is the NUMBER token + TokenNumber + + // TokenBoolean is the BOOLEAN token + TokenBoolean +) + +const ( + // Option to generate token position in its string representation + dumpTokenPos = false + + // Option to generate values for all token kinds for their string representations + dumpAllTokensVal = true +) + +// TokenKind represents a Token type. +type TokenKind int + +// Token represents a scanned token. +type Token struct { + Kind TokenKind // Token kind + Val string // Token value + + Pos int // Byte position in input string + Line int // Line number in input string +} + +// tokenName permits to display token name given token type +var tokenName = map[TokenKind]string{ + TokenError: "Error", + TokenEOF: "EOF", + TokenContent: "Content", + TokenComment: "Comment", + TokenOpen: "Open", + TokenClose: "Close", + TokenOpenUnescaped: "OpenUnescaped", + TokenCloseUnescaped: "CloseUnescaped", + TokenOpenBlock: "OpenBlock", + TokenOpenEndBlock: "OpenEndBlock", + TokenOpenRawBlock: "OpenRawBlock", + TokenCloseRawBlock: "CloseRawBlock", + TokenOpenEndRawBlock: "OpenEndRawBlock", + TokenOpenBlockParams: "OpenBlockParams", + TokenCloseBlockParams: "CloseBlockParams", + TokenInverse: "Inverse", + TokenOpenInverse: "OpenInverse", + TokenOpenInverseChain: "OpenInverseChain", + TokenOpenPartial: "OpenPartial", + TokenOpenSexpr: "OpenSexpr", + TokenCloseSexpr: "CloseSexpr", + TokenID: "ID", + TokenEquals: "Equals", + TokenString: "String", + TokenNumber: "Number", + TokenBoolean: "Boolean", + TokenData: "Data", + TokenSep: "Sep", +} + +// String returns the token kind string representation for debugging. +func (k TokenKind) String() string { + s := tokenName[k] + if s == "" { + return fmt.Sprintf("Token-%d", int(k)) + } + return s +} + +// String returns the token string representation for debugging. +func (t Token) String() string { + result := "" + + if dumpTokenPos { + result += fmt.Sprintf("%d:", t.Pos) + } + + result += fmt.Sprintf("%s", t.Kind) + + if (dumpAllTokensVal || (t.Kind >= TokenContent)) && len(t.Val) > 0 { + if len(t.Val) > 100 { + result += fmt.Sprintf("{%.20q...}", t.Val) + } else { + result += fmt.Sprintf("{%q}", t.Val) + } + } + + return result +} diff --git a/vendor/github.com/aymerick/raymond/parser/parser.go b/vendor/github.com/aymerick/raymond/parser/parser.go new file mode 100644 index 0000000..22eed3c --- /dev/null +++ b/vendor/github.com/aymerick/raymond/parser/parser.go @@ -0,0 +1,846 @@ +// Package parser provides a handlebars syntax analyser. It consumes the tokens provided by the lexer to build an AST. +package parser + +import ( + "fmt" + "regexp" + "runtime" + "strconv" + + "github.com/aymerick/raymond/ast" + "github.com/aymerick/raymond/lexer" +) + +// References: +// - https://github.com/wycats/handlebars.js/blob/master/src/handlebars.yy +// - https://github.com/golang/go/blob/master/src/text/template/parse/parse.go + +// parser is a syntax analyzer. +type parser struct { + // Lexer + lex *lexer.Lexer + + // Root node + root ast.Node + + // Tokens parsed but not consumed yet + tokens []*lexer.Token + + // All tokens have been retreieved from lexer + lexOver bool +} + +var ( + rOpenComment = regexp.MustCompile(`^\{\{~?!-?-?`) + rCloseComment = regexp.MustCompile(`-?-?~?\}\}$`) + rOpenAmp = regexp.MustCompile(`^\{\{~?&`) +) + +// new instanciates a new parser +func new(input string) *parser { + return &parser{ + lex: lexer.Scan(input), + } +} + +// Parse analyzes given input and returns the AST root node. +func Parse(input string) (result *ast.Program, err error) { + // recover error + defer errRecover(&err) + + parser := new(input) + + // parse + result = parser.parseProgram() + + // check last token + token := parser.shift() + if token.Kind != lexer.TokenEOF { + // Parsing ended before EOF + errToken(token, "Syntax error") + } + + // fix whitespaces + processWhitespaces(result) + + // named returned values + return +} + +// errRecover recovers parsing panic +func errRecover(errp *error) { + e := recover() + if e != nil { + switch err := e.(type) { + case runtime.Error: + panic(e) + case error: + *errp = err + default: + panic(e) + } + } +} + +// errPanic panics +func errPanic(err error, line int) { + panic(fmt.Errorf("Parse error on line %d:\n%s", line, err)) +} + +// errNode panics with given node infos +func errNode(node ast.Node, msg string) { + errPanic(fmt.Errorf("%s\nNode: %s", msg, node), node.Location().Line) +} + +// errNode panics with given Token infos +func errToken(tok *lexer.Token, msg string) { + errPanic(fmt.Errorf("%s\nToken: %s", msg, tok), tok.Line) +} + +// errNode panics because of an unexpected Token kind +func errExpected(expect lexer.TokenKind, tok *lexer.Token) { + errPanic(fmt.Errorf("Expecting %s, got: '%s'", expect, tok), tok.Line) +} + +// program : statement* +func (p *parser) parseProgram() *ast.Program { + result := ast.NewProgram(p.next().Pos, p.next().Line) + + for p.isStatement() { + result.AddStatement(p.parseStatement()) + } + + return result +} + +// statement : mustache | block | rawBlock | partial | content | COMMENT +func (p *parser) parseStatement() ast.Node { + var result ast.Node + + tok := p.next() + + switch tok.Kind { + case lexer.TokenOpen, lexer.TokenOpenUnescaped: + // mustache + result = p.parseMustache() + case lexer.TokenOpenBlock: + // block + result = p.parseBlock() + case lexer.TokenOpenInverse: + // block + result = p.parseInverse() + case lexer.TokenOpenRawBlock: + // rawBlock + result = p.parseRawBlock() + case lexer.TokenOpenPartial: + // partial + result = p.parsePartial() + case lexer.TokenContent: + // content + result = p.parseContent() + case lexer.TokenComment: + // COMMENT + result = p.parseComment() + } + + return result +} + +// isStatement returns true if next token starts a statement +func (p *parser) isStatement() bool { + if !p.have(1) { + return false + } + + switch p.next().Kind { + case lexer.TokenOpen, lexer.TokenOpenUnescaped, lexer.TokenOpenBlock, + lexer.TokenOpenInverse, lexer.TokenOpenRawBlock, lexer.TokenOpenPartial, + lexer.TokenContent, lexer.TokenComment: + return true + } + + return false +} + +// content : CONTENT +func (p *parser) parseContent() *ast.ContentStatement { + // CONTENT + tok := p.shift() + if tok.Kind != lexer.TokenContent { + // @todo This check can be removed if content is optional in a raw block + errExpected(lexer.TokenContent, tok) + } + + return ast.NewContentStatement(tok.Pos, tok.Line, tok.Val) +} + +// COMMENT +func (p *parser) parseComment() *ast.CommentStatement { + // COMMENT + tok := p.shift() + + value := rOpenComment.ReplaceAllString(tok.Val, "") + value = rCloseComment.ReplaceAllString(value, "") + + result := ast.NewCommentStatement(tok.Pos, tok.Line, value) + result.Strip = ast.NewStripForStr(tok.Val) + + return result +} + +// param* hash? +func (p *parser) parseExpressionParamsHash() ([]ast.Node, *ast.Hash) { + var params []ast.Node + var hash *ast.Hash + + // params* + if p.isParam() { + params = p.parseParams() + } + + // hash? + if p.isHashSegment() { + hash = p.parseHash() + } + + return params, hash +} + +// helperName param* hash? +func (p *parser) parseExpression(tok *lexer.Token) *ast.Expression { + result := ast.NewExpression(tok.Pos, tok.Line) + + // helperName + result.Path = p.parseHelperName() + + // param* hash? + result.Params, result.Hash = p.parseExpressionParamsHash() + + return result +} + +// rawBlock : openRawBlock content endRawBlock +// openRawBlock : OPEN_RAW_BLOCK helperName param* hash? CLOSE_RAW_BLOCK +// endRawBlock : OPEN_END_RAW_BLOCK helperName CLOSE_RAW_BLOCK +func (p *parser) parseRawBlock() *ast.BlockStatement { + // OPEN_RAW_BLOCK + tok := p.shift() + + result := ast.NewBlockStatement(tok.Pos, tok.Line) + + // helperName param* hash? + result.Expression = p.parseExpression(tok) + + openName := result.Expression.Canonical() + + // CLOSE_RAW_BLOCK + tok = p.shift() + if tok.Kind != lexer.TokenCloseRawBlock { + errExpected(lexer.TokenCloseRawBlock, tok) + } + + // content + // @todo Is content mandatory in a raw block ? + content := p.parseContent() + + program := ast.NewProgram(tok.Pos, tok.Line) + program.AddStatement(content) + + result.Program = program + + // OPEN_END_RAW_BLOCK + tok = p.shift() + if tok.Kind != lexer.TokenOpenEndRawBlock { + // should never happen as it is caught by lexer + errExpected(lexer.TokenOpenEndRawBlock, tok) + } + + // helperName + endID := p.parseHelperName() + + closeName, ok := ast.HelperNameStr(endID) + if !ok { + errNode(endID, "Erroneous closing expression") + } + + if openName != closeName { + errNode(endID, fmt.Sprintf("%s doesn't match %s", openName, closeName)) + } + + // CLOSE_RAW_BLOCK + tok = p.shift() + if tok.Kind != lexer.TokenCloseRawBlock { + errExpected(lexer.TokenCloseRawBlock, tok) + } + + return result +} + +// block : openBlock program inverseChain? closeBlock +func (p *parser) parseBlock() *ast.BlockStatement { + // openBlock + result, blockParams := p.parseOpenBlock() + + // program + program := p.parseProgram() + program.BlockParams = blockParams + result.Program = program + + // inverseChain? + if p.isInverseChain() { + result.Inverse = p.parseInverseChain() + } + + // closeBlock + p.parseCloseBlock(result) + + setBlockInverseStrip(result) + + return result +} + +// setBlockInverseStrip is called when parsing `block` (openBlock | openInverse) and `inverseChain` +// +// TODO: This was totally cargo culted ! CHECK THAT ! +// +// cf. prepareBlock() in: +// https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/helper.js +func setBlockInverseStrip(block *ast.BlockStatement) { + if block.Inverse == nil { + return + } + + if block.Inverse.Chained { + b, _ := block.Inverse.Body[0].(*ast.BlockStatement) + b.CloseStrip = block.CloseStrip + } + + block.InverseStrip = block.Inverse.Strip +} + +// block : openInverse program inverseAndProgram? closeBlock +func (p *parser) parseInverse() *ast.BlockStatement { + // openInverse + result, blockParams := p.parseOpenBlock() + + // program + program := p.parseProgram() + + program.BlockParams = blockParams + result.Inverse = program + + // inverseAndProgram? + if p.isInverse() { + result.Program = p.parseInverseAndProgram() + } + + // closeBlock + p.parseCloseBlock(result) + + setBlockInverseStrip(result) + + return result +} + +// helperName param* hash? blockParams? +func (p *parser) parseOpenBlockExpression(tok *lexer.Token) (*ast.BlockStatement, []string) { + var blockParams []string + + result := ast.NewBlockStatement(tok.Pos, tok.Line) + + // helperName param* hash? + result.Expression = p.parseExpression(tok) + + // blockParams? + if p.isBlockParams() { + blockParams = p.parseBlockParams() + } + + // named returned values + return result, blockParams +} + +// inverseChain : openInverseChain program inverseChain? +// | inverseAndProgram +func (p *parser) parseInverseChain() *ast.Program { + if p.isInverse() { + // inverseAndProgram + return p.parseInverseAndProgram() + } + + result := ast.NewProgram(p.next().Pos, p.next().Line) + + // openInverseChain + block, blockParams := p.parseOpenBlock() + + // program + program := p.parseProgram() + + program.BlockParams = blockParams + block.Program = program + + // inverseChain? + if p.isInverseChain() { + block.Inverse = p.parseInverseChain() + } + + setBlockInverseStrip(block) + + result.Chained = true + result.AddStatement(block) + + return result +} + +// Returns true if current token starts an inverse chain +func (p *parser) isInverseChain() bool { + return p.isOpenInverseChain() || p.isInverse() +} + +// inverseAndProgram : INVERSE program +func (p *parser) parseInverseAndProgram() *ast.Program { + // INVERSE + tok := p.shift() + + // program + result := p.parseProgram() + result.Strip = ast.NewStripForStr(tok.Val) + + return result +} + +// openBlock : OPEN_BLOCK helperName param* hash? blockParams? CLOSE +// openInverse : OPEN_INVERSE helperName param* hash? blockParams? CLOSE +// openInverseChain: OPEN_INVERSE_CHAIN helperName param* hash? blockParams? CLOSE +func (p *parser) parseOpenBlock() (*ast.BlockStatement, []string) { + // OPEN_BLOCK | OPEN_INVERSE | OPEN_INVERSE_CHAIN + tok := p.shift() + + // helperName param* hash? blockParams? + result, blockParams := p.parseOpenBlockExpression(tok) + + // CLOSE + tokClose := p.shift() + if tokClose.Kind != lexer.TokenClose { + errExpected(lexer.TokenClose, tokClose) + } + + result.OpenStrip = ast.NewStrip(tok.Val, tokClose.Val) + + // named returned values + return result, blockParams +} + +// closeBlock : OPEN_ENDBLOCK helperName CLOSE +func (p *parser) parseCloseBlock(block *ast.BlockStatement) { + // OPEN_ENDBLOCK + tok := p.shift() + if tok.Kind != lexer.TokenOpenEndBlock { + errExpected(lexer.TokenOpenEndBlock, tok) + } + + // helperName + endID := p.parseHelperName() + + closeName, ok := ast.HelperNameStr(endID) + if !ok { + errNode(endID, "Erroneous closing expression") + } + + openName := block.Expression.Canonical() + if openName != closeName { + errNode(endID, fmt.Sprintf("%s doesn't match %s", openName, closeName)) + } + + // CLOSE + tokClose := p.shift() + if tokClose.Kind != lexer.TokenClose { + errExpected(lexer.TokenClose, tokClose) + } + + block.CloseStrip = ast.NewStrip(tok.Val, tokClose.Val) +} + +// mustache : OPEN helperName param* hash? CLOSE +// | OPEN_UNESCAPED helperName param* hash? CLOSE_UNESCAPED +func (p *parser) parseMustache() *ast.MustacheStatement { + // OPEN | OPEN_UNESCAPED + tok := p.shift() + + closeToken := lexer.TokenClose + if tok.Kind == lexer.TokenOpenUnescaped { + closeToken = lexer.TokenCloseUnescaped + } + + unescaped := false + if (tok.Kind == lexer.TokenOpenUnescaped) || (rOpenAmp.MatchString(tok.Val)) { + unescaped = true + } + + result := ast.NewMustacheStatement(tok.Pos, tok.Line, unescaped) + + // helperName param* hash? + result.Expression = p.parseExpression(tok) + + // CLOSE | CLOSE_UNESCAPED + tokClose := p.shift() + if tokClose.Kind != closeToken { + errExpected(closeToken, tokClose) + } + + result.Strip = ast.NewStrip(tok.Val, tokClose.Val) + + return result +} + +// partial : OPEN_PARTIAL partialName param* hash? CLOSE +func (p *parser) parsePartial() *ast.PartialStatement { + // OPEN_PARTIAL + tok := p.shift() + + result := ast.NewPartialStatement(tok.Pos, tok.Line) + + // partialName + result.Name = p.parsePartialName() + + // param* hash? + result.Params, result.Hash = p.parseExpressionParamsHash() + + // CLOSE + tokClose := p.shift() + if tokClose.Kind != lexer.TokenClose { + errExpected(lexer.TokenClose, tokClose) + } + + result.Strip = ast.NewStrip(tok.Val, tokClose.Val) + + return result +} + +// helperName | sexpr +func (p *parser) parseHelperNameOrSexpr() ast.Node { + if p.isSexpr() { + // sexpr + return p.parseSexpr() + } + + // helperName + return p.parseHelperName() +} + +// param : helperName | sexpr +func (p *parser) parseParam() ast.Node { + return p.parseHelperNameOrSexpr() +} + +// Returns true if next tokens represent a `param` +func (p *parser) isParam() bool { + return (p.isSexpr() || p.isHelperName()) && !p.isHashSegment() +} + +// param* +func (p *parser) parseParams() []ast.Node { + var result []ast.Node + + for p.isParam() { + result = append(result, p.parseParam()) + } + + return result +} + +// sexpr : OPEN_SEXPR helperName param* hash? CLOSE_SEXPR +func (p *parser) parseSexpr() *ast.SubExpression { + // OPEN_SEXPR + tok := p.shift() + + result := ast.NewSubExpression(tok.Pos, tok.Line) + + // helperName param* hash? + result.Expression = p.parseExpression(tok) + + // CLOSE_SEXPR + tok = p.shift() + if tok.Kind != lexer.TokenCloseSexpr { + errExpected(lexer.TokenCloseSexpr, tok) + } + + return result +} + +// hash : hashSegment+ +func (p *parser) parseHash() *ast.Hash { + var pairs []*ast.HashPair + + for p.isHashSegment() { + pairs = append(pairs, p.parseHashSegment()) + } + + firstLoc := pairs[0].Location() + + result := ast.NewHash(firstLoc.Pos, firstLoc.Line) + result.Pairs = pairs + + return result +} + +// returns true if next tokens represents a `hashSegment` +func (p *parser) isHashSegment() bool { + return p.have(2) && (p.next().Kind == lexer.TokenID) && (p.nextAt(1).Kind == lexer.TokenEquals) +} + +// hashSegment : ID EQUALS param +func (p *parser) parseHashSegment() *ast.HashPair { + // ID + tok := p.shift() + + // EQUALS + p.shift() + + // param + param := p.parseParam() + + result := ast.NewHashPair(tok.Pos, tok.Line) + result.Key = tok.Val + result.Val = param + + return result +} + +// blockParams : OPEN_BLOCK_PARAMS ID+ CLOSE_BLOCK_PARAMS +func (p *parser) parseBlockParams() []string { + var result []string + + // OPEN_BLOCK_PARAMS + tok := p.shift() + + // ID+ + for p.isID() { + result = append(result, p.shift().Val) + } + + if len(result) == 0 { + errExpected(lexer.TokenID, p.next()) + } + + // CLOSE_BLOCK_PARAMS + tok = p.shift() + if tok.Kind != lexer.TokenCloseBlockParams { + errExpected(lexer.TokenCloseBlockParams, tok) + } + + return result +} + +// helperName : path | dataName | STRING | NUMBER | BOOLEAN | UNDEFINED | NULL +func (p *parser) parseHelperName() ast.Node { + var result ast.Node + + tok := p.next() + + switch tok.Kind { + case lexer.TokenBoolean: + // BOOLEAN + p.shift() + result = ast.NewBooleanLiteral(tok.Pos, tok.Line, (tok.Val == "true"), tok.Val) + case lexer.TokenNumber: + // NUMBER + p.shift() + + val, isInt := parseNumber(tok) + result = ast.NewNumberLiteral(tok.Pos, tok.Line, val, isInt, tok.Val) + case lexer.TokenString: + // STRING + p.shift() + result = ast.NewStringLiteral(tok.Pos, tok.Line, tok.Val) + case lexer.TokenData: + // dataName + result = p.parseDataName() + default: + // path + result = p.parsePath(false) + } + + return result +} + +// parseNumber parses a number +func parseNumber(tok *lexer.Token) (result float64, isInt bool) { + var valInt int + var err error + + valInt, err = strconv.Atoi(tok.Val) + if err == nil { + isInt = true + + result = float64(valInt) + } else { + isInt = false + + result, err = strconv.ParseFloat(tok.Val, 64) + if err != nil { + errToken(tok, fmt.Sprintf("Failed to parse number: %s", tok.Val)) + } + } + + // named returned values + return +} + +// Returns true if next tokens represent a `helperName` +func (p *parser) isHelperName() bool { + switch p.next().Kind { + case lexer.TokenBoolean, lexer.TokenNumber, lexer.TokenString, lexer.TokenData, lexer.TokenID: + return true + } + + return false +} + +// partialName : helperName | sexpr +func (p *parser) parsePartialName() ast.Node { + return p.parseHelperNameOrSexpr() +} + +// dataName : DATA pathSegments +func (p *parser) parseDataName() *ast.PathExpression { + // DATA + p.shift() + + // pathSegments + return p.parsePath(true) +} + +// path : pathSegments +// pathSegments : pathSegments SEP ID +// | ID +func (p *parser) parsePath(data bool) *ast.PathExpression { + var tok *lexer.Token + + // ID + tok = p.shift() + if tok.Kind != lexer.TokenID { + errExpected(lexer.TokenID, tok) + } + + result := ast.NewPathExpression(tok.Pos, tok.Line, data) + result.Part(tok.Val) + + for p.isPathSep() { + // SEP + tok = p.shift() + result.Sep(tok.Val) + + // ID + tok = p.shift() + if tok.Kind != lexer.TokenID { + errExpected(lexer.TokenID, tok) + } + + result.Part(tok.Val) + + if len(result.Parts) > 0 { + switch tok.Val { + case "..", ".", "this": + errToken(tok, "Invalid path: "+result.Original) + } + } + } + + return result +} + +// Ensures there is token to parse at given index +func (p *parser) ensure(index int) { + if p.lexOver { + // nothing more to grab + return + } + + nb := index + 1 + + for len(p.tokens) < nb { + // fetch next token + tok := p.lex.NextToken() + + // queue it + p.tokens = append(p.tokens, &tok) + + if (tok.Kind == lexer.TokenEOF) || (tok.Kind == lexer.TokenError) { + p.lexOver = true + break + } + } +} + +// have returns true is there are a list given number of tokens to consume left +func (p *parser) have(nb int) bool { + p.ensure(nb - 1) + + return len(p.tokens) >= nb +} + +// nextAt returns next token at given index, without consuming it +func (p *parser) nextAt(index int) *lexer.Token { + p.ensure(index) + + return p.tokens[index] +} + +// next returns next token without consuming it +func (p *parser) next() *lexer.Token { + return p.nextAt(0) +} + +// shift returns next token and remove it from the tokens buffer +// +// Panics if next token is `TokenError` +func (p *parser) shift() *lexer.Token { + var result *lexer.Token + + p.ensure(0) + + result, p.tokens = p.tokens[0], p.tokens[1:] + + // check error token + if result.Kind == lexer.TokenError { + errToken(result, "Lexer error") + } + + return result +} + +// isToken returns true if next token is of given type +func (p *parser) isToken(kind lexer.TokenKind) bool { + return p.have(1) && p.next().Kind == kind +} + +// isSexpr returns true if next token starts a sexpr +func (p *parser) isSexpr() bool { + return p.isToken(lexer.TokenOpenSexpr) +} + +// isPathSep returns true if next token is a path separator +func (p *parser) isPathSep() bool { + return p.isToken(lexer.TokenSep) +} + +// isID returns true if next token is an ID +func (p *parser) isID() bool { + return p.isToken(lexer.TokenID) +} + +// isBlockParams returns true if next token starts a block params +func (p *parser) isBlockParams() bool { + return p.isToken(lexer.TokenOpenBlockParams) +} + +// isInverse returns true if next token starts an INVERSE sequence +func (p *parser) isInverse() bool { + return p.isToken(lexer.TokenInverse) +} + +// isOpenInverseChain returns true if next token is OPEN_INVERSE_CHAIN +func (p *parser) isOpenInverseChain() bool { + return p.isToken(lexer.TokenOpenInverseChain) +} diff --git a/vendor/github.com/aymerick/raymond/parser/whitespace.go b/vendor/github.com/aymerick/raymond/parser/whitespace.go new file mode 100644 index 0000000..8f8c2c4 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/parser/whitespace.go @@ -0,0 +1,360 @@ +package parser + +import ( + "regexp" + + "github.com/aymerick/raymond/ast" +) + +// whitespaceVisitor walks through the AST to perform whitespace control +// +// The logic was shamelessly borrowed from: +// https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/whitespace-control.js +type whitespaceVisitor struct { + isRootSeen bool +} + +var ( + rTrimLeft = regexp.MustCompile(`^[ \t]*\r?\n?`) + rTrimLeftMultiple = regexp.MustCompile(`^\s+`) + + rTrimRight = regexp.MustCompile(`[ \t]+$`) + rTrimRightMultiple = regexp.MustCompile(`\s+$`) + + rPrevWhitespace = regexp.MustCompile(`\r?\n\s*?$`) + rPrevWhitespaceStart = regexp.MustCompile(`(^|\r?\n)\s*?$`) + + rNextWhitespace = regexp.MustCompile(`^\s*?\r?\n`) + rNextWhitespaceEnd = regexp.MustCompile(`^\s*?(\r?\n|$)`) + + rPartialIndent = regexp.MustCompile(`([ \t]+$)`) +) + +// newWhitespaceVisitor instanciates a new whitespaceVisitor +func newWhitespaceVisitor() *whitespaceVisitor { + return &whitespaceVisitor{} +} + +// processWhitespaces performs whitespace control on given AST +// +// WARNING: It must be called only once on AST. +func processWhitespaces(node ast.Node) { + node.Accept(newWhitespaceVisitor()) +} + +func omitRightFirst(body []ast.Node, multiple bool) { + omitRight(body, -1, multiple) +} + +func omitRight(body []ast.Node, i int, multiple bool) { + if i+1 >= len(body) { + return + } + + current := body[i+1] + + node, ok := current.(*ast.ContentStatement) + if !ok { + return + } + + if !multiple && node.RightStripped { + return + } + + original := node.Value + + r := rTrimLeft + if multiple { + r = rTrimLeftMultiple + } + + node.Value = r.ReplaceAllString(node.Value, "") + + node.RightStripped = (original != node.Value) +} + +func omitLeftLast(body []ast.Node, multiple bool) { + omitLeft(body, len(body), multiple) +} + +func omitLeft(body []ast.Node, i int, multiple bool) bool { + if i-1 < 0 { + return false + } + + current := body[i-1] + + node, ok := current.(*ast.ContentStatement) + if !ok { + return false + } + + if !multiple && node.LeftStripped { + return false + } + + original := node.Value + + r := rTrimRight + if multiple { + r = rTrimRightMultiple + } + + node.Value = r.ReplaceAllString(node.Value, "") + + node.LeftStripped = (original != node.Value) + + return node.LeftStripped +} + +func isPrevWhitespace(body []ast.Node) bool { + return isPrevWhitespaceProgram(body, len(body), false) +} + +func isPrevWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool { + if i < 1 { + return isRoot + } + + prev := body[i-1] + + if node, ok := prev.(*ast.ContentStatement); ok { + if (node.Value == "") && node.RightStripped { + // already stripped, so it may be an empty string not catched by regexp + return true + } + + r := rPrevWhitespaceStart + if (i > 1) || !isRoot { + r = rPrevWhitespace + } + + return r.MatchString(node.Value) + } + + return false +} + +func isNextWhitespace(body []ast.Node) bool { + return isNextWhitespaceProgram(body, -1, false) +} + +func isNextWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool { + if i+1 >= len(body) { + return isRoot + } + + next := body[i+1] + + if node, ok := next.(*ast.ContentStatement); ok { + if (node.Value == "") && node.LeftStripped { + // already stripped, so it may be an empty string not catched by regexp + return true + } + + r := rNextWhitespaceEnd + if (i+2 > len(body)) || !isRoot { + r = rNextWhitespace + } + + return r.MatchString(node.Value) + } + + return false +} + +// +// Visitor interface +// + +func (v *whitespaceVisitor) VisitProgram(program *ast.Program) interface{} { + isRoot := !v.isRootSeen + v.isRootSeen = true + + body := program.Body + for i, current := range body { + strip, _ := current.Accept(v).(*ast.Strip) + if strip == nil { + continue + } + + _isPrevWhitespace := isPrevWhitespaceProgram(body, i, isRoot) + _isNextWhitespace := isNextWhitespaceProgram(body, i, isRoot) + + openStandalone := strip.OpenStandalone && _isPrevWhitespace + closeStandalone := strip.CloseStandalone && _isNextWhitespace + inlineStandalone := strip.InlineStandalone && _isPrevWhitespace && _isNextWhitespace + + if strip.Close { + omitRight(body, i, true) + } + + if strip.Open && (i > 0) { + omitLeft(body, i, true) + } + + if inlineStandalone { + omitRight(body, i, false) + + if omitLeft(body, i, false) { + // If we are on a standalone node, save the indent info for partials + if partial, ok := current.(*ast.PartialStatement); ok { + // Pull out the whitespace from the final line + if i > 0 { + if prevContent, ok := body[i-1].(*ast.ContentStatement); ok { + partial.Indent = rPartialIndent.FindString(prevContent.Original) + } + } + } + } + } + + if b, ok := current.(*ast.BlockStatement); ok { + if openStandalone { + prog := b.Program + if prog == nil { + prog = b.Inverse + } + + omitRightFirst(prog.Body, false) + + // Strip out the previous content node if it's whitespace only + omitLeft(body, i, false) + } + + if closeStandalone { + prog := b.Inverse + if prog == nil { + prog = b.Program + } + + // Always strip the next node + omitRight(body, i, false) + + omitLeftLast(prog.Body, false) + } + + } + } + + return nil +} + +func (v *whitespaceVisitor) VisitBlock(block *ast.BlockStatement) interface{} { + if block.Program != nil { + block.Program.Accept(v) + } + + if block.Inverse != nil { + block.Inverse.Accept(v) + } + + program := block.Program + inverse := block.Inverse + + if program == nil { + program = inverse + inverse = nil + } + + firstInverse := inverse + lastInverse := inverse + + if (inverse != nil) && inverse.Chained { + b, _ := inverse.Body[0].(*ast.BlockStatement) + firstInverse = b.Program + + for lastInverse.Chained { + b, _ := lastInverse.Body[len(lastInverse.Body)-1].(*ast.BlockStatement) + lastInverse = b.Program + } + } + + closeProg := firstInverse + if closeProg == nil { + closeProg = program + } + + strip := &ast.Strip{ + Open: (block.OpenStrip != nil) && block.OpenStrip.Open, + Close: (block.CloseStrip != nil) && block.CloseStrip.Close, + + OpenStandalone: isNextWhitespace(program.Body), + CloseStandalone: isPrevWhitespace(closeProg.Body), + } + + if (block.OpenStrip != nil) && block.OpenStrip.Close { + omitRightFirst(program.Body, true) + } + + if inverse != nil { + if block.InverseStrip != nil { + inverseStrip := block.InverseStrip + + if inverseStrip.Open { + omitLeftLast(program.Body, true) + } + + if inverseStrip.Close { + omitRightFirst(firstInverse.Body, true) + } + } + + if (block.CloseStrip != nil) && block.CloseStrip.Open { + omitLeftLast(lastInverse.Body, true) + } + + // Find standalone else statements + if isPrevWhitespace(program.Body) && isNextWhitespace(firstInverse.Body) { + omitLeftLast(program.Body, false) + + omitRightFirst(firstInverse.Body, false) + } + } else if (block.CloseStrip != nil) && block.CloseStrip.Open { + omitLeftLast(program.Body, true) + } + + return strip +} + +func (v *whitespaceVisitor) VisitMustache(mustache *ast.MustacheStatement) interface{} { + return mustache.Strip +} + +func _inlineStandalone(strip *ast.Strip) interface{} { + return &ast.Strip{ + Open: strip.Open, + Close: strip.Close, + InlineStandalone: true, + } +} + +func (v *whitespaceVisitor) VisitPartial(node *ast.PartialStatement) interface{} { + strip := node.Strip + if strip == nil { + strip = &ast.Strip{} + } + + return _inlineStandalone(strip) +} + +func (v *whitespaceVisitor) VisitComment(node *ast.CommentStatement) interface{} { + strip := node.Strip + if strip == nil { + strip = &ast.Strip{} + } + + return _inlineStandalone(strip) +} + +// NOOP +func (v *whitespaceVisitor) VisitContent(node *ast.ContentStatement) interface{} { return nil } +func (v *whitespaceVisitor) VisitExpression(node *ast.Expression) interface{} { return nil } +func (v *whitespaceVisitor) VisitSubExpression(node *ast.SubExpression) interface{} { return nil } +func (v *whitespaceVisitor) VisitPath(node *ast.PathExpression) interface{} { return nil } +func (v *whitespaceVisitor) VisitString(node *ast.StringLiteral) interface{} { return nil } +func (v *whitespaceVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} { return nil } +func (v *whitespaceVisitor) VisitNumber(node *ast.NumberLiteral) interface{} { return nil } +func (v *whitespaceVisitor) VisitHash(node *ast.Hash) interface{} { return nil } +func (v *whitespaceVisitor) VisitHashPair(node *ast.HashPair) interface{} { return nil } diff --git a/vendor/github.com/aymerick/raymond/partial.go b/vendor/github.com/aymerick/raymond/partial.go new file mode 100644 index 0000000..3299d02 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/partial.go @@ -0,0 +1,85 @@ +package raymond + +import ( + "fmt" + "sync" +) + +// partial represents a partial template +type partial struct { + name string + source string + tpl *Template +} + +// partials stores all global partials +var partials map[string]*partial + +// protects global partials +var partialsMutex sync.RWMutex + +func init() { + partials = make(map[string]*partial) +} + +// newPartial instanciates a new partial +func newPartial(name string, source string, tpl *Template) *partial { + return &partial{ + name: name, + source: source, + tpl: tpl, + } +} + +// RegisterPartial registers a global partial. That partial will be available to all templates. +func RegisterPartial(name string, source string) { + partialsMutex.Lock() + defer partialsMutex.Unlock() + + if partials[name] != nil { + panic(fmt.Errorf("Partial already registered: %s", name)) + } + + partials[name] = newPartial(name, source, nil) +} + +// RegisterPartials registers several global partials. Those partials will be available to all templates. +func RegisterPartials(partials map[string]string) { + for name, p := range partials { + RegisterPartial(name, p) + } +} + +// RegisterPartialTemplate registers a global partial with given parsed template. That partial will be available to all templates. +func RegisterPartialTemplate(name string, tpl *Template) { + partialsMutex.Lock() + defer partialsMutex.Unlock() + + if partials[name] != nil { + panic(fmt.Errorf("Partial already registered: %s", name)) + } + + partials[name] = newPartial(name, "", tpl) +} + +// findPartial finds a registered global partial +func findPartial(name string) *partial { + partialsMutex.RLock() + defer partialsMutex.RUnlock() + + return partials[name] +} + +// template returns parsed partial template +func (p *partial) template() (*Template, error) { + if p.tpl == nil { + var err error + + p.tpl, err = Parse(p.source) + if err != nil { + return nil, err + } + } + + return p.tpl, nil +} diff --git a/vendor/github.com/aymerick/raymond/raymond.go b/vendor/github.com/aymerick/raymond/raymond.go new file mode 100644 index 0000000..c6df6b3 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/raymond.go @@ -0,0 +1,28 @@ +// Package raymond provides handlebars evaluation +package raymond + +// Render parses a template and evaluates it with given context +// +// Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead. +func Render(source string, ctx interface{}) (string, error) { + // parse template + tpl, err := Parse(source) + if err != nil { + return "", err + } + + // renders template + str, err := tpl.Exec(ctx) + if err != nil { + return "", err + } + + return str, nil +} + +// MustRender parses a template and evaluates it with given context. It panics on error. +// +// Note that this function call is not optimal as your template is parsed everytime you call it. You should use Parse() function instead. +func MustRender(source string, ctx interface{}) string { + return MustParse(source).MustExec(ctx) +} diff --git a/vendor/github.com/aymerick/raymond/raymond.png b/vendor/github.com/aymerick/raymond/raymond.png new file mode 100644 index 0000000..6a7c942 Binary files /dev/null and b/vendor/github.com/aymerick/raymond/raymond.png differ diff --git a/vendor/github.com/aymerick/raymond/string.go b/vendor/github.com/aymerick/raymond/string.go new file mode 100644 index 0000000..7194769 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/string.go @@ -0,0 +1,84 @@ +package raymond + +import ( + "fmt" + "reflect" + "strconv" +) + +// SafeString represents a string that must not be escaped. +// +// A SafeString can be returned by helpers to disable escaping. +type SafeString string + +// isSafeString returns true if argument is a SafeString +func isSafeString(value interface{}) bool { + if _, ok := value.(SafeString); ok { + return true + } + return false +} + +// Str returns string representation of any basic type value. +func Str(value interface{}) string { + return strValue(reflect.ValueOf(value)) +} + +// strValue returns string representation of a reflect.Value +func strValue(value reflect.Value) string { + result := "" + + ival, ok := printableValue(value) + if !ok { + panic(fmt.Errorf("Can't print value: %q", value)) + } + + val := reflect.ValueOf(ival) + + switch val.Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < val.Len(); i++ { + result += strValue(val.Index(i)) + } + case reflect.Bool: + result = "false" + if val.Bool() { + result = "true" + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + result = fmt.Sprintf("%d", ival) + case reflect.Float32, reflect.Float64: + result = strconv.FormatFloat(val.Float(), 'f', -1, 64) + case reflect.Invalid: + result = "" + default: + result = fmt.Sprintf("%s", ival) + } + + return result +} + +// printableValue returns the, possibly indirected, interface value inside v that +// is best for a call to formatted printer. +// +// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go +func printableValue(v reflect.Value) (interface{}, bool) { + if v.Kind() == reflect.Ptr { + v, _ = indirect(v) // fmt.Fprint handles nil. + } + if !v.IsValid() { + return "", true + } + + if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) { + if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) { + v = v.Addr() + } else { + switch v.Kind() { + case reflect.Chan, reflect.Func: + return nil, false + } + } + } + return v.Interface(), true +} diff --git a/vendor/github.com/aymerick/raymond/template.go b/vendor/github.com/aymerick/raymond/template.go new file mode 100644 index 0000000..f16ed2f --- /dev/null +++ b/vendor/github.com/aymerick/raymond/template.go @@ -0,0 +1,248 @@ +package raymond + +import ( + "fmt" + "io/ioutil" + "reflect" + "runtime" + "sync" + + "github.com/aymerick/raymond/ast" + "github.com/aymerick/raymond/parser" +) + +// Template represents a handlebars template. +type Template struct { + source string + program *ast.Program + helpers map[string]reflect.Value + partials map[string]*partial + mutex sync.RWMutex // protects helpers and partials +} + +// newTemplate instanciate a new template without parsing it +func newTemplate(source string) *Template { + return &Template{ + source: source, + helpers: make(map[string]reflect.Value), + partials: make(map[string]*partial), + } +} + +// Parse instanciates a template by parsing given source. +func Parse(source string) (*Template, error) { + tpl := newTemplate(source) + + // parse template + if err := tpl.parse(); err != nil { + return nil, err + } + + return tpl, nil +} + +// MustParse instanciates a template by parsing given source. It panics on error. +func MustParse(source string) *Template { + result, err := Parse(source) + if err != nil { + panic(err) + } + return result +} + +// ParseFile reads given file and returns parsed template. +func ParseFile(filePath string) (*Template, error) { + b, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, err + } + + return Parse(string(b)) +} + +// parse parses the template +// +// It can be called several times, the parsing will be done only once. +func (tpl *Template) parse() error { + if tpl.program == nil { + var err error + + tpl.program, err = parser.Parse(tpl.source) + if err != nil { + return err + } + } + + return nil +} + +// Clone returns a copy of that template. +func (tpl *Template) Clone() *Template { + result := newTemplate(tpl.source) + + result.program = tpl.program + + tpl.mutex.RLock() + defer tpl.mutex.RUnlock() + + for name, helper := range tpl.helpers { + result.RegisterHelper(name, helper.Interface()) + } + + for name, partial := range tpl.partials { + result.addPartial(name, partial.source, partial.tpl) + } + + return result +} + +func (tpl *Template) findHelper(name string) reflect.Value { + tpl.mutex.RLock() + defer tpl.mutex.RUnlock() + + return tpl.helpers[name] +} + +// RegisterHelper registers a helper for that template. +func (tpl *Template) RegisterHelper(name string, helper interface{}) { + tpl.mutex.Lock() + defer tpl.mutex.Unlock() + + if tpl.helpers[name] != zero { + panic(fmt.Sprintf("Helper %s already registered", name)) + } + + val := reflect.ValueOf(helper) + ensureValidHelper(name, val) + + tpl.helpers[name] = val +} + +// RegisterHelpers registers several helpers for that template. +func (tpl *Template) RegisterHelpers(helpers map[string]interface{}) { + for name, helper := range helpers { + tpl.RegisterHelper(name, helper) + } +} + +func (tpl *Template) addPartial(name string, source string, template *Template) { + tpl.mutex.Lock() + defer tpl.mutex.Unlock() + + if tpl.partials[name] != nil { + panic(fmt.Sprintf("Partial %s already registered", name)) + } + + tpl.partials[name] = newPartial(name, source, template) +} + +func (tpl *Template) findPartial(name string) *partial { + tpl.mutex.RLock() + defer tpl.mutex.RUnlock() + + return tpl.partials[name] +} + +// RegisterPartial registers a partial for that template. +func (tpl *Template) RegisterPartial(name string, source string) { + tpl.addPartial(name, source, nil) +} + +// RegisterPartials registers several partials for that template. +func (tpl *Template) RegisterPartials(partials map[string]string) { + for name, partial := range partials { + tpl.RegisterPartial(name, partial) + } +} + +// RegisterPartialFile reads given file and registers its content as a partial with given name. +func (tpl *Template) RegisterPartialFile(filePath string, name string) error { + b, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + tpl.RegisterPartial(name, string(b)) + + return nil +} + +// RegisterPartialFiles reads several files and registers them as partials, the filename base is used as the partial name. +func (tpl *Template) RegisterPartialFiles(filePaths ...string) error { + if len(filePaths) == 0 { + return nil + } + + for _, filePath := range filePaths { + name := fileBase(filePath) + + if err := tpl.RegisterPartialFile(filePath, name); err != nil { + return err + } + } + + return nil +} + +// RegisterPartialTemplate registers an already parsed partial for that template. +func (tpl *Template) RegisterPartialTemplate(name string, template *Template) { + tpl.addPartial(name, "", template) +} + +// Exec evaluates template with given context. +func (tpl *Template) Exec(ctx interface{}) (result string, err error) { + return tpl.ExecWith(ctx, nil) +} + +// MustExec evaluates template with given context. It panics on error. +func (tpl *Template) MustExec(ctx interface{}) string { + result, err := tpl.Exec(ctx) + if err != nil { + panic(err) + } + return result +} + +// ExecWith evaluates template with given context and private data frame. +func (tpl *Template) ExecWith(ctx interface{}, privData *DataFrame) (result string, err error) { + defer errRecover(&err) + + // parses template if necessary + err = tpl.parse() + if err != nil { + return + } + + // setup visitor + v := newEvalVisitor(tpl, ctx, privData) + + // visit AST + result, _ = tpl.program.Accept(v).(string) + + // named return values + return +} + +// errRecover recovers evaluation panic +func errRecover(errp *error) { + e := recover() + if e != nil { + switch err := e.(type) { + case runtime.Error: + panic(e) + case error: + *errp = err + default: + panic(e) + } + } +} + +// PrintAST returns string representation of parsed template. +func (tpl *Template) PrintAST() string { + if err := tpl.parse(); err != nil { + return fmt.Sprintf("PARSER ERROR: %s", err) + } + + return ast.Print(tpl.program) +} diff --git a/vendor/github.com/aymerick/raymond/utils.go b/vendor/github.com/aymerick/raymond/utils.go new file mode 100644 index 0000000..3deaaf3 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/utils.go @@ -0,0 +1,85 @@ +package raymond + +import ( + "path" + "reflect" +) + +// indirect returns the item at the end of indirection, and a bool to indicate if it's nil. +// We indirect through pointers and empty interfaces (only) because +// non-empty interfaces have methods we might need. +// +// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go +func indirect(v reflect.Value) (rv reflect.Value, isNil bool) { + for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() { + if v.IsNil() { + return v, true + } + if v.Kind() == reflect.Interface && v.NumMethod() > 0 { + break + } + } + return v, false +} + +// IsTrue returns true if obj is a truthy value. +func IsTrue(obj interface{}) bool { + thruth, ok := isTrueValue(reflect.ValueOf(obj)) + if !ok { + return false + } + return thruth +} + +// isTrueValue reports whether the value is 'true', in the sense of not the zero of its type, +// and whether the value has a meaningful truth value +// +// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go +func isTrueValue(val reflect.Value) (truth, ok bool) { + if !val.IsValid() { + // Something like var x interface{}, never set. It's a form of nil. + return false, true + } + switch val.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + truth = val.Len() > 0 + case reflect.Bool: + truth = val.Bool() + case reflect.Complex64, reflect.Complex128: + truth = val.Complex() != 0 + case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface: + truth = !val.IsNil() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + truth = val.Int() != 0 + case reflect.Float32, reflect.Float64: + truth = val.Float() != 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + truth = val.Uint() != 0 + case reflect.Struct: + truth = true // Struct values are always true. + default: + return + } + return truth, true +} + +// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero. +// +// NOTE: borrowed from https://github.com/golang/go/tree/master/src/text/template/exec.go +func canBeNil(typ reflect.Type) bool { + switch typ.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return true + } + return false +} + +// fileBase returns base file name +// +// example: /foo/bar/baz.png => baz +func fileBase(filePath string) string { + fileName := path.Base(filePath) + fileExt := path.Ext(filePath) + + return fileName[:len(fileName)-len(fileExt)] +} diff --git a/vendor/github.com/urfave/cli/CHANGELOG.md b/vendor/github.com/urfave/cli/CHANGELOG.md new file mode 100644 index 0000000..07f7546 --- /dev/null +++ b/vendor/github.com/urfave/cli/CHANGELOG.md @@ -0,0 +1,392 @@ +# Change Log + +**ATTN**: This project uses [semantic versioning](http://semver.org/). + +## [Unreleased] + +## [1.19.1] - 2016-11-21 + +### Fixed + +- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as + the `Action` for a command would cause it to error rather than calling the + function. Should not have a affected declarative cases using `func(c + *cli.Context) err)`. +- Shell completion now handles the case where the user specifies + `--generate-bash-completion` immediately after a flag that takes an argument. + Previously it call the application with `--generate-bash-completion` as the + flag value. + +## [1.19.0] - 2016-11-19 +### Added +- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`) +- A `Description` field was added to `App` for a more detailed description of + the application (similar to the existing `Description` field on `Command`) +- Flag type code generation via `go generate` +- Write to stderr and exit 1 if action returns non-nil error +- Added support for TOML to the `altsrc` loader +- `SkipArgReorder` was added to allow users to skip the argument reordering. + This is useful if you want to consider all "flags" after an argument as + arguments rather than flags (the default behavior of the stdlib `flag` + library). This is backported functionality from the [removal of the flag + reordering](https://github.com/urfave/cli/pull/398) in the unreleased version + 2 +- For formatted errors (those implementing `ErrorFormatter`), the errors will + be formatted during output. Compatible with `pkg/errors`. + +### Changed +- Raise minimum tested/supported Go version to 1.2+ + +### Fixed +- Consider empty environment variables as set (previously environment variables + with the equivalent of `""` would be skipped rather than their value used). +- Return an error if the value in a given environment variable cannot be parsed + as the flag type. Previously these errors were silently swallowed. +- Print full error when an invalid flag is specified (which includes the invalid flag) +- `App.Writer` defaults to `stdout` when `nil` +- If no action is specified on a command or app, the help is now printed instead of `panic`ing +- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized) +- Correctly show help message if `-h` is provided to a subcommand +- `context.(Global)IsSet` now respects environment variables. Previously it + would return `false` if a flag was specified in the environment rather than + as an argument +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user +- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This + fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well + as `altsrc` where Go would complain that the types didn't match + +## [1.18.1] - 2016-08-28 +### Fixed +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported) + +## [1.18.0] - 2016-06-27 +### Added +- `./runtests` test runner with coverage tracking by default +- testing on OS X +- testing on Windows +- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code + +### Changed +- Use spaces for alignment in help/usage output instead of tabs, making the + output alignment consistent regardless of tab width + +### Fixed +- Printing of command aliases in help text +- Printing of visible flags for both struct and struct pointer flags +- Display the `help` subcommand when using `CommandCategories` +- No longer swallows `panic`s that occur within the `Action`s themselves when + detecting the signature of the `Action` field + +## [1.17.1] - 2016-08-28 +### Fixed +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user + +## [1.17.0] - 2016-05-09 +### Added +- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` +- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` +- Support for hiding commands by setting `Hidden: true` -- this will hide the + commands in help output + +### Changed +- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer + quoted in help text output. +- All flag types now include `(default: {value})` strings following usage when a + default value can be (reasonably) detected. +- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent + with non-slice flag types +- Apps now exit with a code of 3 if an unknown subcommand is specified + (previously they printed "No help topic for...", but still exited 0. This + makes it easier to script around apps built using `cli` since they can trust + that a 0 exit code indicated a successful execution. +- cleanups based on [Go Report Card + feedback](https://goreportcard.com/report/github.com/urfave/cli) + +## [1.16.1] - 2016-08-28 +### Fixed +- Removed deprecation warnings to STDERR to avoid them leaking to the end-user + +## [1.16.0] - 2016-05-02 +### Added +- `Hidden` field on all flag struct types to omit from generated help text + +### Changed +- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from +generated help text via the `Hidden` field + +### Fixed +- handling of error values in `HandleAction` and `HandleExitCoder` + +## [1.15.0] - 2016-04-30 +### Added +- This file! +- Support for placeholders in flag usage strings +- `App.Metadata` map for arbitrary data/state management +- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after +parsing. +- Support for nested lookup of dot-delimited keys in structures loaded from +YAML. + +### Changed +- The `App.Action` and `Command.Action` now prefer a return signature of +`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil +`error` is returned, there may be two outcomes: + - If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called + automatically + - Else the error is bubbled up and returned from `App.Run` +- Specifying an `Action` with the legacy return signature of +`func(*cli.Context)` will produce a deprecation message to stderr +- Specifying an `Action` that is not a `func` type will produce a non-zero exit +from `App.Run` +- Specifying an `Action` func that has an invalid (input) signature will +produce a non-zero exit from `App.Run` + +### Deprecated +- +`cli.App.RunAndExitOnError`, which should now be done by returning an error +that fulfills `cli.ExitCoder` to `cli.App.Run`. +- the legacy signature for +`cli.App.Action` of `func(*cli.Context)`, which should now have a return +signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. + +### Fixed +- Added missing `*cli.Context.GlobalFloat64` method + +## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) +### Added +- Codebeat badge +- Support for categorization via `CategorizedHelp` and `Categories` on app. + +### Changed +- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`. + +### Fixed +- Ensure version is not shown in help text when `HideVersion` set. + +## [1.13.0] - 2016-03-06 (backfilled 2016-04-25) +### Added +- YAML file input support. +- `NArg` method on context. + +## [1.12.0] - 2016-02-17 (backfilled 2016-04-25) +### Added +- Custom usage error handling. +- Custom text support in `USAGE` section of help output. +- Improved help messages for empty strings. +- AppVeyor CI configuration. + +### Changed +- Removed `panic` from default help printer func. +- De-duping and optimizations. + +### Fixed +- Correctly handle `Before`/`After` at command level when no subcommands. +- Case of literal `-` argument causing flag reordering. +- Environment variable hints on Windows. +- Docs updates. + +## [1.11.1] - 2015-12-21 (backfilled 2016-04-25) +### Changed +- Use `path.Base` in `Name` and `HelpName` +- Export `GetName` on flag types. + +### Fixed +- Flag parsing when skipping is enabled. +- Test output cleanup. +- Move completion check to account for empty input case. + +## [1.11.0] - 2015-11-15 (backfilled 2016-04-25) +### Added +- Destination scan support for flags. +- Testing against `tip` in Travis CI config. + +### Changed +- Go version in Travis CI config. + +### Fixed +- Removed redundant tests. +- Use correct example naming in tests. + +## [1.10.2] - 2015-10-29 (backfilled 2016-04-25) +### Fixed +- Remove unused var in bash completion. + +## [1.10.1] - 2015-10-21 (backfilled 2016-04-25) +### Added +- Coverage and reference logos in README. + +### Fixed +- Use specified values in help and version parsing. +- Only display app version and help message once. + +## [1.10.0] - 2015-10-06 (backfilled 2016-04-25) +### Added +- More tests for existing functionality. +- `ArgsUsage` at app and command level for help text flexibility. + +### Fixed +- Honor `HideHelp` and `HideVersion` in `App.Run`. +- Remove juvenile word from README. + +## [1.9.0] - 2015-09-08 (backfilled 2016-04-25) +### Added +- `FullName` on command with accompanying help output update. +- Set default `$PROG` in bash completion. + +### Changed +- Docs formatting. + +### Fixed +- Removed self-referential imports in tests. + +## [1.8.0] - 2015-06-30 (backfilled 2016-04-25) +### Added +- Support for `Copyright` at app level. +- `Parent` func at context level to walk up context lineage. + +### Fixed +- Global flag processing at top level. + +## [1.7.1] - 2015-06-11 (backfilled 2016-04-25) +### Added +- Aggregate errors from `Before`/`After` funcs. +- Doc comments on flag structs. +- Include non-global flags when checking version and help. +- Travis CI config updates. + +### Fixed +- Ensure slice type flags have non-nil values. +- Collect global flags from the full command hierarchy. +- Docs prose. + +## [1.7.0] - 2015-05-03 (backfilled 2016-04-25) +### Changed +- `HelpPrinter` signature includes output writer. + +### Fixed +- Specify go 1.1+ in docs. +- Set `Writer` when running command as app. + +## [1.6.0] - 2015-03-23 (backfilled 2016-04-25) +### Added +- Multiple author support. +- `NumFlags` at context level. +- `Aliases` at command level. + +### Deprecated +- `ShortName` at command level. + +### Fixed +- Subcommand help output. +- Backward compatible support for deprecated `Author` and `Email` fields. +- Docs regarding `Names`/`Aliases`. + +## [1.5.0] - 2015-02-20 (backfilled 2016-04-25) +### Added +- `After` hook func support at app and command level. + +### Fixed +- Use parsed context when running command as subcommand. +- Docs prose. + +## [1.4.1] - 2015-01-09 (backfilled 2016-04-25) +### Added +- Support for hiding `-h / --help` flags, but not `help` subcommand. +- Stop flag parsing after `--`. + +### Fixed +- Help text for generic flags to specify single value. +- Use double quotes in output for defaults. +- Use `ParseInt` instead of `ParseUint` for int environment var values. +- Use `0` as base when parsing int environment var values. + +## [1.4.0] - 2014-12-12 (backfilled 2016-04-25) +### Added +- Support for environment variable lookup "cascade". +- Support for `Stdout` on app for output redirection. + +### Fixed +- Print command help instead of app help in `ShowCommandHelp`. + +## [1.3.1] - 2014-11-13 (backfilled 2016-04-25) +### Added +- Docs and example code updates. + +### Changed +- Default `-v / --version` flag made optional. + +## [1.3.0] - 2014-08-10 (backfilled 2016-04-25) +### Added +- `FlagNames` at context level. +- Exposed `VersionPrinter` var for more control over version output. +- Zsh completion hook. +- `AUTHOR` section in default app help template. +- Contribution guidelines. +- `DurationFlag` type. + +## [1.2.0] - 2014-08-02 +### Added +- Support for environment variable defaults on flags plus tests. + +## [1.1.0] - 2014-07-15 +### Added +- Bash completion. +- Optional hiding of built-in help command. +- Optional skipping of flag parsing at command level. +- `Author`, `Email`, and `Compiled` metadata on app. +- `Before` hook func support at app and command level. +- `CommandNotFound` func support at app level. +- Command reference available on context. +- `GenericFlag` type. +- `Float64Flag` type. +- `BoolTFlag` type. +- `IsSet` flag helper on context. +- More flag lookup funcs at context level. +- More tests & docs. + +### Changed +- Help template updates to account for presence/absence of flags. +- Separated subcommand help template. +- Exposed `HelpPrinter` var for more control over help output. + +## [1.0.0] - 2013-11-01 +### Added +- `help` flag in default app flag set and each command flag set. +- Custom handling of argument parsing errors. +- Command lookup by name at app level. +- `StringSliceFlag` type and supporting `StringSlice` type. +- `IntSliceFlag` type and supporting `IntSlice` type. +- Slice type flag lookups by name at context level. +- Export of app and command help functions. +- More tests & docs. + +## 0.1.0 - 2013-07-22 +### Added +- Initial implementation. + +[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD +[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0 +[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 +[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 +[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 +[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 +[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 +[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 +[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 +[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 +[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 +[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 +[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 +[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 +[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 +[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 +[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 +[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 +[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 +[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 +[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 +[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 +[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 +[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 diff --git a/vendor/github.com/urfave/cli/LICENSE b/vendor/github.com/urfave/cli/LICENSE new file mode 100644 index 0000000..42a597e --- /dev/null +++ b/vendor/github.com/urfave/cli/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Jeremy Saenz & Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/urfave/cli/README.md b/vendor/github.com/urfave/cli/README.md new file mode 100644 index 0000000..2bbbd8e --- /dev/null +++ b/vendor/github.com/urfave/cli/README.md @@ -0,0 +1,1381 @@ +cli +=== + +[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) +[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) +[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / +[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) + +**Notice:** This is the library formerly known as +`github.com/codegangsta/cli` -- Github will automatically redirect requests +to this repository, but we recommend updating your references for clarity. + +cli is a simple, fast, and fun package for building command line apps in Go. The +goal is to enable developers to write fast and distributable command line +applications in an expressive way. + + + +- [Overview](#overview) +- [Installation](#installation) + * [Supported platforms](#supported-platforms) + * [Using the `v2` branch](#using-the-v2-branch) + * [Pinning to the `v1` releases](#pinning-to-the-v1-releases) +- [Getting Started](#getting-started) +- [Examples](#examples) + * [Arguments](#arguments) + * [Flags](#flags) + + [Placeholder Values](#placeholder-values) + + [Alternate Names](#alternate-names) + + [Ordering](#ordering) + + [Values from the Environment](#values-from-the-environment) + + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) + * [Subcommands](#subcommands) + * [Subcommands categories](#subcommands-categories) + * [Exit code](#exit-code) + * [Bash Completion](#bash-completion) + + [Enabling](#enabling) + + [Distribution](#distribution) + + [Customization](#customization) + * [Generated Help Text](#generated-help-text) + + [Customization](#customization-1) + * [Version Flag](#version-flag) + + [Customization](#customization-2) + + [Full API Example](#full-api-example) +- [Contribution Guidelines](#contribution-guidelines) + + + +## Overview + +Command line apps are usually so tiny that there is absolutely no reason why +your code should *not* be self-documenting. Things like generating help text and +parsing command flags/options should not hinder productivity when writing a +command line app. + +**This is where cli comes into play.** cli makes command line programming fun, +organized, and expressive! + +## Installation + +Make sure you have a working Go environment. Go version 1.2+ is supported. [See +the install instructions for Go](http://golang.org/doc/install.html). + +To install cli, simply run: +``` +$ go get github.com/urfave/cli +``` + +Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can +be easily used: +``` +export PATH=$PATH:$GOPATH/bin +``` + +### Supported platforms + +cli is tested against multiple versions of Go on Linux, and against the latest +released version of Go on OS X and Windows. For full details, see +[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml). + +### Using the `v2` branch + +**Warning**: The `v2` branch is currently unreleased and considered unstable. + +There is currently a long-lived branch named `v2` that is intended to land as +the new `master` branch once development there has settled down. The current +`master` branch (mirrored as `v1`) is being manually merged into `v2` on +an irregular human-based schedule, but generally if one wants to "upgrade" to +`v2` *now* and accept the volatility (read: "awesomeness") that comes along with +that, please use whatever version pinning of your preference, such as via +`gopkg.in`: + +``` +$ go get gopkg.in/urfave/cli.v2 +``` + +``` go +... +import ( + "gopkg.in/urfave/cli.v2" // imports as package "cli" +) +... +``` + +### Pinning to the `v1` releases + +Similarly to the section above describing use of the `v2` branch, if one wants +to avoid any unexpected compatibility pains once `v2` becomes `master`, then +pinning to `v1` is an acceptable option, e.g.: + +``` +$ go get gopkg.in/urfave/cli.v1 +``` + +``` go +... +import ( + "gopkg.in/urfave/cli.v1" // imports as package "cli" +) +... +``` + +This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing). + +## Getting Started + +One of the philosophies behind cli is that an API should be playful and full of +discovery. So a cli app can be as little as one line of code in `main()`. + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.NewApp().Run(os.Args) +} +``` + +This app will run and show help text, but is not very useful. Let's give an +action to execute and some help documentation: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Action = func(c *cli.Context) error { + fmt.Println("boom! I say!") + return nil + } + + app.Run(os.Args) +} +``` + +Running this already gives you a ton of functionality, plus support for things +like subcommands and flags, which are covered below. + +## Examples + +Being a programmer can be a lonely job. Thankfully by the power of automation +that is not the case! Let's create a greeter app to fend off our demons of +loneliness! + +Start by creating a directory named `greet`, and within it, add a file, +`greet.go` with the following code in it: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "greet" + app.Usage = "fight the loneliness!" + app.Action = func(c *cli.Context) error { + fmt.Println("Hello friend!") + return nil + } + + app.Run(os.Args) +} +``` + +Install our command to the `$GOPATH/bin` directory: + +``` +$ go install +``` + +Finally run our new command: + +``` +$ greet +Hello friend! +``` + +cli also generates neat help text: + +``` +$ greet help +NAME: + greet - fight the loneliness! + +USAGE: + greet [global options] command [command options] [arguments...] + +VERSION: + 0.0.0 + +COMMANDS: + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS + --version Shows version information +``` + +### Arguments + +You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Action = func(c *cli.Context) error { + fmt.Printf("Hello %q", c.Args().Get(0)) + return nil + } + + app.Run(os.Args) +} +``` + +### Flags + +Setting and querying flags is simple. + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + } + + app.Action = func(c *cli.Context) error { + name := "Nefertiti" + if c.NArg() > 0 { + name = c.Args().Get(0) + } + if c.String("lang") == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + app.Run(os.Args) +} +``` + +You can also set a destination variable for a flag, to which the content will be +scanned. + + +``` go +package main + +import ( + "os" + "fmt" + + "github.com/urfave/cli" +) + +func main() { + var language string + + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + Destination: &language, + }, + } + + app.Action = func(c *cli.Context) error { + name := "someone" + if c.NArg() > 0 { + name = c.Args()[0] + } + if language == "spanish" { + fmt.Println("Hola", name) + } else { + fmt.Println("Hello", name) + } + return nil + } + + app.Run(os.Args) +} +``` + +See full list of flags at http://godoc.org/github.com/urfave/cli + +#### Placeholder Values + +Sometimes it's useful to specify a flag's value within the usage string itself. +Such placeholders are indicated with back quotes. + +For example this: + + +```go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + app.Run(os.Args) +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +``` + +Note that only the first placeholder is used. Subsequent back-quoted words will +be left as-is. + +#### Alternate Names + +You can set alternate (or short) names for flags by providing a comma-delimited +list for the `Name`. e.g. + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + }, + } + + app.Run(os.Args) +} +``` + +That flag can then be set with `--lang spanish` or `-l spanish`. Note that +giving two different forms of the same flag in the same command invocation is an +error. + +#### Ordering + +Flags for the application and commands are shown in the order they are defined. +However, it's possible to sort them from outside this library by using `FlagsByName` +or `CommandsByName` with `sort`. + +For example this: + + +``` go +package main + +import ( + "os" + "sort" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "Language for the greeting", + }, + cli.StringFlag{ + Name: "config, c", + Usage: "Load configuration from `FILE`", + }, + } + + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + return nil + }, + }, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + + app.Run(os.Args) +} +``` + +Will result in help output like: + +``` +--config FILE, -c FILE Load configuration from FILE +--lang value, -l value Language for the greeting (default: "english") +``` + +#### Values from the Environment + +You can also have the default value set from the environment via `EnvVar`. e.g. + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "APP_LANG", + }, + } + + app.Run(os.Args) +} +``` + +The `EnvVar` may also be given as a comma-delimited "cascade", where the first +environment variable that resolves is used as the default. + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Flags = []cli.Flag { + cli.StringFlag{ + Name: "lang, l", + Value: "english", + Usage: "language for the greeting", + EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", + }, + } + + app.Run(os.Args) +} +``` + +#### Values from alternate input sources (YAML, TOML, and others) + +There is a separate package altsrc that adds support for getting flag values +from other file input sources. + +Currently supported input source formats: +* YAML +* TOML + +In order to get values for a flag from an alternate input source the following +code would be added to wrap an existing cli.Flag like below: + +``` go + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) +``` + +Initialization must also occur for these flags. Below is an example initializing +getting data from a yaml file below. + +``` go + command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) +``` + +The code above will use the "load" string as a flag name to get the file name of +a yaml file from the cli.Context. It will then use that file name to initialize +the yaml input source for any flags that are defined on that command. As a note +the "load" flag used would also have to be defined on the command flags in order +for this code snipped to work. + +Currently only the aboved specified formats are supported but developers can +add support for other input sources by implementing the +altsrc.InputSourceContext for their given sources. + +Here is a more complete sample of a command using YAML support: + + +``` go +package notmain + +import ( + "fmt" + "os" + + "github.com/urfave/cli" + "github.com/urfave/cli/altsrc" +) + +func main() { + app := cli.NewApp() + + flags := []cli.Flag{ + altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}, + } + + app.Action = func(c *cli.Context) error { + fmt.Println("yaml ist rad") + return nil + } + + app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) + app.Flags = flags + + app.Run(os.Args) +} +``` + +### Subcommands + +Subcommands can be defined for a more git-like command line app. + + +```go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "add a task to the list", + Action: func(c *cli.Context) error { + fmt.Println("added task: ", c.Args().First()) + return nil + }, + }, + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + }, + { + Name: "template", + Aliases: []string{"t"}, + Usage: "options for task templates", + Subcommands: []cli.Command{ + { + Name: "add", + Usage: "add a new template", + Action: func(c *cli.Context) error { + fmt.Println("new task template: ", c.Args().First()) + return nil + }, + }, + { + Name: "remove", + Usage: "remove an existing template", + Action: func(c *cli.Context) error { + fmt.Println("removed task template: ", c.Args().First()) + return nil + }, + }, + }, + }, + } + + app.Run(os.Args) +} +``` + +### Subcommands categories + +For additional organization in apps that have many subcommands, you can +associate a category for each command to group them together in the help +output. + +E.g. + +```go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + + app.Commands = []cli.Command{ + { + Name: "noop", + }, + { + Name: "add", + Category: "template", + }, + { + Name: "remove", + Category: "template", + }, + } + + app.Run(os.Args) +} +``` + +Will include: + +``` +COMMANDS: + noop + + Template actions: + add + remove +``` + +### Exit code + +Calling `App.Run` will not automatically call `os.Exit`, which means that by +default the exit code will "fall through" to being `0`. An explicit exit code +may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a +`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Flags = []cli.Flag{ + cli.BoolTFlag{ + Name: "ginger-crouton", + Usage: "is it in the soup?", + }, + } + app.Action = func(ctx *cli.Context) error { + if !ctx.Bool("ginger-crouton") { + return cli.NewExitError("it is not in the soup", 86) + } + return nil + } + + app.Run(os.Args) +} +``` + +### Bash Completion + +You can enable completion commands by setting the `EnableBashCompletion` +flag on the `App` object. By default, this setting will only auto-complete to +show an app's subcommands, but you can write your own completion methods for +the App or its subcommands. + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +func main() { + tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "complete", + Aliases: []string{"c"}, + Usage: "complete a task on the list", + Action: func(c *cli.Context) error { + fmt.Println("completed task: ", c.Args().First()) + return nil + }, + BashComplete: func(c *cli.Context) { + // This will complete if no args are passed + if c.NArg() > 0 { + return + } + for _, t := range tasks { + fmt.Println(t) + } + }, + }, + } + + app.Run(os.Args) +} +``` + +#### Enabling + +Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while +setting the `PROG` variable to the name of your program: + +`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` + +#### Distribution + +Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename +it to the name of the program you wish to add autocomplete support for (or +automatically install it there if you are distributing a package). Don't forget +to source the file to make it active in the current shell. + +``` +sudo cp src/bash_autocomplete /etc/bash_completion.d/ +source /etc/bash_completion.d/ +``` + +Alternatively, you can just document that users should source the generic +`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set +to the name of their program (as above). + +#### Customization + +The default bash completion flag (`--generate-bash-completion`) is defined as +`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.BashCompletionFlag = cli.BoolFlag{ + Name: "compgen", + Hidden: true, + } + + app := cli.NewApp() + app.EnableBashCompletion = true + app.Commands = []cli.Command{ + { + Name: "wat", + }, + } + app.Run(os.Args) +} +``` + +### Generated Help Text + +The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked +by the cli internals in order to print generated help text for the app, command, +or subcommand, and break execution. + +#### Customization + +All of the help text generation may be customized, and at multiple levels. The +templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and +`SubcommandHelpTemplate` which may be reassigned or augmented, and full override +is possible by assigning a compatible func to the `cli.HelpPrinter` variable, +e.g.: + + +``` go +package main + +import ( + "fmt" + "io" + "os" + + "github.com/urfave/cli" +) + +func main() { + // EXAMPLE: Append to an existing template + cli.AppHelpTemplate = fmt.Sprintf(`%s + +WEBSITE: http://awesometown.example.com + +SUPPORT: support@awesometown.example.com + +`, cli.AppHelpTemplate) + + // EXAMPLE: Override a template + cli.AppHelpTemplate = `NAME: + {{.Name}} - {{.Usage}} +USAGE: + {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} + {{if len .Authors}} +AUTHOR: + {{range .Authors}}{{ . }}{{end}} + {{end}}{{if .Commands}} +COMMANDS: +{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +GLOBAL OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}}{{if .Copyright }} +COPYRIGHT: + {{.Copyright}} + {{end}}{{if .Version}} +VERSION: + {{.Version}} + {{end}} +` + + // EXAMPLE: Replace the `HelpPrinter` func + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Println("Ha HA. I pwnd the help!!1") + } + + cli.NewApp().Run(os.Args) +} +``` + +The default flag may be customized to something other than `-h/--help` by +setting `cli.HelpFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.HelpFlag = cli.BoolFlag{ + Name: "halp, haaaaalp", + Usage: "HALP", + EnvVar: "SHOW_HALP,HALPPLZ", + } + + cli.NewApp().Run(os.Args) +} +``` + +### Version Flag + +The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which +is checked by the cli internals in order to print the `App.Version` via +`cli.VersionPrinter` and break execution. + +#### Customization + +The default flag may be customized to something other than `-v/--version` by +setting `cli.VersionFlag`, e.g.: + + +``` go +package main + +import ( + "os" + + "github.com/urfave/cli" +) + +func main() { + cli.VersionFlag = cli.BoolFlag{ + Name: "print-version, V", + Usage: "print only the version", + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + app.Run(os.Args) +} +``` + +Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: + + +``` go +package main + +import ( + "fmt" + "os" + + "github.com/urfave/cli" +) + +var ( + Revision = "fafafaf" +) + +func main() { + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) + } + + app := cli.NewApp() + app.Name = "partay" + app.Version = "19.99.0" + app.Run(os.Args) +} +``` + +#### Full API Example + +**Notice**: This is a contrived (functioning) example meant strictly for API +demonstration purposes. Use of one's imagination is encouraged. + + +``` go +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "time" + + "github.com/urfave/cli" +) + +func init() { + cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" + cli.CommandHelpTemplate += "\nYMMV\n" + cli.SubcommandHelpTemplate += "\nor something\n" + + cli.HelpFlag = cli.BoolFlag{Name: "halp"} + cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} + cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} + + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + fmt.Fprintf(w, "best of luck to you\n") + } + cli.VersionPrinter = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) + } + cli.OsExiter = func(c int) { + fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) + } + cli.ErrWriter = ioutil.Discard + cli.FlagStringer = func(fl cli.Flag) string { + return fmt.Sprintf("\t\t%s", fl.GetName()) + } +} + +type hexWriter struct{} + +func (w *hexWriter) Write(p []byte) (int, error) { + for _, b := range p { + fmt.Printf("%x", b) + } + fmt.Printf("\n") + + return len(p), nil +} + +type genericType struct{ + s string +} + +func (g *genericType) Set(value string) error { + g.s = value + return nil +} + +func (g *genericType) String() string { + return g.s +} + +func main() { + app := cli.NewApp() + app.Name = "kənˈtrīv" + app.Version = "19.99.0" + app.Compiled = time.Now() + app.Authors = []cli.Author{ + cli.Author{ + Name: "Example Human", + Email: "human@example.com", + }, + } + app.Copyright = "(c) 1999 Serious Enterprise" + app.HelpName = "contrive" + app.Usage = "demonstrate available API" + app.UsageText = "contrive - demonstrating the available API" + app.ArgsUsage = "[args and such]" + app.Commands = []cli.Command{ + cli.Command{ + Name: "doo", + Aliases: []string{"do"}, + Category: "motion", + Usage: "do the doo", + UsageText: "doo - does the dooing", + Description: "no really, there is a lot of dooing to be done", + ArgsUsage: "[arrgh]", + Flags: []cli.Flag{ + cli.BoolFlag{Name: "forever, forevvarr"}, + }, + Subcommands: cli.Commands{ + cli.Command{ + Name: "wop", + Action: wopAction, + }, + }, + SkipFlagParsing: false, + HideHelp: false, + Hidden: false, + HelpName: "doo!", + BashComplete: func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "--better\n") + }, + Before: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "brace for impact\n") + return nil + }, + After: func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") + return nil + }, + Action: func(c *cli.Context) error { + c.Command.FullName() + c.Command.HasName("wop") + c.Command.Names() + c.Command.VisibleFlags() + fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") + if c.Bool("forever") { + c.Command.Run(c) + } + return nil + }, + OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { + fmt.Fprintf(c.App.Writer, "for shame\n") + return err + }, + }, + } + app.Flags = []cli.Flag{ + cli.BoolFlag{Name: "fancy"}, + cli.BoolTFlag{Name: "fancier"}, + cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, + cli.Float64Flag{Name: "howmuch"}, + cli.GenericFlag{Name: "wat", Value: &genericType{}}, + cli.Int64Flag{Name: "longdistance"}, + cli.Int64SliceFlag{Name: "intervals"}, + cli.IntFlag{Name: "distance"}, + cli.IntSliceFlag{Name: "times"}, + cli.StringFlag{Name: "dance-move, d"}, + cli.StringSliceFlag{Name: "names, N"}, + cli.UintFlag{Name: "age"}, + cli.Uint64Flag{Name: "bigage"}, + } + app.EnableBashCompletion = true + app.HideHelp = false + app.HideVersion = false + app.BashComplete = func(c *cli.Context) { + fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") + } + app.Before = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") + return nil + } + app.After = func(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, "Phew!\n") + return nil + } + app.CommandNotFound = func(c *cli.Context, command string) { + fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) + } + app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { + if isSubcommand { + return err + } + + fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) + return nil + } + app.Action = func(c *cli.Context) error { + cli.DefaultAppComplete(c) + cli.HandleExitCoder(errors.New("not an exit coder, though")) + cli.ShowAppHelp(c) + cli.ShowCommandCompletions(c, "nope") + cli.ShowCommandHelp(c, "also-nope") + cli.ShowCompletions(c) + cli.ShowSubcommandHelp(c) + cli.ShowVersion(c) + + categories := c.App.Categories() + categories.AddCommand("sounds", cli.Command{ + Name: "bloop", + }) + + for _, category := range c.App.Categories() { + fmt.Fprintf(c.App.Writer, "%s\n", category.Name) + fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) + fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) + } + + fmt.Printf("%#v\n", c.App.Command("doo")) + if c.Bool("infinite") { + c.App.Run([]string{"app", "doo", "wop"}) + } + + if c.Bool("forevar") { + c.App.RunAsSubcommand(c) + } + c.App.Setup() + fmt.Printf("%#v\n", c.App.VisibleCategories()) + fmt.Printf("%#v\n", c.App.VisibleCommands()) + fmt.Printf("%#v\n", c.App.VisibleFlags()) + + fmt.Printf("%#v\n", c.Args().First()) + if len(c.Args()) > 0 { + fmt.Printf("%#v\n", c.Args()[1]) + } + fmt.Printf("%#v\n", c.Args().Present()) + fmt.Printf("%#v\n", c.Args().Tail()) + + set := flag.NewFlagSet("contrive", 0) + nc := cli.NewContext(c.App, set, c) + + fmt.Printf("%#v\n", nc.Args()) + fmt.Printf("%#v\n", nc.Bool("nope")) + fmt.Printf("%#v\n", nc.BoolT("nerp")) + fmt.Printf("%#v\n", nc.Duration("howlong")) + fmt.Printf("%#v\n", nc.Float64("hay")) + fmt.Printf("%#v\n", nc.Generic("bloop")) + fmt.Printf("%#v\n", nc.Int64("bonk")) + fmt.Printf("%#v\n", nc.Int64Slice("burnks")) + fmt.Printf("%#v\n", nc.Int("bips")) + fmt.Printf("%#v\n", nc.IntSlice("blups")) + fmt.Printf("%#v\n", nc.String("snurt")) + fmt.Printf("%#v\n", nc.StringSlice("snurkles")) + fmt.Printf("%#v\n", nc.Uint("flub")) + fmt.Printf("%#v\n", nc.Uint64("florb")) + fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) + fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) + fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) + fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) + fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) + fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) + fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) + fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) + fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) + + fmt.Printf("%#v\n", nc.FlagNames()) + fmt.Printf("%#v\n", nc.GlobalFlagNames()) + fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) + fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) + fmt.Printf("%#v\n", nc.NArg()) + fmt.Printf("%#v\n", nc.NumFlags()) + fmt.Printf("%#v\n", nc.Parent()) + + nc.Set("wat", "also-nope") + + ec := cli.NewExitError("ohwell", 86) + fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) + fmt.Printf("made it!\n") + return ec + } + + if os.Getenv("HEXY") != "" { + app.Writer = &hexWriter{} + app.ErrWriter = &hexWriter{} + } + + app.Metadata = map[string]interface{}{ + "layers": "many", + "explicable": false, + "whatever-values": 19.99, + } + + app.Run(os.Args) +} + +func wopAction(c *cli.Context) error { + fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") + return nil +} +``` + +## Contribution Guidelines + +Feel free to put up a pull request to fix a bug or maybe add a feature. I will +give it a code review and make sure that it does not break backwards +compatibility. If I or any other collaborators agree that it is in line with +the vision of the project, we will work with you to get the code into +a mergeable state and merge it into the master branch. + +If you have contributed something significant to the project, we will most +likely add you as a collaborator. As a collaborator you are given the ability +to merge others pull requests. It is very important that new code does not +break existing code, so be careful about what code you do choose to merge. + +If you feel like you have contributed to the project but have not yet been +added as a collaborator, we probably forgot to add you, please open an issue. diff --git a/vendor/github.com/urfave/cli/app.go b/vendor/github.com/urfave/cli/app.go new file mode 100644 index 0000000..51fc45d --- /dev/null +++ b/vendor/github.com/urfave/cli/app.go @@ -0,0 +1,497 @@ +package cli + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sort" + "time" +) + +var ( + changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" + appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) + runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) + + contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." + + errInvalidActionType = NewExitError("ERROR invalid Action type. "+ + fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ + fmt.Sprintf("See %s", appActionDeprecationURL), 2) +) + +// App is the main structure of a cli application. It is recommended that +// an app be created with the cli.NewApp() function +type App struct { + // The name of the program. Defaults to path.Base(os.Args[0]) + Name string + // Full name of command for help, defaults to Name + HelpName string + // Description of the program. + Usage string + // Text to override the USAGE section of help + UsageText string + // Description of the program argument format. + ArgsUsage string + // Version of the program + Version string + // Description of the program + Description string + // List of commands to execute + Commands []Command + // List of flags to parse + Flags []Flag + // Boolean to enable bash completion commands + EnableBashCompletion bool + // Boolean to hide built-in help command + HideHelp bool + // Boolean to hide built-in version flag and the VERSION section of help + HideVersion bool + // Populate on app startup, only gettable through method Categories() + categories CommandCategories + // An action to execute when the bash-completion flag is set + BashComplete BashCompleteFunc + // An action to execute before any subcommands are run, but after the context is ready + // If a non-nil error is returned, no subcommands are run + Before BeforeFunc + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + + // The action to execute when no subcommands are specified + // Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}` + // *Note*: support for the deprecated `Action` signature will be removed in a future version + Action interface{} + + // Execute this function if the proper command cannot be found + CommandNotFound CommandNotFoundFunc + // Execute this function if an usage error occurs + OnUsageError OnUsageErrorFunc + // Compilation date + Compiled time.Time + // List of all authors who contributed + Authors []Author + // Copyright of the binary if any + Copyright string + // Name of Author (Note: Use App.Authors, this is deprecated) + Author string + // Email of Author (Note: Use App.Authors, this is deprecated) + Email string + // Writer writer to write output to + Writer io.Writer + // ErrWriter writes error output + ErrWriter io.Writer + // Other custom info + Metadata map[string]interface{} + // Carries a function which returns app specific info. + ExtraInfo func() map[string]string + // CustomAppHelpTemplate the text template for app help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomAppHelpTemplate string + + didSetup bool +} + +// Tries to find out when this binary was compiled. +// Returns the current time if it fails to find it. +func compileTime() time.Time { + info, err := os.Stat(os.Args[0]) + if err != nil { + return time.Now() + } + return info.ModTime() +} + +// NewApp creates a new cli Application with some reasonable defaults for Name, +// Usage, Version and Action. +func NewApp() *App { + return &App{ + Name: filepath.Base(os.Args[0]), + HelpName: filepath.Base(os.Args[0]), + Usage: "A new cli application", + UsageText: "", + Version: "0.0.0", + BashComplete: DefaultAppComplete, + Action: helpCommand.Action, + Compiled: compileTime(), + Writer: os.Stdout, + } +} + +// Setup runs initialization code to ensure all data structures are ready for +// `Run` or inspection prior to `Run`. It is internally called by `Run`, but +// will return early if setup has already happened. +func (a *App) Setup() { + if a.didSetup { + return + } + + a.didSetup = true + + if a.Author != "" || a.Email != "" { + a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) + } + + newCmds := []Command{} + for _, c := range a.Commands { + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } + newCmds = append(newCmds, c) + } + a.Commands = newCmds + + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } + } + + if !a.HideVersion { + a.appendFlag(VersionFlag) + } + + a.categories = CommandCategories{} + for _, command := range a.Commands { + a.categories = a.categories.AddCommand(command.Category, command) + } + sort.Sort(a.categories) + + if a.Metadata == nil { + a.Metadata = make(map[string]interface{}) + } + + if a.Writer == nil { + a.Writer = os.Stdout + } +} + +// Run is the entry point to the cli app. Parses the arguments slice and routes +// to the proper flag/args combination +func (a *App) Run(arguments []string) (err error) { + a.Setup() + + // handle the completion flag separately from the flagset since + // completion could be attempted after a flag, but before its value was put + // on the command line. this causes the flagset to interpret the completion + // flag name as the value of the flag before it which is undesirable + // note that we can only do this because the shell autocomplete function + // always appends the completion flag at the end of the command + shellComplete, arguments := checkShellCompleteFlag(a, arguments) + + // parse flags + set, err := flagSet(a.Name, a.Flags) + if err != nil { + return err + } + + set.SetOutput(ioutil.Discard) + err = set.Parse(arguments[1:]) + nerr := normalizeFlags(a.Flags, set) + context := NewContext(a, set, nil) + if nerr != nil { + fmt.Fprintln(a.Writer, nerr) + ShowAppHelp(context) + return nerr + } + context.shellComplete = shellComplete + + if checkCompletions(context) { + return nil + } + + if err != nil { + if a.OnUsageError != nil { + err := a.OnUsageError(context, err, false) + HandleExitCoder(err) + return err + } + fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + ShowAppHelp(context) + return err + } + + if !a.HideHelp && checkHelp(context) { + ShowAppHelp(context) + return nil + } + + if !a.HideVersion && checkVersion(context) { + ShowVersion(context) + return nil + } + + if a.After != nil { + defer func() { + if afterErr := a.After(context); afterErr != nil { + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if a.Before != nil { + beforeErr := a.Before(context) + if beforeErr != nil { + ShowAppHelp(context) + HandleExitCoder(beforeErr) + err = beforeErr + return err + } + } + + args := context.Args() + if args.Present() { + name := args.First() + c := a.Command(name) + if c != nil { + return c.Run(context) + } + } + + if a.Action == nil { + a.Action = helpCommand.Action + } + + // Run default Action + err = HandleAction(a.Action, context) + + HandleExitCoder(err) + return err +} + +// RunAndExitOnError calls .Run() and exits non-zero if an error was returned +// +// Deprecated: instead you should return an error that fulfills cli.ExitCoder +// to cli.App.Run. This will cause the application to exit with the given eror +// code in the cli.ExitCoder +func (a *App) RunAndExitOnError() { + if err := a.Run(os.Args); err != nil { + fmt.Fprintln(a.errWriter(), err) + OsExiter(1) + } +} + +// RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to +// generate command-specific flags +func (a *App) RunAsSubcommand(ctx *Context) (err error) { + // append help to commands + if len(a.Commands) > 0 { + if a.Command(helpCommand.Name) == nil && !a.HideHelp { + a.Commands = append(a.Commands, helpCommand) + if (HelpFlag != BoolFlag{}) { + a.appendFlag(HelpFlag) + } + } + } + + newCmds := []Command{} + for _, c := range a.Commands { + if c.HelpName == "" { + c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) + } + newCmds = append(newCmds, c) + } + a.Commands = newCmds + + // parse flags + set, err := flagSet(a.Name, a.Flags) + if err != nil { + return err + } + + set.SetOutput(ioutil.Discard) + err = set.Parse(ctx.Args().Tail()) + nerr := normalizeFlags(a.Flags, set) + context := NewContext(a, set, ctx) + + if nerr != nil { + fmt.Fprintln(a.Writer, nerr) + fmt.Fprintln(a.Writer) + if len(a.Commands) > 0 { + ShowSubcommandHelp(context) + } else { + ShowCommandHelp(ctx, context.Args().First()) + } + return nerr + } + + if checkCompletions(context) { + return nil + } + + if err != nil { + if a.OnUsageError != nil { + err = a.OnUsageError(context, err, true) + HandleExitCoder(err) + return err + } + fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + ShowSubcommandHelp(context) + return err + } + + if len(a.Commands) > 0 { + if checkSubcommandHelp(context) { + return nil + } + } else { + if checkCommandHelp(ctx, context.Args().First()) { + return nil + } + } + + if a.After != nil { + defer func() { + afterErr := a.After(context) + if afterErr != nil { + HandleExitCoder(err) + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if a.Before != nil { + beforeErr := a.Before(context) + if beforeErr != nil { + HandleExitCoder(beforeErr) + err = beforeErr + return err + } + } + + args := context.Args() + if args.Present() { + name := args.First() + c := a.Command(name) + if c != nil { + return c.Run(context) + } + } + + // Run default Action + err = HandleAction(a.Action, context) + + HandleExitCoder(err) + return err +} + +// Command returns the named command on App. Returns nil if the command does not exist +func (a *App) Command(name string) *Command { + for _, c := range a.Commands { + if c.HasName(name) { + return &c + } + } + + return nil +} + +// Categories returns a slice containing all the categories with the commands they contain +func (a *App) Categories() CommandCategories { + return a.categories +} + +// VisibleCategories returns a slice of categories and commands that are +// Hidden=false +func (a *App) VisibleCategories() []*CommandCategory { + ret := []*CommandCategory{} + for _, category := range a.categories { + if visible := func() *CommandCategory { + for _, command := range category.Commands { + if !command.Hidden { + return category + } + } + return nil + }(); visible != nil { + ret = append(ret, visible) + } + } + return ret +} + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (a *App) VisibleCommands() []Command { + ret := []Command{} + for _, command := range a.Commands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} + +// VisibleFlags returns a slice of the Flags with Hidden=false +func (a *App) VisibleFlags() []Flag { + return visibleFlags(a.Flags) +} + +func (a *App) hasFlag(flag Flag) bool { + for _, f := range a.Flags { + if flag == f { + return true + } + } + + return false +} + +func (a *App) errWriter() io.Writer { + + // When the app ErrWriter is nil use the package level one. + if a.ErrWriter == nil { + return ErrWriter + } + + return a.ErrWriter +} + +func (a *App) appendFlag(flag Flag) { + if !a.hasFlag(flag) { + a.Flags = append(a.Flags, flag) + } +} + +// Author represents someone who has contributed to a cli project. +type Author struct { + Name string // The Authors name + Email string // The Authors email +} + +// String makes Author comply to the Stringer interface, to allow an easy print in the templating process +func (a Author) String() string { + e := "" + if a.Email != "" { + e = " <" + a.Email + ">" + } + + return fmt.Sprintf("%v%v", a.Name, e) +} + +// HandleAction attempts to figure out which Action signature was used. If +// it's an ActionFunc or a func with the legacy signature for Action, the func +// is run! +func HandleAction(action interface{}, context *Context) (err error) { + if a, ok := action.(ActionFunc); ok { + return a(context) + } else if a, ok := action.(func(*Context) error); ok { + return a(context) + } else if a, ok := action.(func(*Context)); ok { // deprecated function signature + a(context) + return nil + } else { + return errInvalidActionType + } +} diff --git a/vendor/github.com/urfave/cli/appveyor.yml b/vendor/github.com/urfave/cli/appveyor.yml new file mode 100644 index 0000000..698b188 --- /dev/null +++ b/vendor/github.com/urfave/cli/appveyor.yml @@ -0,0 +1,24 @@ +version: "{build}" + +os: Windows Server 2012 R2 + +clone_folder: c:\gopath\src\github.com\urfave\cli + +environment: + GOPATH: C:\gopath + GOVERSION: 1.6 + PYTHON: C:\Python27-x64 + PYTHON_VERSION: 2.7.x + PYTHON_ARCH: 64 + +install: +- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% +- go version +- go env +- go get github.com/urfave/gfmrun/... +- go get -v -t ./... + +build_script: +- python runtests vet +- python runtests test +- python runtests gfmrun diff --git a/vendor/github.com/urfave/cli/category.go b/vendor/github.com/urfave/cli/category.go new file mode 100644 index 0000000..1a60550 --- /dev/null +++ b/vendor/github.com/urfave/cli/category.go @@ -0,0 +1,44 @@ +package cli + +// CommandCategories is a slice of *CommandCategory. +type CommandCategories []*CommandCategory + +// CommandCategory is a category containing commands. +type CommandCategory struct { + Name string + Commands Commands +} + +func (c CommandCategories) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandCategories) Len() int { + return len(c) +} + +func (c CommandCategories) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +// AddCommand adds a command to a category. +func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { + for _, commandCategory := range c { + if commandCategory.Name == category { + commandCategory.Commands = append(commandCategory.Commands, command) + return c + } + } + return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) +} + +// VisibleCommands returns a slice of the Commands with Hidden=false +func (c *CommandCategory) VisibleCommands() []Command { + ret := []Command{} + for _, command := range c.Commands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} diff --git a/vendor/github.com/urfave/cli/cli.go b/vendor/github.com/urfave/cli/cli.go new file mode 100644 index 0000000..90c07eb --- /dev/null +++ b/vendor/github.com/urfave/cli/cli.go @@ -0,0 +1,22 @@ +// Package cli provides a minimal framework for creating and organizing command line +// Go applications. cli is designed to be easy to understand and write, the most simple +// cli application can be written as follows: +// func main() { +// cli.NewApp().Run(os.Args) +// } +// +// Of course this application does not do much, so let's make this an actual application: +// func main() { +// app := cli.NewApp() +// app.Name = "greet" +// app.Usage = "say a greeting" +// app.Action = func(c *cli.Context) error { +// println("Greetings") +// return nil +// } +// +// app.Run(os.Args) +// } +package cli + +//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go diff --git a/vendor/github.com/urfave/cli/command.go b/vendor/github.com/urfave/cli/command.go new file mode 100644 index 0000000..23de294 --- /dev/null +++ b/vendor/github.com/urfave/cli/command.go @@ -0,0 +1,304 @@ +package cli + +import ( + "fmt" + "io/ioutil" + "sort" + "strings" +) + +// Command is a subcommand for a cli.App. +type Command struct { + // The name of the command + Name string + // short name of the command. Typically one character (deprecated, use `Aliases`) + ShortName string + // A list of aliases for the command + Aliases []string + // A short description of the usage of this command + Usage string + // Custom text to show on USAGE section of help + UsageText string + // A longer explanation of how the command works + Description string + // A short description of the arguments of this command + ArgsUsage string + // The category the command is part of + Category string + // The function to call when checking for bash command completions + BashComplete BashCompleteFunc + // An action to execute before any sub-subcommands are run, but after the context is ready + // If a non-nil error is returned, no sub-subcommands are run + Before BeforeFunc + // An action to execute after any subcommands are run, but after the subcommand has finished + // It is run even if Action() panics + After AfterFunc + // The function to call when this command is invoked + Action interface{} + // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind + // of deprecation period has passed, maybe? + + // Execute this function if a usage error occurs. + OnUsageError OnUsageErrorFunc + // List of child commands + Subcommands Commands + // List of flags to parse + Flags []Flag + // Treat all flags as normal arguments if true + SkipFlagParsing bool + // Skip argument reordering which attempts to move flags before arguments, + // but only works if all flags appear after all arguments. This behavior was + // removed n version 2 since it only works under specific conditions so we + // backport here by exposing it as an option for compatibility. + SkipArgReorder bool + // Boolean to hide built-in help command + HideHelp bool + // Boolean to hide this command from help or completion + Hidden bool + + // Full name of command for help, defaults to full command name, including parent commands. + HelpName string + commandNamePath []string + + // CustomHelpTemplate the text template for the command help topic. + // cli.go uses text/template to render templates. You can + // render custom help text by setting this variable. + CustomHelpTemplate string +} + +type CommandsByName []Command + +func (c CommandsByName) Len() int { + return len(c) +} + +func (c CommandsByName) Less(i, j int) bool { + return c[i].Name < c[j].Name +} + +func (c CommandsByName) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} + +// FullName returns the full name of the command. +// For subcommands this ensures that parent commands are part of the command path +func (c Command) FullName() string { + if c.commandNamePath == nil { + return c.Name + } + return strings.Join(c.commandNamePath, " ") +} + +// Commands is a slice of Command +type Commands []Command + +// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags +func (c Command) Run(ctx *Context) (err error) { + if len(c.Subcommands) > 0 { + return c.startApp(ctx) + } + + if !c.HideHelp && (HelpFlag != BoolFlag{}) { + // append help to flags + c.Flags = append( + c.Flags, + HelpFlag, + ) + } + + set, err := flagSet(c.Name, c.Flags) + if err != nil { + return err + } + set.SetOutput(ioutil.Discard) + + if c.SkipFlagParsing { + err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) + } else if !c.SkipArgReorder { + firstFlagIndex := -1 + terminatorIndex := -1 + for index, arg := range ctx.Args() { + if arg == "--" { + terminatorIndex = index + break + } else if arg == "-" { + // Do nothing. A dash alone is not really a flag. + continue + } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { + firstFlagIndex = index + } + } + + if firstFlagIndex > -1 { + args := ctx.Args() + regularArgs := make([]string, len(args[1:firstFlagIndex])) + copy(regularArgs, args[1:firstFlagIndex]) + + var flagArgs []string + if terminatorIndex > -1 { + flagArgs = args[firstFlagIndex:terminatorIndex] + regularArgs = append(regularArgs, args[terminatorIndex:]...) + } else { + flagArgs = args[firstFlagIndex:] + } + + err = set.Parse(append(flagArgs, regularArgs...)) + } else { + err = set.Parse(ctx.Args().Tail()) + } + } else { + err = set.Parse(ctx.Args().Tail()) + } + + nerr := normalizeFlags(c.Flags, set) + if nerr != nil { + fmt.Fprintln(ctx.App.Writer, nerr) + fmt.Fprintln(ctx.App.Writer) + ShowCommandHelp(ctx, c.Name) + return nerr + } + + context := NewContext(ctx.App, set, ctx) + context.Command = c + if checkCommandCompletions(context, c.Name) { + return nil + } + + if err != nil { + if c.OnUsageError != nil { + err := c.OnUsageError(context, err, false) + HandleExitCoder(err) + return err + } + fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) + fmt.Fprintln(context.App.Writer) + ShowCommandHelp(context, c.Name) + return err + } + + if checkCommandHelp(context, c.Name) { + return nil + } + + if c.After != nil { + defer func() { + afterErr := c.After(context) + if afterErr != nil { + HandleExitCoder(err) + if err != nil { + err = NewMultiError(err, afterErr) + } else { + err = afterErr + } + } + }() + } + + if c.Before != nil { + err = c.Before(context) + if err != nil { + ShowCommandHelp(context, c.Name) + HandleExitCoder(err) + return err + } + } + + if c.Action == nil { + c.Action = helpSubcommand.Action + } + + err = HandleAction(c.Action, context) + + if err != nil { + HandleExitCoder(err) + } + return err +} + +// Names returns the names including short names and aliases. +func (c Command) Names() []string { + names := []string{c.Name} + + if c.ShortName != "" { + names = append(names, c.ShortName) + } + + return append(names, c.Aliases...) +} + +// HasName returns true if Command.Name or Command.ShortName matches given name +func (c Command) HasName(name string) bool { + for _, n := range c.Names() { + if n == name { + return true + } + } + return false +} + +func (c Command) startApp(ctx *Context) error { + app := NewApp() + app.Metadata = ctx.App.Metadata + // set the name and usage + app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) + if c.HelpName == "" { + app.HelpName = c.HelpName + } else { + app.HelpName = app.Name + } + + app.Usage = c.Usage + app.Description = c.Description + app.ArgsUsage = c.ArgsUsage + + // set CommandNotFound + app.CommandNotFound = ctx.App.CommandNotFound + app.CustomAppHelpTemplate = c.CustomHelpTemplate + + // set the flags and commands + app.Commands = c.Subcommands + app.Flags = c.Flags + app.HideHelp = c.HideHelp + + app.Version = ctx.App.Version + app.HideVersion = ctx.App.HideVersion + app.Compiled = ctx.App.Compiled + app.Author = ctx.App.Author + app.Email = ctx.App.Email + app.Writer = ctx.App.Writer + app.ErrWriter = ctx.App.ErrWriter + + app.categories = CommandCategories{} + for _, command := range c.Subcommands { + app.categories = app.categories.AddCommand(command.Category, command) + } + + sort.Sort(app.categories) + + // bash completion + app.EnableBashCompletion = ctx.App.EnableBashCompletion + if c.BashComplete != nil { + app.BashComplete = c.BashComplete + } + + // set the actions + app.Before = c.Before + app.After = c.After + if c.Action != nil { + app.Action = c.Action + } else { + app.Action = helpSubcommand.Action + } + app.OnUsageError = c.OnUsageError + + for index, cc := range app.Commands { + app.Commands[index].commandNamePath = []string{c.Name, cc.Name} + } + + return app.RunAsSubcommand(ctx) +} + +// VisibleFlags returns a slice of the Flags with Hidden=false +func (c Command) VisibleFlags() []Flag { + return visibleFlags(c.Flags) +} diff --git a/vendor/github.com/urfave/cli/context.go b/vendor/github.com/urfave/cli/context.go new file mode 100644 index 0000000..db94191 --- /dev/null +++ b/vendor/github.com/urfave/cli/context.go @@ -0,0 +1,278 @@ +package cli + +import ( + "errors" + "flag" + "reflect" + "strings" + "syscall" +) + +// Context is a type that is passed through to +// each Handler action in a cli application. Context +// can be used to retrieve context-specific Args and +// parsed command-line options. +type Context struct { + App *App + Command Command + shellComplete bool + flagSet *flag.FlagSet + setFlags map[string]bool + parentContext *Context +} + +// NewContext creates a new context. For use in when invoking an App or Command action. +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { + c := &Context{App: app, flagSet: set, parentContext: parentCtx} + + if parentCtx != nil { + c.shellComplete = parentCtx.shellComplete + } + + return c +} + +// NumFlags returns the number of flags set +func (c *Context) NumFlags() int { + return c.flagSet.NFlag() +} + +// Set sets a context flag to a value. +func (c *Context) Set(name, value string) error { + c.setFlags = nil + return c.flagSet.Set(name, value) +} + +// GlobalSet sets a context flag to a value on the global flagset +func (c *Context) GlobalSet(name, value string) error { + globalContext(c).setFlags = nil + return globalContext(c).flagSet.Set(name, value) +} + +// IsSet determines if the flag was actually set +func (c *Context) IsSet(name string) bool { + if c.setFlags == nil { + c.setFlags = make(map[string]bool) + + c.flagSet.Visit(func(f *flag.Flag) { + c.setFlags[f.Name] = true + }) + + c.flagSet.VisitAll(func(f *flag.Flag) { + if _, ok := c.setFlags[f.Name]; ok { + return + } + c.setFlags[f.Name] = false + }) + + // XXX hack to support IsSet for flags with EnvVar + // + // There isn't an easy way to do this with the current implementation since + // whether a flag was set via an environment variable is very difficult to + // determine here. Instead, we intend to introduce a backwards incompatible + // change in version 2 to add `IsSet` to the Flag interface to push the + // responsibility closer to where the information required to determine + // whether a flag is set by non-standard means such as environment + // variables is avaliable. + // + // See https://github.com/urfave/cli/issues/294 for additional discussion + flags := c.Command.Flags + if c.Command.Name == "" { // cannot == Command{} since it contains slice types + if c.App != nil { + flags = c.App.Flags + } + } + for _, f := range flags { + eachName(f.GetName(), func(name string) { + if isSet, ok := c.setFlags[name]; isSet || !ok { + return + } + + val := reflect.ValueOf(f) + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + + envVarValue := val.FieldByName("EnvVar") + if !envVarValue.IsValid() { + return + } + + eachName(envVarValue.String(), func(envVar string) { + envVar = strings.TrimSpace(envVar) + if _, ok := syscall.Getenv(envVar); ok { + c.setFlags[name] = true + return + } + }) + }) + } + } + + return c.setFlags[name] +} + +// GlobalIsSet determines if the global flag was actually set +func (c *Context) GlobalIsSet(name string) bool { + ctx := c + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + + for ; ctx != nil; ctx = ctx.parentContext { + if ctx.IsSet(name) { + return true + } + } + return false +} + +// FlagNames returns a slice of flag names used in this context. +func (c *Context) FlagNames() (names []string) { + for _, flag := range c.Command.Flags { + name := strings.Split(flag.GetName(), ",")[0] + if name == "help" { + continue + } + names = append(names, name) + } + return +} + +// GlobalFlagNames returns a slice of global flag names used by the app. +func (c *Context) GlobalFlagNames() (names []string) { + for _, flag := range c.App.Flags { + name := strings.Split(flag.GetName(), ",")[0] + if name == "help" || name == "version" { + continue + } + names = append(names, name) + } + return +} + +// Parent returns the parent context, if any +func (c *Context) Parent() *Context { + return c.parentContext +} + +// value returns the value of the flag coressponding to `name` +func (c *Context) value(name string) interface{} { + return c.flagSet.Lookup(name).Value.(flag.Getter).Get() +} + +// Args contains apps console arguments +type Args []string + +// Args returns the command line arguments associated with the context. +func (c *Context) Args() Args { + args := Args(c.flagSet.Args()) + return args +} + +// NArg returns the number of the command line arguments. +func (c *Context) NArg() int { + return len(c.Args()) +} + +// Get returns the nth argument, or else a blank string +func (a Args) Get(n int) string { + if len(a) > n { + return a[n] + } + return "" +} + +// First returns the first argument, or else a blank string +func (a Args) First() string { + return a.Get(0) +} + +// Tail returns the rest of the arguments (not the first one) +// or else an empty string slice +func (a Args) Tail() []string { + if len(a) >= 2 { + return []string(a)[1:] + } + return []string{} +} + +// Present checks if there are any arguments present +func (a Args) Present() bool { + return len(a) != 0 +} + +// Swap swaps arguments at the given indexes +func (a Args) Swap(from, to int) error { + if from >= len(a) || to >= len(a) { + return errors.New("index out of range") + } + a[from], a[to] = a[to], a[from] + return nil +} + +func globalContext(ctx *Context) *Context { + if ctx == nil { + return nil + } + + for { + if ctx.parentContext == nil { + return ctx + } + ctx = ctx.parentContext + } +} + +func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { + if ctx.parentContext != nil { + ctx = ctx.parentContext + } + for ; ctx != nil; ctx = ctx.parentContext { + if f := ctx.flagSet.Lookup(name); f != nil { + return ctx.flagSet + } + } + return nil +} + +func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { + switch ff.Value.(type) { + case *StringSlice: + default: + set.Set(name, ff.Value.String()) + } +} + +func normalizeFlags(flags []Flag, set *flag.FlagSet) error { + visited := make(map[string]bool) + set.Visit(func(f *flag.Flag) { + visited[f.Name] = true + }) + for _, f := range flags { + parts := strings.Split(f.GetName(), ",") + if len(parts) == 1 { + continue + } + var ff *flag.Flag + for _, name := range parts { + name = strings.Trim(name, " ") + if visited[name] { + if ff != nil { + return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) + } + ff = set.Lookup(name) + } + } + if ff == nil { + continue + } + for _, name := range parts { + name = strings.Trim(name, " ") + if !visited[name] { + copyFlag(name, ff, set) + } + } + } + return nil +} diff --git a/vendor/github.com/urfave/cli/errors.go b/vendor/github.com/urfave/cli/errors.go new file mode 100644 index 0000000..562b295 --- /dev/null +++ b/vendor/github.com/urfave/cli/errors.go @@ -0,0 +1,115 @@ +package cli + +import ( + "fmt" + "io" + "os" + "strings" +) + +// OsExiter is the function used when the app exits. If not set defaults to os.Exit. +var OsExiter = os.Exit + +// ErrWriter is used to write errors to the user. This can be anything +// implementing the io.Writer interface and defaults to os.Stderr. +var ErrWriter io.Writer = os.Stderr + +// MultiError is an error that wraps multiple errors. +type MultiError struct { + Errors []error +} + +// NewMultiError creates a new MultiError. Pass in one or more errors. +func NewMultiError(err ...error) MultiError { + return MultiError{Errors: err} +} + +// Error implements the error interface. +func (m MultiError) Error() string { + errs := make([]string, len(m.Errors)) + for i, err := range m.Errors { + errs[i] = err.Error() + } + + return strings.Join(errs, "\n") +} + +type ErrorFormatter interface { + Format(s fmt.State, verb rune) +} + +// ExitCoder is the interface checked by `App` and `Command` for a custom exit +// code +type ExitCoder interface { + error + ExitCode() int +} + +// ExitError fulfills both the builtin `error` interface and `ExitCoder` +type ExitError struct { + exitCode int + message interface{} +} + +// NewExitError makes a new *ExitError +func NewExitError(message interface{}, exitCode int) *ExitError { + return &ExitError{ + exitCode: exitCode, + message: message, + } +} + +// Error returns the string message, fulfilling the interface required by +// `error` +func (ee *ExitError) Error() string { + return fmt.Sprintf("%v", ee.message) +} + +// ExitCode returns the exit code, fulfilling the interface required by +// `ExitCoder` +func (ee *ExitError) ExitCode() int { + return ee.exitCode +} + +// HandleExitCoder checks if the error fulfills the ExitCoder interface, and if +// so prints the error to stderr (if it is non-empty) and calls OsExiter with the +// given exit code. If the given error is a MultiError, then this func is +// called on all members of the Errors slice and calls OsExiter with the last exit code. +func HandleExitCoder(err error) { + if err == nil { + return + } + + if exitErr, ok := err.(ExitCoder); ok { + if err.Error() != "" { + if _, ok := exitErr.(ErrorFormatter); ok { + fmt.Fprintf(ErrWriter, "%+v\n", err) + } else { + fmt.Fprintln(ErrWriter, err) + } + } + OsExiter(exitErr.ExitCode()) + return + } + + if multiErr, ok := err.(MultiError); ok { + code := handleMultiError(multiErr) + OsExiter(code) + return + } +} + +func handleMultiError(multiErr MultiError) int { + code := 1 + for _, merr := range multiErr.Errors { + if multiErr2, ok := merr.(MultiError); ok { + code = handleMultiError(multiErr2) + } else { + fmt.Fprintln(ErrWriter, merr) + if exitErr, ok := merr.(ExitCoder); ok { + code = exitErr.ExitCode() + } + } + } + return code +} diff --git a/vendor/github.com/urfave/cli/flag-types.json b/vendor/github.com/urfave/cli/flag-types.json new file mode 100644 index 0000000..1223107 --- /dev/null +++ b/vendor/github.com/urfave/cli/flag-types.json @@ -0,0 +1,93 @@ +[ + { + "name": "Bool", + "type": "bool", + "value": false, + "context_default": "false", + "parser": "strconv.ParseBool(f.Value.String())" + }, + { + "name": "BoolT", + "type": "bool", + "value": false, + "doctail": " that is true by default", + "context_default": "false", + "parser": "strconv.ParseBool(f.Value.String())" + }, + { + "name": "Duration", + "type": "time.Duration", + "doctail": " (see https://golang.org/pkg/time/#ParseDuration)", + "context_default": "0", + "parser": "time.ParseDuration(f.Value.String())" + }, + { + "name": "Float64", + "type": "float64", + "context_default": "0", + "parser": "strconv.ParseFloat(f.Value.String(), 64)" + }, + { + "name": "Generic", + "type": "Generic", + "dest": false, + "context_default": "nil", + "context_type": "interface{}" + }, + { + "name": "Int64", + "type": "int64", + "context_default": "0", + "parser": "strconv.ParseInt(f.Value.String(), 0, 64)" + }, + { + "name": "Int", + "type": "int", + "context_default": "0", + "parser": "strconv.ParseInt(f.Value.String(), 0, 64)", + "parser_cast": "int(parsed)" + }, + { + "name": "IntSlice", + "type": "*IntSlice", + "dest": false, + "context_default": "nil", + "context_type": "[]int", + "parser": "(f.Value.(*IntSlice)).Value(), error(nil)" + }, + { + "name": "Int64Slice", + "type": "*Int64Slice", + "dest": false, + "context_default": "nil", + "context_type": "[]int64", + "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)" + }, + { + "name": "String", + "type": "string", + "context_default": "\"\"", + "parser": "f.Value.String(), error(nil)" + }, + { + "name": "StringSlice", + "type": "*StringSlice", + "dest": false, + "context_default": "nil", + "context_type": "[]string", + "parser": "(f.Value.(*StringSlice)).Value(), error(nil)" + }, + { + "name": "Uint64", + "type": "uint64", + "context_default": "0", + "parser": "strconv.ParseUint(f.Value.String(), 0, 64)" + }, + { + "name": "Uint", + "type": "uint", + "context_default": "0", + "parser": "strconv.ParseUint(f.Value.String(), 0, 64)", + "parser_cast": "uint(parsed)" + } +] diff --git a/vendor/github.com/urfave/cli/flag.go b/vendor/github.com/urfave/cli/flag.go new file mode 100644 index 0000000..877ff35 --- /dev/null +++ b/vendor/github.com/urfave/cli/flag.go @@ -0,0 +1,799 @@ +package cli + +import ( + "flag" + "fmt" + "reflect" + "runtime" + "strconv" + "strings" + "syscall" + "time" +) + +const defaultPlaceholder = "value" + +// BashCompletionFlag enables bash-completion for all commands and subcommands +var BashCompletionFlag Flag = BoolFlag{ + Name: "generate-bash-completion", + Hidden: true, +} + +// VersionFlag prints the version for the application +var VersionFlag Flag = BoolFlag{ + Name: "version, v", + Usage: "print the version", +} + +// HelpFlag prints the help for all commands and subcommands +// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand +// unless HideHelp is set to true) +var HelpFlag Flag = BoolFlag{ + Name: "help, h", + Usage: "show help", +} + +// FlagStringer converts a flag definition to a string. This is used by help +// to display a flag. +var FlagStringer FlagStringFunc = stringifyFlag + +// FlagsByName is a slice of Flag. +type FlagsByName []Flag + +func (f FlagsByName) Len() int { + return len(f) +} + +func (f FlagsByName) Less(i, j int) bool { + return f[i].GetName() < f[j].GetName() +} + +func (f FlagsByName) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +// Flag is a common interface related to parsing flags in cli. +// For more advanced flag parsing techniques, it is recommended that +// this interface be implemented. +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) + GetName() string +} + +// errorableFlag is an interface that allows us to return errors during apply +// it allows flags defined in this library to return errors in a fashion backwards compatible +// TODO remove in v2 and modify the existing Flag interface to return errors +type errorableFlag interface { + Flag + + ApplyWithError(*flag.FlagSet) error +} + +func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { + set := flag.NewFlagSet(name, flag.ContinueOnError) + + for _, f := range flags { + //TODO remove in v2 when errorableFlag is removed + if ef, ok := f.(errorableFlag); ok { + if err := ef.ApplyWithError(set); err != nil { + return nil, err + } + } else { + f.Apply(set) + } + } + return set, nil +} + +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) + } +} + +// Generic is a generic parseable type identified by a specific flag +type Generic interface { + Set(value string) error + String() string +} + +// Apply takes the flagset and calls Set on the generic flag with the value +// provided by the user for parsing by the flag +// Ignores parsing errors +func (f GenericFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError takes the flagset and calls Set on the generic flag with the value +// provided by the user for parsing by the flag +func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { + val := f.Value + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + if err := val.Set(envVal); err != nil { + return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) + } + break + } + } + } + + eachName(f.Name, func(name string) { + set.Var(f.Value, name, f.Usage) + }) + + return nil +} + +// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter +type StringSlice []string + +// Set appends the string value to the list of values +func (f *StringSlice) Set(value string) error { + *f = append(*f, value) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *StringSlice) String() string { + return fmt.Sprintf("%s", *f) +} + +// Value returns the slice of strings set by this flag +func (f *StringSlice) Value() []string { + return *f +} + +// Get returns the slice of strings set by this flag +func (f *StringSlice) Get() interface{} { + return *f +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f StringSliceFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + newVal := &StringSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) + } + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &StringSlice{} + } + set.Var(f.Value, name, f.Usage) + }) + + return nil +} + +// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter +type IntSlice []int + +// Set parses the value into an integer and appends it to the list of values +func (f *IntSlice) Set(value string) error { + tmp, err := strconv.Atoi(value) + if err != nil { + return err + } + *f = append(*f, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *IntSlice) String() string { + return fmt.Sprintf("%#v", *f) +} + +// Value returns the slice of ints set by this flag +func (f *IntSlice) Value() []int { + return *f +} + +// Get returns the slice of ints set by this flag +func (f *IntSlice) Get() interface{} { + return *f +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f IntSliceFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + newVal := &IntSlice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) + } + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &IntSlice{} + } + set.Var(f.Value, name, f.Usage) + }) + + return nil +} + +// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter +type Int64Slice []int64 + +// Set parses the value into an integer and appends it to the list of values +func (f *Int64Slice) Set(value string) error { + tmp, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return err + } + *f = append(*f, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Int64Slice) String() string { + return fmt.Sprintf("%#v", *f) +} + +// Value returns the slice of ints set by this flag +func (f *Int64Slice) Value() []int64 { + return *f +} + +// Get returns the slice of ints set by this flag +func (f *Int64Slice) Get() interface{} { + return *f +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f Int64SliceFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + newVal := &Int64Slice{} + for _, s := range strings.Split(envVal, ",") { + s = strings.TrimSpace(s) + if err := newVal.Set(s); err != nil { + return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) + } + } + f.Value = newVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Value == nil { + f.Value = &Int64Slice{} + } + set.Var(f.Value, name, f.Usage) + }) + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f BoolFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { + val := false + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + if envVal == "" { + val = false + break + } + + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) + } + + val = envValBool + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.BoolVar(f.Destination, name, val, f.Usage) + return + } + set.Bool(name, val, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f BoolTFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { + val := true + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + if envVal == "" { + val = false + break + } + + envValBool, err := strconv.ParseBool(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) + } + + val = envValBool + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.BoolVar(f.Destination, name, val, f.Usage) + return + } + set.Bool(name, val, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f StringFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + f.Value = envVal + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + return + } + set.String(name, f.Value, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f IntFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) + } + f.Value = int(envValInt) + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.IntVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Int(name, f.Value, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f Int64Flag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + envValInt, err := strconv.ParseInt(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) + } + + f.Value = envValInt + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Int64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Int64(name, f.Value, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f UintFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) + } + + f.Value = uint(envValInt) + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.UintVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint(name, f.Value, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f Uint64Flag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + envValInt, err := strconv.ParseUint(envVal, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) + } + + f.Value = uint64(envValInt) + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Uint64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Uint64(name, f.Value, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f DurationFlag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + envValDuration, err := time.ParseDuration(envVal) + if err != nil { + return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) + } + + f.Value = envValDuration + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.DurationVar(f.Destination, name, f.Value, f.Usage) + return + } + set.Duration(name, f.Value, f.Usage) + }) + + return nil +} + +// Apply populates the flag given the flag set and environment +// Ignores errors +func (f Float64Flag) Apply(set *flag.FlagSet) { + f.ApplyWithError(set) +} + +// ApplyWithError populates the flag given the flag set and environment +func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { + if f.EnvVar != "" { + for _, envVar := range strings.Split(f.EnvVar, ",") { + envVar = strings.TrimSpace(envVar) + if envVal, ok := syscall.Getenv(envVar); ok { + envValFloat, err := strconv.ParseFloat(envVal, 10) + if err != nil { + return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) + } + + f.Value = float64(envValFloat) + break + } + } + } + + eachName(f.Name, func(name string) { + if f.Destination != nil { + set.Float64Var(f.Destination, name, f.Value, f.Usage) + return + } + set.Float64(name, f.Value, f.Usage) + }) + + return nil +} + +func visibleFlags(fl []Flag) []Flag { + visible := []Flag{} + for _, flag := range fl { + field := flagValue(flag).FieldByName("Hidden") + if !field.IsValid() || !field.Bool() { + visible = append(visible, flag) + } + } + return visible +} + +func prefixFor(name string) (prefix string) { + if len(name) == 1 { + prefix = "-" + } else { + prefix = "--" + } + + return +} + +// Returns the placeholder, if any, and the unquoted usage string. +func unquoteUsage(usage string) (string, string) { + for i := 0; i < len(usage); i++ { + if usage[i] == '`' { + for j := i + 1; j < len(usage); j++ { + if usage[j] == '`' { + name := usage[i+1 : j] + usage = usage[:i] + name + usage[j+1:] + return name, usage + } + } + break + } + } + return "", usage +} + +func prefixedNames(fullName, placeholder string) string { + var prefixed string + parts := strings.Split(fullName, ",") + for i, name := range parts { + name = strings.Trim(name, " ") + prefixed += prefixFor(name) + name + if placeholder != "" { + prefixed += " " + placeholder + } + if i < len(parts)-1 { + prefixed += ", " + } + } + return prefixed +} + +func withEnvHint(envVar, str string) string { + envText := "" + if envVar != "" { + prefix := "$" + suffix := "" + sep := ", $" + if runtime.GOOS == "windows" { + prefix = "%" + suffix = "%" + sep = "%, %" + } + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) + } + return str + envText +} + +func flagValue(f Flag) reflect.Value { + fv := reflect.ValueOf(f) + for fv.Kind() == reflect.Ptr { + fv = reflect.Indirect(fv) + } + return fv +} + +func stringifyFlag(f Flag) string { + fv := flagValue(f) + + switch f.(type) { + case IntSliceFlag: + return withEnvHint(fv.FieldByName("EnvVar").String(), + stringifyIntSliceFlag(f.(IntSliceFlag))) + case Int64SliceFlag: + return withEnvHint(fv.FieldByName("EnvVar").String(), + stringifyInt64SliceFlag(f.(Int64SliceFlag))) + case StringSliceFlag: + return withEnvHint(fv.FieldByName("EnvVar").String(), + stringifyStringSliceFlag(f.(StringSliceFlag))) + } + + placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) + + needsPlaceholder := false + defaultValueString := "" + + if val := fv.FieldByName("Value"); val.IsValid() { + needsPlaceholder = true + defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) + + if val.Kind() == reflect.String && val.String() != "" { + defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) + } + } + + if defaultValueString == " (default: )" { + defaultValueString = "" + } + + if needsPlaceholder && placeholder == "" { + placeholder = defaultPlaceholder + } + + usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) + + return withEnvHint(fv.FieldByName("EnvVar").String(), + fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) +} + +func stringifyIntSliceFlag(f IntSliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + } + } + + return stringifySliceFlag(f.Usage, f.Name, defaultVals) +} + +func stringifyInt64SliceFlag(f Int64SliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) + } + } + + return stringifySliceFlag(f.Usage, f.Name, defaultVals) +} + +func stringifyStringSliceFlag(f StringSliceFlag) string { + defaultVals := []string{} + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, s := range f.Value.Value() { + if len(s) > 0 { + defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) + } + } + } + + return stringifySliceFlag(f.Usage, f.Name, defaultVals) +} + +func stringifySliceFlag(usage, name string, defaultVals []string) string { + placeholder, usage := unquoteUsage(usage) + if placeholder == "" { + placeholder = defaultPlaceholder + } + + defaultVal := "" + if len(defaultVals) > 0 { + defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) + } + + usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) + return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) +} diff --git a/vendor/github.com/urfave/cli/flag_generated.go b/vendor/github.com/urfave/cli/flag_generated.go new file mode 100644 index 0000000..491b619 --- /dev/null +++ b/vendor/github.com/urfave/cli/flag_generated.go @@ -0,0 +1,627 @@ +package cli + +import ( + "flag" + "strconv" + "time" +) + +// WARNING: This file is generated! + +// BoolFlag is a flag with type bool +type BoolFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Destination *bool +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f BoolFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f BoolFlag) GetName() string { + return f.Name +} + +// Bool looks up the value of a local BoolFlag, returns +// false if not found +func (c *Context) Bool(name string) bool { + return lookupBool(name, c.flagSet) +} + +// GlobalBool looks up the value of a global BoolFlag, returns +// false if not found +func (c *Context) GlobalBool(name string) bool { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + return false +} + +func lookupBool(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return false + } + return parsed + } + return false +} + +// BoolTFlag is a flag with type bool that is true by default +type BoolTFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Destination *bool +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f BoolTFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f BoolTFlag) GetName() string { + return f.Name +} + +// BoolT looks up the value of a local BoolTFlag, returns +// false if not found +func (c *Context) BoolT(name string) bool { + return lookupBoolT(name, c.flagSet) +} + +// GlobalBoolT looks up the value of a global BoolTFlag, returns +// false if not found +func (c *Context) GlobalBoolT(name string) bool { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupBoolT(name, fs) + } + return false +} + +func lookupBoolT(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return false + } + return parsed + } + return false +} + +// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration) +type DurationFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value time.Duration + Destination *time.Duration +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f DurationFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f DurationFlag) GetName() string { + return f.Name +} + +// Duration looks up the value of a local DurationFlag, returns +// 0 if not found +func (c *Context) Duration(name string) time.Duration { + return lookupDuration(name, c.flagSet) +} + +// GlobalDuration looks up the value of a global DurationFlag, returns +// 0 if not found +func (c *Context) GlobalDuration(name string) time.Duration { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + return 0 +} + +func lookupDuration(name string, set *flag.FlagSet) time.Duration { + f := set.Lookup(name) + if f != nil { + parsed, err := time.ParseDuration(f.Value.String()) + if err != nil { + return 0 + } + return parsed + } + return 0 +} + +// Float64Flag is a flag with type float64 +type Float64Flag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value float64 + Destination *float64 +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f Float64Flag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f Float64Flag) GetName() string { + return f.Name +} + +// Float64 looks up the value of a local Float64Flag, returns +// 0 if not found +func (c *Context) Float64(name string) float64 { + return lookupFloat64(name, c.flagSet) +} + +// GlobalFloat64 looks up the value of a global Float64Flag, returns +// 0 if not found +func (c *Context) GlobalFloat64(name string) float64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupFloat64(name, fs) + } + return 0 +} + +func lookupFloat64(name string, set *flag.FlagSet) float64 { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseFloat(f.Value.String(), 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} + +// GenericFlag is a flag with type Generic +type GenericFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value Generic +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f GenericFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f GenericFlag) GetName() string { + return f.Name +} + +// Generic looks up the value of a local GenericFlag, returns +// nil if not found +func (c *Context) Generic(name string) interface{} { + return lookupGeneric(name, c.flagSet) +} + +// GlobalGeneric looks up the value of a global GenericFlag, returns +// nil if not found +func (c *Context) GlobalGeneric(name string) interface{} { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupGeneric(name, fs) + } + return nil +} + +func lookupGeneric(name string, set *flag.FlagSet) interface{} { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value, error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} + +// Int64Flag is a flag with type int64 +type Int64Flag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value int64 + Destination *int64 +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f Int64Flag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f Int64Flag) GetName() string { + return f.Name +} + +// Int64 looks up the value of a local Int64Flag, returns +// 0 if not found +func (c *Context) Int64(name string) int64 { + return lookupInt64(name, c.flagSet) +} + +// GlobalInt64 looks up the value of a global Int64Flag, returns +// 0 if not found +func (c *Context) GlobalInt64(name string) int64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt64(name, fs) + } + return 0 +} + +func lookupInt64(name string, set *flag.FlagSet) int64 { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} + +// IntFlag is a flag with type int +type IntFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value int + Destination *int +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f IntFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f IntFlag) GetName() string { + return f.Name +} + +// Int looks up the value of a local IntFlag, returns +// 0 if not found +func (c *Context) Int(name string) int { + return lookupInt(name, c.flagSet) +} + +// GlobalInt looks up the value of a global IntFlag, returns +// 0 if not found +func (c *Context) GlobalInt(name string) int { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt(name, fs) + } + return 0 +} + +func lookupInt(name string, set *flag.FlagSet) int { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return int(parsed) + } + return 0 +} + +// IntSliceFlag is a flag with type *IntSlice +type IntSliceFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value *IntSlice +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f IntSliceFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f IntSliceFlag) GetName() string { + return f.Name +} + +// IntSlice looks up the value of a local IntSliceFlag, returns +// nil if not found +func (c *Context) IntSlice(name string) []int { + return lookupIntSlice(name, c.flagSet) +} + +// GlobalIntSlice looks up the value of a global IntSliceFlag, returns +// nil if not found +func (c *Context) GlobalIntSlice(name string) []int { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupIntSlice(name, fs) + } + return nil +} + +func lookupIntSlice(name string, set *flag.FlagSet) []int { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} + +// Int64SliceFlag is a flag with type *Int64Slice +type Int64SliceFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value *Int64Slice +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f Int64SliceFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f Int64SliceFlag) GetName() string { + return f.Name +} + +// Int64Slice looks up the value of a local Int64SliceFlag, returns +// nil if not found +func (c *Context) Int64Slice(name string) []int64 { + return lookupInt64Slice(name, c.flagSet) +} + +// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns +// nil if not found +func (c *Context) GlobalInt64Slice(name string) []int64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupInt64Slice(name, fs) + } + return nil +} + +func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} + +// StringFlag is a flag with type string +type StringFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value string + Destination *string +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f StringFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f StringFlag) GetName() string { + return f.Name +} + +// String looks up the value of a local StringFlag, returns +// "" if not found +func (c *Context) String(name string) string { + return lookupString(name, c.flagSet) +} + +// GlobalString looks up the value of a global StringFlag, returns +// "" if not found +func (c *Context) GlobalString(name string) string { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupString(name, fs) + } + return "" +} + +func lookupString(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value.String(), error(nil) + if err != nil { + return "" + } + return parsed + } + return "" +} + +// StringSliceFlag is a flag with type *StringSlice +type StringSliceFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value *StringSlice +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f StringSliceFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f StringSliceFlag) GetName() string { + return f.Name +} + +// StringSlice looks up the value of a local StringSliceFlag, returns +// nil if not found +func (c *Context) StringSlice(name string) []string { + return lookupStringSlice(name, c.flagSet) +} + +// GlobalStringSlice looks up the value of a global StringSliceFlag, returns +// nil if not found +func (c *Context) GlobalStringSlice(name string) []string { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupStringSlice(name, fs) + } + return nil +} + +func lookupStringSlice(name string, set *flag.FlagSet) []string { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} + +// Uint64Flag is a flag with type uint64 +type Uint64Flag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value uint64 + Destination *uint64 +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f Uint64Flag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f Uint64Flag) GetName() string { + return f.Name +} + +// Uint64 looks up the value of a local Uint64Flag, returns +// 0 if not found +func (c *Context) Uint64(name string) uint64 { + return lookupUint64(name, c.flagSet) +} + +// GlobalUint64 looks up the value of a global Uint64Flag, returns +// 0 if not found +func (c *Context) GlobalUint64(name string) uint64 { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupUint64(name, fs) + } + return 0 +} + +func lookupUint64(name string, set *flag.FlagSet) uint64 { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} + +// UintFlag is a flag with type uint +type UintFlag struct { + Name string + Usage string + EnvVar string + Hidden bool + Value uint + Destination *uint +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f UintFlag) String() string { + return FlagStringer(f) +} + +// GetName returns the name of the flag +func (f UintFlag) GetName() string { + return f.Name +} + +// Uint looks up the value of a local UintFlag, returns +// 0 if not found +func (c *Context) Uint(name string) uint { + return lookupUint(name, c.flagSet) +} + +// GlobalUint looks up the value of a global UintFlag, returns +// 0 if not found +func (c *Context) GlobalUint(name string) uint { + if fs := lookupGlobalFlagSet(name, c); fs != nil { + return lookupUint(name, fs) + } + return 0 +} + +func lookupUint(name string, set *flag.FlagSet) uint { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return uint(parsed) + } + return 0 +} diff --git a/vendor/github.com/urfave/cli/funcs.go b/vendor/github.com/urfave/cli/funcs.go new file mode 100644 index 0000000..cba5e6c --- /dev/null +++ b/vendor/github.com/urfave/cli/funcs.go @@ -0,0 +1,28 @@ +package cli + +// BashCompleteFunc is an action to execute when the bash-completion flag is set +type BashCompleteFunc func(*Context) + +// BeforeFunc is an action to execute before any subcommands are run, but after +// the context is ready if a non-nil error is returned, no subcommands are run +type BeforeFunc func(*Context) error + +// AfterFunc is an action to execute after any subcommands are run, but after the +// subcommand has finished it is run even if Action() panics +type AfterFunc func(*Context) error + +// ActionFunc is the action to execute when no subcommands are specified +type ActionFunc func(*Context) error + +// CommandNotFoundFunc is executed if the proper command cannot be found +type CommandNotFoundFunc func(*Context, string) + +// OnUsageErrorFunc is executed if an usage error occurs. This is useful for displaying +// customized usage error messages. This function is able to replace the +// original error messages. If this function is not set, the "Incorrect usage" +// is displayed and the execution is interrupted. +type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error + +// FlagStringFunc is used by the help generation to display a flag, which is +// expected to be a single line. +type FlagStringFunc func(Flag) string diff --git a/vendor/github.com/urfave/cli/generate-flag-types b/vendor/github.com/urfave/cli/generate-flag-types new file mode 100755 index 0000000..7147381 --- /dev/null +++ b/vendor/github.com/urfave/cli/generate-flag-types @@ -0,0 +1,255 @@ +#!/usr/bin/env python +""" +The flag types that ship with the cli library have many things in common, and +so we can take advantage of the `go generate` command to create much of the +source code from a list of definitions. These definitions attempt to cover +the parts that vary between flag types, and should evolve as needed. + +An example of the minimum definition needed is: + + { + "name": "SomeType", + "type": "sometype", + "context_default": "nil" + } + +In this example, the code generated for the `cli` package will include a type +named `SomeTypeFlag` that is expected to wrap a value of type `sometype`. +Fetching values by name via `*cli.Context` will default to a value of `nil`. + +A more complete, albeit somewhat redundant, example showing all available +definition keys is: + + { + "name": "VeryMuchType", + "type": "*VeryMuchType", + "value": true, + "dest": false, + "doctail": " which really only wraps a []float64, oh well!", + "context_type": "[]float64", + "context_default": "nil", + "parser": "parseVeryMuchType(f.Value.String())", + "parser_cast": "[]float64(parsed)" + } + +The meaning of each field is as follows: + + name (string) - The type "name", which will be suffixed with + `Flag` when generating the type definition + for `cli` and the wrapper type for `altsrc` + type (string) - The type that the generated `Flag` type for `cli` + is expected to "contain" as its `.Value` member + value (bool) - Should the generated `cli` type have a `Value` + member? + dest (bool) - Should the generated `cli` type support a + destination pointer? + doctail (string) - Additional docs for the `cli` flag type comment + context_type (string) - The literal type used in the `*cli.Context` + reader func signature + context_default (string) - The literal value used as the default by the + `*cli.Context` reader funcs when no value is + present + parser (string) - Literal code used to parse the flag `f`, + expected to have a return signature of + (value, error) + parser_cast (string) - Literal code used to cast the `parsed` value + returned from the `parser` code +""" + +from __future__ import print_function, unicode_literals + +import argparse +import json +import os +import subprocess +import sys +import tempfile +import textwrap + + +class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter, + argparse.RawDescriptionHelpFormatter): + pass + + +def main(sysargs=sys.argv[:]): + parser = argparse.ArgumentParser( + description='Generate flag type code!', + formatter_class=_FancyFormatter) + parser.add_argument( + 'package', + type=str, default='cli', choices=_WRITEFUNCS.keys(), + help='Package for which flag types will be generated' + ) + parser.add_argument( + '-i', '--in-json', + type=argparse.FileType('r'), + default=sys.stdin, + help='Input JSON file which defines each type to be generated' + ) + parser.add_argument( + '-o', '--out-go', + type=argparse.FileType('w'), + default=sys.stdout, + help='Output file/stream to which generated source will be written' + ) + parser.epilog = __doc__ + + args = parser.parse_args(sysargs[1:]) + _generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json) + return 0 + + +def _generate_flag_types(writefunc, output_go, input_json): + types = json.load(input_json) + + tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False) + writefunc(tmp, types) + tmp.close() + + new_content = subprocess.check_output( + ['goimports', tmp.name] + ).decode('utf-8') + + print(new_content, file=output_go, end='') + output_go.flush() + os.remove(tmp.name) + + +def _set_typedef_defaults(typedef): + typedef.setdefault('doctail', '') + typedef.setdefault('context_type', typedef['type']) + typedef.setdefault('dest', True) + typedef.setdefault('value', True) + typedef.setdefault('parser', 'f.Value, error(nil)') + typedef.setdefault('parser_cast', 'parsed') + + +def _write_cli_flag_types(outfile, types): + _fwrite(outfile, """\ + package cli + + // WARNING: This file is generated! + + """) + + for typedef in types: + _set_typedef_defaults(typedef) + + _fwrite(outfile, """\ + // {name}Flag is a flag with type {type}{doctail} + type {name}Flag struct {{ + Name string + Usage string + EnvVar string + Hidden bool + """.format(**typedef)) + + if typedef['value']: + _fwrite(outfile, """\ + Value {type} + """.format(**typedef)) + + if typedef['dest']: + _fwrite(outfile, """\ + Destination *{type} + """.format(**typedef)) + + _fwrite(outfile, "\n}\n\n") + + _fwrite(outfile, """\ + // String returns a readable representation of this value + // (for usage defaults) + func (f {name}Flag) String() string {{ + return FlagStringer(f) + }} + + // GetName returns the name of the flag + func (f {name}Flag) GetName() string {{ + return f.Name + }} + + // {name} looks up the value of a local {name}Flag, returns + // {context_default} if not found + func (c *Context) {name}(name string) {context_type} {{ + return lookup{name}(name, c.flagSet) + }} + + // Global{name} looks up the value of a global {name}Flag, returns + // {context_default} if not found + func (c *Context) Global{name}(name string) {context_type} {{ + if fs := lookupGlobalFlagSet(name, c); fs != nil {{ + return lookup{name}(name, fs) + }} + return {context_default} + }} + + func lookup{name}(name string, set *flag.FlagSet) {context_type} {{ + f := set.Lookup(name) + if f != nil {{ + parsed, err := {parser} + if err != nil {{ + return {context_default} + }} + return {parser_cast} + }} + return {context_default} + }} + """.format(**typedef)) + + +def _write_altsrc_flag_types(outfile, types): + _fwrite(outfile, """\ + package altsrc + + import ( + "gopkg.in/urfave/cli.v1" + ) + + // WARNING: This file is generated! + + """) + + for typedef in types: + _set_typedef_defaults(typedef) + + _fwrite(outfile, """\ + // {name}Flag is the flag type that wraps cli.{name}Flag to allow + // for other values to be specified + type {name}Flag struct {{ + cli.{name}Flag + set *flag.FlagSet + }} + + // New{name}Flag creates a new {name}Flag + func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{ + return &{name}Flag{{{name}Flag: fl, set: nil}} + }} + + // Apply saves the flagSet for later usage calls, then calls the + // wrapped {name}Flag.Apply + func (f *{name}Flag) Apply(set *flag.FlagSet) {{ + f.set = set + f.{name}Flag.Apply(set) + }} + + // ApplyWithError saves the flagSet for later usage calls, then calls the + // wrapped {name}Flag.ApplyWithError + func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{ + f.set = set + return f.{name}Flag.ApplyWithError(set) + }} + """.format(**typedef)) + + +def _fwrite(outfile, text): + print(textwrap.dedent(text), end='', file=outfile) + + +_WRITEFUNCS = { + 'cli': _write_cli_flag_types, + 'altsrc': _write_altsrc_flag_types +} + +if __name__ == '__main__': + sys.exit(main()) diff --git a/vendor/github.com/urfave/cli/help.go b/vendor/github.com/urfave/cli/help.go new file mode 100644 index 0000000..57ec98d --- /dev/null +++ b/vendor/github.com/urfave/cli/help.go @@ -0,0 +1,338 @@ +package cli + +import ( + "fmt" + "io" + "os" + "strings" + "text/tabwriter" + "text/template" +) + +// AppHelpTemplate is the text template for the Default help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var AppHelpTemplate = `NAME: + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if len .Authors}} + +AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +GLOBAL OPTIONS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{.Copyright}}{{end}} +` + +// CommandHelpTemplate is the text template for the command help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var CommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +// SubcommandHelpTemplate is the text template for the subcommand help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{end}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} +{{end}}{{if .VisibleFlags}} +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +var helpCommand = Command{ + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", + Action: func(c *Context) error { + args := c.Args() + if args.Present() { + return ShowCommandHelp(c, args.First()) + } + + ShowAppHelp(c) + return nil + }, +} + +var helpSubcommand = Command{ + Name: "help", + Aliases: []string{"h"}, + Usage: "Shows a list of commands or help for one command", + ArgsUsage: "[command]", + Action: func(c *Context) error { + args := c.Args() + if args.Present() { + return ShowCommandHelp(c, args.First()) + } + + return ShowSubcommandHelp(c) + }, +} + +// Prints help for the App or Command +type helpPrinter func(w io.Writer, templ string, data interface{}) + +// Prints help for the App or Command with custom template function. +type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{}) + +// HelpPrinter is a function that writes the help output. If not set a default +// is used. The function signature is: +// func(w io.Writer, templ string, data interface{}) +var HelpPrinter helpPrinter = printHelp + +// HelpPrinterCustom is same as HelpPrinter but +// takes a custom function for template function map. +var HelpPrinterCustom helpPrinterCustom = printHelpCustom + +// VersionPrinter prints the version for the App +var VersionPrinter = printVersion + +// ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code. +func ShowAppHelpAndExit(c *Context, exitCode int) { + ShowAppHelp(c) + os.Exit(exitCode) +} + +// ShowAppHelp is an action that displays the help. +func ShowAppHelp(c *Context) (err error) { + if c.App.CustomAppHelpTemplate == "" { + HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) + return + } + customAppData := func() map[string]interface{} { + if c.App.ExtraInfo == nil { + return nil + } + return map[string]interface{}{ + "ExtraInfo": c.App.ExtraInfo, + } + } + HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData()) + return nil +} + +// DefaultAppComplete prints the list of subcommands as the default app completion method +func DefaultAppComplete(c *Context) { + for _, command := range c.App.Commands { + if command.Hidden { + continue + } + for _, name := range command.Names() { + fmt.Fprintln(c.App.Writer, name) + } + } +} + +// ShowCommandHelpAndExit - exits with code after showing help +func ShowCommandHelpAndExit(c *Context, command string, code int) { + ShowCommandHelp(c, command) + os.Exit(code) +} + +// ShowCommandHelp prints help for the given command +func ShowCommandHelp(ctx *Context, command string) error { + // show the subcommand help for a command with subcommands + if command == "" { + HelpPrinter(ctx.App.Writer, SubcommandHelpTemplate, ctx.App) + return nil + } + + for _, c := range ctx.App.Commands { + if c.HasName(command) { + if c.CustomHelpTemplate != "" { + HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil) + } else { + HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + } + return nil + } + } + + if ctx.App.CommandNotFound == nil { + return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) + } + + ctx.App.CommandNotFound(ctx, command) + return nil +} + +// ShowSubcommandHelp prints help for the given subcommand +func ShowSubcommandHelp(c *Context) error { + return ShowCommandHelp(c, c.Command.Name) +} + +// ShowVersion prints the version number of the App +func ShowVersion(c *Context) { + VersionPrinter(c) +} + +func printVersion(c *Context) { + fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) +} + +// ShowCompletions prints the lists of commands within a given context +func ShowCompletions(c *Context) { + a := c.App + if a != nil && a.BashComplete != nil { + a.BashComplete(c) + } +} + +// ShowCommandCompletions prints the custom completions for a given command +func ShowCommandCompletions(ctx *Context, command string) { + c := ctx.App.Command(command) + if c != nil && c.BashComplete != nil { + c.BashComplete(ctx) + } +} + +func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { + funcMap := template.FuncMap{ + "join": strings.Join, + } + if customFunc != nil { + for key, value := range customFunc { + funcMap[key] = value + } + } + + w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) + t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + err := t.Execute(w, data) + if err != nil { + // If the writer is closed, t.Execute will fail, and there's nothing + // we can do to recover. + if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { + fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) + } + return + } + w.Flush() +} + +func printHelp(out io.Writer, templ string, data interface{}) { + printHelpCustom(out, templ, data, nil) +} + +func checkVersion(c *Context) bool { + found := false + if VersionFlag.GetName() != "" { + eachName(VersionFlag.GetName(), func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + found = true + } + }) + } + return found +} + +func checkHelp(c *Context) bool { + found := false + if HelpFlag.GetName() != "" { + eachName(HelpFlag.GetName(), func(name string) { + if c.GlobalBool(name) || c.Bool(name) { + found = true + } + }) + } + return found +} + +func checkCommandHelp(c *Context, name string) bool { + if c.Bool("h") || c.Bool("help") { + ShowCommandHelp(c, name) + return true + } + + return false +} + +func checkSubcommandHelp(c *Context) bool { + if c.Bool("h") || c.Bool("help") { + ShowSubcommandHelp(c) + return true + } + + return false +} + +func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { + if !a.EnableBashCompletion { + return false, arguments + } + + pos := len(arguments) - 1 + lastArg := arguments[pos] + + if lastArg != "--"+BashCompletionFlag.GetName() { + return false, arguments + } + + return true, arguments[:pos] +} + +func checkCompletions(c *Context) bool { + if !c.shellComplete { + return false + } + + if args := c.Args(); args.Present() { + name := args.First() + if cmd := c.App.Command(name); cmd != nil { + // let the command handle the completion + return false + } + } + + ShowCompletions(c) + return true +} + +func checkCommandCompletions(c *Context, name string) bool { + if !c.shellComplete { + return false + } + + ShowCommandCompletions(c, name) + return true +} diff --git a/vendor/github.com/urfave/cli/runtests b/vendor/github.com/urfave/cli/runtests new file mode 100755 index 0000000..ee22bde --- /dev/null +++ b/vendor/github.com/urfave/cli/runtests @@ -0,0 +1,122 @@ +#!/usr/bin/env python +from __future__ import print_function + +import argparse +import os +import sys +import tempfile + +from subprocess import check_call, check_output + + +PACKAGE_NAME = os.environ.get( + 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' +) + + +def main(sysargs=sys.argv[:]): + targets = { + 'vet': _vet, + 'test': _test, + 'gfmrun': _gfmrun, + 'toc': _toc, + 'gen': _gen, + } + + parser = argparse.ArgumentParser() + parser.add_argument( + 'target', nargs='?', choices=tuple(targets.keys()), default='test' + ) + args = parser.parse_args(sysargs[1:]) + + targets[args.target]() + return 0 + + +def _test(): + if check_output('go version'.split()).split()[2] < 'go1.2': + _run('go test -v .') + return + + coverprofiles = [] + for subpackage in ['', 'altsrc']: + coverprofile = 'cli.coverprofile' + if subpackage != '': + coverprofile = '{}.coverprofile'.format(subpackage) + + coverprofiles.append(coverprofile) + + _run('go test -v'.split() + [ + '-coverprofile={}'.format(coverprofile), + ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') + ]) + + combined_name = _combine_coverprofiles(coverprofiles) + _run('go tool cover -func={}'.format(combined_name)) + os.remove(combined_name) + + +def _gfmrun(): + go_version = check_output('go version'.split()).split()[2] + if go_version < 'go1.3': + print('runtests: skip on {}'.format(go_version), file=sys.stderr) + return + _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md']) + + +def _vet(): + _run('go vet ./...') + + +def _toc(): + _run('node_modules/.bin/markdown-toc -i README.md') + _run('git diff --exit-code') + + +def _gen(): + go_version = check_output('go version'.split()).split()[2] + if go_version < 'go1.5': + print('runtests: skip on {}'.format(go_version), file=sys.stderr) + return + + _run('go generate ./...') + _run('git diff --exit-code') + + +def _run(command): + if hasattr(command, 'split'): + command = command.split() + print('runtests: {}'.format(' '.join(command)), file=sys.stderr) + check_call(command) + + +def _gfmrun_count(): + with open('README.md') as infile: + lines = infile.read().splitlines() + return len(filter(_is_go_runnable, lines)) + + +def _is_go_runnable(line): + return line.startswith('package main') + + +def _combine_coverprofiles(coverprofiles): + combined = tempfile.NamedTemporaryFile( + suffix='.coverprofile', delete=False + ) + combined.write('mode: set\n') + + for coverprofile in coverprofiles: + with open(coverprofile, 'r') as infile: + for line in infile.readlines(): + if not line.startswith('mode: '): + combined.write(line) + + combined.flush() + name = combined.name + combined.close() + return name + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 0000000..906a4b9 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,37 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "checksumSHA1": "PZ4KJai7DnuJ2YNJ2v2l2BseB1g=", + "path": "github.com/aymerick/raymond", + "revision": "72acac2207479d21dd45898c2a4264246c818148", + "revisionTime": "2016-12-09T22:07:24Z" + }, + { + "checksumSHA1": "Rvn+RH9pwFno1w6W+mhWsj/PxlA=", + "path": "github.com/aymerick/raymond/ast", + "revision": "72acac2207479d21dd45898c2a4264246c818148", + "revisionTime": "2016-12-09T22:07:24Z" + }, + { + "checksumSHA1": "5SJwPK0MYtJt5YiE1BNc9Wl3+S0=", + "path": "github.com/aymerick/raymond/lexer", + "revision": "72acac2207479d21dd45898c2a4264246c818148", + "revisionTime": "2016-12-09T22:07:24Z" + }, + { + "checksumSHA1": "TCu/8QBP8TApLjSt13a7Qjnyxrs=", + "path": "github.com/aymerick/raymond/parser", + "revision": "72acac2207479d21dd45898c2a4264246c818148", + "revisionTime": "2016-12-09T22:07:24Z" + }, + { + "checksumSHA1": "9LeR7BH4PSu8LRDZ8bY7QY1HXJE=", + "path": "github.com/urfave/cli", + "revision": "4b90d79a682b4bf685762c7452db20f2a676ecb2", + "revisionTime": "2017-07-06T19:46:25Z" + } + ], + "rootPath": "github.com/drone-plugins/drone-webhook" +}