From 136aea7d5a6a4a49a8b53ffe26fb6e8346042e65 Mon Sep 17 00:00:00 2001 From: Thomas Boerger Date: Mon, 8 Jan 2018 09:54:13 +0100 Subject: [PATCH 1/3] Added vendored dependencies --- .../github.com/aymerick/raymond/.gitmodules | 3 + .../github.com/aymerick/raymond/.travis.yml | 6 + .../github.com/aymerick/raymond/BENCHMARKS.md | 46 + .../github.com/aymerick/raymond/CHANGELOG.md | 33 + vendor/github.com/aymerick/raymond/LICENSE | 22 + vendor/github.com/aymerick/raymond/README.md | 1382 +++++++++++++ vendor/github.com/aymerick/raymond/VERSION | 1 + .../github.com/aymerick/raymond/ast/node.go | 785 ++++++++ .../github.com/aymerick/raymond/ast/print.go | 279 +++ .../github.com/aymerick/raymond/base_test.go | 167 ++ .../aymerick/raymond/benchmark_test.go | 316 +++ .../github.com/aymerick/raymond/data_frame.go | 95 + vendor/github.com/aymerick/raymond/escape.go | 65 + .../aymerick/raymond/escape_test.go | 20 + vendor/github.com/aymerick/raymond/eval.go | 984 ++++++++++ .../github.com/aymerick/raymond/eval_test.go | 215 ++ .../aymerick/raymond/handlebars/base_test.go | 100 + .../aymerick/raymond/handlebars/basic_test.go | 650 ++++++ .../raymond/handlebars/blocks_test.go | 208 ++ .../raymond/handlebars/builtins_test.go | 341 ++++ .../aymerick/raymond/handlebars/data_test.go | 300 +++ .../aymerick/raymond/handlebars/doc.go | 2 + .../raymond/handlebars/helpers_test.go | 665 +++++++ .../raymond/handlebars/partials_test.go | 182 ++ .../raymond/handlebars/subexpressions_test.go | 209 ++ .../raymond/handlebars/whitespace_test.go | 259 +++ vendor/github.com/aymerick/raymond/helper.go | 371 ++++ .../aymerick/raymond/helper_test.go | 193 ++ .../aymerick/raymond/lexer/lexer.go | 639 ++++++ .../aymerick/raymond/lexer/lexer_test.go | 541 +++++ .../aymerick/raymond/lexer/token.go | 183 ++ .../aymerick/raymond/mustache_test.go | 234 +++ .../aymerick/raymond/parser/parser.go | 846 ++++++++ .../aymerick/raymond/parser/parser_test.go | 200 ++ .../aymerick/raymond/parser/whitespace.go | 360 ++++ vendor/github.com/aymerick/raymond/partial.go | 85 + vendor/github.com/aymerick/raymond/raymond.go | 28 + .../github.com/aymerick/raymond/raymond.png | Bin 0 -> 13661 bytes .../aymerick/raymond/raymond_test.go | 115 ++ vendor/github.com/aymerick/raymond/string.go | 84 + .../aymerick/raymond/string_test.go | 59 + .../github.com/aymerick/raymond/template.go | 248 +++ .../aymerick/raymond/template_test.go | 166 ++ vendor/github.com/aymerick/raymond/utils.go | 85 + .../github.com/aymerick/raymond/utils_test.go | 51 + .../github.com/matrix-org/gomatrix/.gitignore | 24 + .../matrix-org/gomatrix/.travis.yml | 9 + vendor/github.com/matrix-org/gomatrix/LICENSE | 201 ++ .../github.com/matrix-org/gomatrix/README.md | 6 + .../github.com/matrix-org/gomatrix/client.go | 703 +++++++ .../gomatrix/client_examples_test.go | 119 ++ .../matrix-org/gomatrix/client_test.go | 105 + .../github.com/matrix-org/gomatrix/events.go | 101 + .../github.com/matrix-org/gomatrix/filter.go | 43 + .../matrix-org/gomatrix/hooks/install.sh | 5 + .../matrix-org/gomatrix/hooks/pre-commit | 26 + .../matrix-org/gomatrix/requests.go | 78 + .../matrix-org/gomatrix/responses.go | 176 ++ vendor/github.com/matrix-org/gomatrix/room.go | 50 + .../github.com/matrix-org/gomatrix/store.go | 65 + vendor/github.com/matrix-org/gomatrix/sync.go | 164 ++ .../github.com/matrix-org/gomatrix/userids.go | 130 ++ .../gomatrix/userids_examples_test.go | 27 + .../matrix-org/gomatrix/userids_test.go | 86 + vendor/github.com/urfave/cli/.flake8 | 2 + vendor/github.com/urfave/cli/.gitignore | 2 + vendor/github.com/urfave/cli/.travis.yml | 27 + vendor/github.com/urfave/cli/CHANGELOG.md | 435 ++++ vendor/github.com/urfave/cli/LICENSE | 21 + vendor/github.com/urfave/cli/README.md | 1381 +++++++++++++ vendor/github.com/urfave/cli/altsrc/altsrc.go | 3 + vendor/github.com/urfave/cli/altsrc/flag.go | 261 +++ .../urfave/cli/altsrc/flag_generated.go | 347 ++++ .../github.com/urfave/cli/altsrc/flag_test.go | 336 ++++ .../urfave/cli/altsrc/helpers_test.go | 18 + .../urfave/cli/altsrc/input_source_context.go | 21 + .../urfave/cli/altsrc/map_input_source.go | 262 +++ .../urfave/cli/altsrc/toml_command_test.go | 310 +++ .../urfave/cli/altsrc/toml_file_loader.go | 113 ++ .../urfave/cli/altsrc/yaml_command_test.go | 313 +++ .../urfave/cli/altsrc/yaml_file_loader.go | 92 + vendor/github.com/urfave/cli/app.go | 497 +++++ vendor/github.com/urfave/cli/app_test.go | 1742 +++++++++++++++++ vendor/github.com/urfave/cli/appveyor.yml | 26 + .../urfave/cli/autocomplete/bash_autocomplete | 16 + .../urfave/cli/autocomplete/zsh_autocomplete | 5 + vendor/github.com/urfave/cli/category.go | 44 + vendor/github.com/urfave/cli/cli.go | 22 + vendor/github.com/urfave/cli/command.go | 304 +++ vendor/github.com/urfave/cli/command_test.go | 240 +++ vendor/github.com/urfave/cli/context.go | 278 +++ vendor/github.com/urfave/cli/context_test.go | 403 ++++ vendor/github.com/urfave/cli/errors.go | 115 ++ vendor/github.com/urfave/cli/errors_test.go | 122 ++ vendor/github.com/urfave/cli/flag-types.json | 93 + vendor/github.com/urfave/cli/flag.go | 799 ++++++++ .../github.com/urfave/cli/flag_generated.go | 627 ++++++ vendor/github.com/urfave/cli/flag_test.go | 1215 ++++++++++++ vendor/github.com/urfave/cli/funcs.go | 28 + .../github.com/urfave/cli/generate-flag-types | 255 +++ vendor/github.com/urfave/cli/help.go | 338 ++++ vendor/github.com/urfave/cli/help_test.go | 452 +++++ vendor/github.com/urfave/cli/helpers_test.go | 28 + .../urfave/cli/helpers_unix_test.go | 9 + .../urfave/cli/helpers_windows_test.go | 20 + vendor/github.com/urfave/cli/runtests | 122 ++ 106 files changed, 25685 insertions(+) create mode 100644 vendor/github.com/aymerick/raymond/.gitmodules create mode 100644 vendor/github.com/aymerick/raymond/.travis.yml create mode 100644 vendor/github.com/aymerick/raymond/BENCHMARKS.md create mode 100644 vendor/github.com/aymerick/raymond/CHANGELOG.md create mode 100644 vendor/github.com/aymerick/raymond/LICENSE create mode 100644 vendor/github.com/aymerick/raymond/README.md create mode 100644 vendor/github.com/aymerick/raymond/VERSION create mode 100644 vendor/github.com/aymerick/raymond/ast/node.go create mode 100644 vendor/github.com/aymerick/raymond/ast/print.go create mode 100644 vendor/github.com/aymerick/raymond/base_test.go create mode 100644 vendor/github.com/aymerick/raymond/benchmark_test.go create mode 100644 vendor/github.com/aymerick/raymond/data_frame.go create mode 100644 vendor/github.com/aymerick/raymond/escape.go create mode 100644 vendor/github.com/aymerick/raymond/escape_test.go create mode 100644 vendor/github.com/aymerick/raymond/eval.go create mode 100644 vendor/github.com/aymerick/raymond/eval_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/base_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/basic_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/blocks_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/builtins_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/data_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/doc.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/helpers_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/partials_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/subexpressions_test.go create mode 100644 vendor/github.com/aymerick/raymond/handlebars/whitespace_test.go create mode 100644 vendor/github.com/aymerick/raymond/helper.go create mode 100644 vendor/github.com/aymerick/raymond/helper_test.go create mode 100644 vendor/github.com/aymerick/raymond/lexer/lexer.go create mode 100644 vendor/github.com/aymerick/raymond/lexer/lexer_test.go create mode 100644 vendor/github.com/aymerick/raymond/lexer/token.go create mode 100644 vendor/github.com/aymerick/raymond/mustache_test.go create mode 100644 vendor/github.com/aymerick/raymond/parser/parser.go create mode 100644 vendor/github.com/aymerick/raymond/parser/parser_test.go create mode 100644 vendor/github.com/aymerick/raymond/parser/whitespace.go create mode 100644 vendor/github.com/aymerick/raymond/partial.go create mode 100644 vendor/github.com/aymerick/raymond/raymond.go create mode 100644 vendor/github.com/aymerick/raymond/raymond.png create mode 100644 vendor/github.com/aymerick/raymond/raymond_test.go create mode 100644 vendor/github.com/aymerick/raymond/string.go create mode 100644 vendor/github.com/aymerick/raymond/string_test.go create mode 100644 vendor/github.com/aymerick/raymond/template.go create mode 100644 vendor/github.com/aymerick/raymond/template_test.go create mode 100644 vendor/github.com/aymerick/raymond/utils.go create mode 100644 vendor/github.com/aymerick/raymond/utils_test.go create mode 100644 vendor/github.com/matrix-org/gomatrix/.gitignore create mode 100644 vendor/github.com/matrix-org/gomatrix/.travis.yml create mode 100644 vendor/github.com/matrix-org/gomatrix/LICENSE create mode 100644 vendor/github.com/matrix-org/gomatrix/README.md create mode 100644 vendor/github.com/matrix-org/gomatrix/client.go create mode 100644 vendor/github.com/matrix-org/gomatrix/client_examples_test.go create mode 100644 vendor/github.com/matrix-org/gomatrix/client_test.go create mode 100644 vendor/github.com/matrix-org/gomatrix/events.go create mode 100644 vendor/github.com/matrix-org/gomatrix/filter.go create mode 100755 vendor/github.com/matrix-org/gomatrix/hooks/install.sh create mode 100755 vendor/github.com/matrix-org/gomatrix/hooks/pre-commit create mode 100644 vendor/github.com/matrix-org/gomatrix/requests.go create mode 100644 vendor/github.com/matrix-org/gomatrix/responses.go create mode 100644 vendor/github.com/matrix-org/gomatrix/room.go create mode 100644 vendor/github.com/matrix-org/gomatrix/store.go create mode 100644 vendor/github.com/matrix-org/gomatrix/sync.go create mode 100644 vendor/github.com/matrix-org/gomatrix/userids.go create mode 100644 vendor/github.com/matrix-org/gomatrix/userids_examples_test.go create mode 100644 vendor/github.com/matrix-org/gomatrix/userids_test.go create mode 100644 vendor/github.com/urfave/cli/.flake8 create mode 100644 vendor/github.com/urfave/cli/.gitignore create mode 100644 vendor/github.com/urfave/cli/.travis.yml create mode 100644 vendor/github.com/urfave/cli/CHANGELOG.md create mode 100644 vendor/github.com/urfave/cli/LICENSE create mode 100644 vendor/github.com/urfave/cli/README.md create mode 100644 vendor/github.com/urfave/cli/altsrc/altsrc.go create mode 100644 vendor/github.com/urfave/cli/altsrc/flag.go create mode 100644 vendor/github.com/urfave/cli/altsrc/flag_generated.go create mode 100644 vendor/github.com/urfave/cli/altsrc/flag_test.go create mode 100644 vendor/github.com/urfave/cli/altsrc/helpers_test.go create mode 100644 vendor/github.com/urfave/cli/altsrc/input_source_context.go create mode 100644 vendor/github.com/urfave/cli/altsrc/map_input_source.go create mode 100644 vendor/github.com/urfave/cli/altsrc/toml_command_test.go create mode 100644 vendor/github.com/urfave/cli/altsrc/toml_file_loader.go create mode 100644 vendor/github.com/urfave/cli/altsrc/yaml_command_test.go create mode 100644 vendor/github.com/urfave/cli/altsrc/yaml_file_loader.go create mode 100644 vendor/github.com/urfave/cli/app.go create mode 100644 vendor/github.com/urfave/cli/app_test.go create mode 100644 vendor/github.com/urfave/cli/appveyor.yml create mode 100755 vendor/github.com/urfave/cli/autocomplete/bash_autocomplete create mode 100644 vendor/github.com/urfave/cli/autocomplete/zsh_autocomplete create mode 100644 vendor/github.com/urfave/cli/category.go create mode 100644 vendor/github.com/urfave/cli/cli.go create mode 100644 vendor/github.com/urfave/cli/command.go create mode 100644 vendor/github.com/urfave/cli/command_test.go create mode 100644 vendor/github.com/urfave/cli/context.go create mode 100644 vendor/github.com/urfave/cli/context_test.go create mode 100644 vendor/github.com/urfave/cli/errors.go create mode 100644 vendor/github.com/urfave/cli/errors_test.go create mode 100644 vendor/github.com/urfave/cli/flag-types.json create mode 100644 vendor/github.com/urfave/cli/flag.go create mode 100644 vendor/github.com/urfave/cli/flag_generated.go create mode 100644 vendor/github.com/urfave/cli/flag_test.go create mode 100644 vendor/github.com/urfave/cli/funcs.go create mode 100755 vendor/github.com/urfave/cli/generate-flag-types create mode 100644 vendor/github.com/urfave/cli/help.go create mode 100644 vendor/github.com/urfave/cli/help_test.go create mode 100644 vendor/github.com/urfave/cli/helpers_test.go create mode 100644 vendor/github.com/urfave/cli/helpers_unix_test.go create mode 100644 vendor/github.com/urfave/cli/helpers_windows_test.go create mode 100755 vendor/github.com/urfave/cli/runtests diff --git a/vendor/github.com/aymerick/raymond/.gitmodules b/vendor/github.com/aymerick/raymond/.gitmodules new file mode 100644 index 0000000..a757998 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mustache"] + path = mustache + url = git://github.com/mustache/spec.git diff --git a/vendor/github.com/aymerick/raymond/.travis.yml b/vendor/github.com/aymerick/raymond/.travis.yml new file mode 100644 index 0000000..43aaf87 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/.travis.yml @@ -0,0 +1,6 @@ +--- +language: go + +go: + - 1.3 + - tip 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..0721bcc --- /dev/null +++ b/vendor/github.com/aymerick/raymond/README.md @@ -0,0 +1,1382 @@ +# 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) + - [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 fill 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. + +```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}}

+
{{body}}
+ {{/each}} +
` + + 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!", + }, + }, + } + + 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 + +``` + +With this context: + +```go +map[string]interface{}{ + "people": []string{ + "Marcel", "Jean-Claude", "Yvette", + }, +} +``` + +Outputs: + +```html + +``` + +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. + + +### 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/base_test.go b/vendor/github.com/aymerick/raymond/base_test.go new file mode 100644 index 0000000..b769331 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/base_test.go @@ -0,0 +1,167 @@ +package raymond + +import ( + "fmt" + "regexp" + "testing" +) + +type Test struct { + name string + input string + data interface{} + privData map[string]interface{} + helpers map[string]interface{} + partials map[string]string + output interface{} +} + +func launchTests(t *testing.T, tests []Test) { + // NOTE: TestMustache() makes Parallel testing fail + // t.Parallel() + + for _, test := range tests { + var err error + var tpl *Template + + // parse template + tpl, err = Parse(test.input) + if err != nil { + t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err) + } else { + if len(test.helpers) > 0 { + // register helpers + tpl.RegisterHelpers(test.helpers) + } + + if len(test.partials) > 0 { + // register partials + tpl.RegisterPartials(test.partials) + } + + // setup private data frame + var privData *DataFrame + if test.privData != nil { + privData = NewDataFrame() + for k, v := range test.privData { + privData.Set(k, v) + } + } + + // render template + output, err := tpl.ExecWith(test.data, privData) + if err != nil { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\nerror:\n\t%s\nAST:\n\t%s", test.name, test.input, Str(test.data), err, tpl.PrintAST()) + } else { + // check output + var expectedArr []string + expectedArr, ok := test.output.([]string) + if ok { + match := false + for _, expectedStr := range expectedArr { + if expectedStr == output { + match = true + break + } + } + + if !match { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, Str(test.data), Str(test.partials), expectedArr, output, tpl.PrintAST()) + } + } else { + expectedStr, ok := test.output.(string) + if !ok { + panic(fmt.Errorf("Erroneous test output description: %q", test.output)) + } + + if expectedStr != output { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, Str(test.data), Str(test.partials), expectedStr, output, tpl.PrintAST()) + } + } + } + } + } +} + +func launchErrorTests(t *testing.T, tests []Test) { + t.Parallel() + + for _, test := range tests { + var err error + var tpl *Template + + // parse template + tpl, err = Parse(test.input) + if err != nil { + t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err) + } else { + if len(test.helpers) > 0 { + // register helpers + tpl.RegisterHelpers(test.helpers) + } + + if len(test.partials) > 0 { + // register partials + tpl.RegisterPartials(test.partials) + } + + // setup private data frame + var privData *DataFrame + if test.privData != nil { + privData := NewDataFrame() + for k, v := range test.privData { + privData.Set(k, v) + } + } + + // render template + output, err := tpl.ExecWith(test.data, privData) + if err == nil { + t.Errorf("Test '%s' failed - Error expected\ninput:\n\t'%s'\ngot\n\t%q\nAST:\n%q", test.name, test.input, output, tpl.PrintAST()) + } else { + var errMatch error + match := false + + // check output + var expectedArr []string + expectedArr, ok := test.output.([]string) + if ok { + if len(expectedArr) > 0 { + for _, expectedStr := range expectedArr { + match, errMatch = regexp.MatchString(regexp.QuoteMeta(expectedStr), fmt.Sprint(err)) + if errMatch != nil { + panic("Failed to match regexp") + } + + if match { + break + } + } + } else { + // nothing to test + match = true + } + } else { + expectedStr, ok := test.output.(string) + if !ok { + panic(fmt.Errorf("Erroneous test output description: %q", test.output)) + } + + if expectedStr != "" { + match, errMatch = regexp.MatchString(regexp.QuoteMeta(expectedStr), fmt.Sprint(err)) + if errMatch != nil { + panic("Failed to match regexp") + } + } else { + // nothing to test + match = true + } + } + + if !match { + t.Errorf("Test '%s' failed - Incorrect error returned\ninput:\n\t'%s'\ndata:\n\t%s\nexpected\n\t%q\ngot\n\t%q", test.name, test.input, Str(test.data), test.output, err) + } + } + } + } +} diff --git a/vendor/github.com/aymerick/raymond/benchmark_test.go b/vendor/github.com/aymerick/raymond/benchmark_test.go new file mode 100644 index 0000000..f9ea74c --- /dev/null +++ b/vendor/github.com/aymerick/raymond/benchmark_test.go @@ -0,0 +1,316 @@ +package raymond + +import "testing" + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/bench/ +// +// Note that handlebars.js does NOT benchmark template compilation, it only benchmarks evaluation. +// + +func BenchmarkArguments(b *testing.B) { + source := `{{foo person "person" 1 true foo=bar foo="person" foo=1 foo=true}}` + + ctx := map[string]bool{ + "bar": true, + } + + tpl := MustParse(source) + tpl.RegisterHelper("foo", func(a, b, c, d interface{}) string { return "" }) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkArrayEach(b *testing.B) { + source := `{{#each names}}{{name}}{{/each}}` + + ctx := map[string][]map[string]string{ + "names": { + {"name": "Moe"}, + {"name": "Larry"}, + {"name": "Curly"}, + {"name": "Shemp"}, + }, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkArrayMustache(b *testing.B) { + source := `{{#names}}{{name}}{{/names}}` + + ctx := map[string][]map[string]string{ + "names": { + {"name": "Moe"}, + {"name": "Larry"}, + {"name": "Curly"}, + {"name": "Shemp"}, + }, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkComplex(b *testing.B) { + source := `

{{header}}

+{{#if items}} +
    + {{#each items}} + {{#if current}} +
  • {{name}}
  • + {{^}} +
  • {{name}}
  • + {{/if}} + {{/each}} +
+{{^}} +

The list is empty.

+{{/if}} +` + + ctx := map[string]interface{}{ + "header": func() string { return "Colors" }, + "hasItems": true, + "items": []map[string]interface{}{ + {"name": "red", "current": true, "url": "#Red"}, + {"name": "green", "current": false, "url": "#Green"}, + {"name": "blue", "current": false, "url": "#Blue"}, + }, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkData(b *testing.B) { + source := `{{#each names}}{{@index}}{{name}}{{/each}}` + + ctx := map[string][]map[string]string{ + "names": { + {"name": "Moe"}, + {"name": "Larry"}, + {"name": "Curly"}, + {"name": "Shemp"}, + }, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkDepth1(b *testing.B) { + source := `{{#each names}}{{../foo}}{{/each}}` + + ctx := map[string]interface{}{ + "names": []map[string]string{ + {"name": "Moe"}, + {"name": "Larry"}, + {"name": "Curly"}, + {"name": "Shemp"}, + }, + "foo": "bar", + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkDepth2(b *testing.B) { + source := `{{#each names}}{{#each name}}{{../bat}}{{../../foo}}{{/each}}{{/each}}` + + ctx := map[string]interface{}{ + "names": []map[string]interface{}{ + {"bat": "foo", "name": []string{"Moe"}}, + {"bat": "foo", "name": []string{"Larry"}}, + {"bat": "foo", "name": []string{"Curly"}}, + {"bat": "foo", "name": []string{"Shemp"}}, + }, + "foo": "bar", + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkObjectMustache(b *testing.B) { + source := `{{#person}}{{name}}{{age}}{{/person}}` + + ctx := map[string]interface{}{ + "person": map[string]interface{}{ + "name": "Larry", + "age": 45, + }, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkObject(b *testing.B) { + source := `{{#with person}}{{name}}{{age}}{{/with}}` + + ctx := map[string]interface{}{ + "person": map[string]interface{}{ + "name": "Larry", + "age": 45, + }, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkPartialRecursion(b *testing.B) { + source := `{{name}}{{#each kids}}{{>recursion}}{{/each}}` + + ctx := map[string]interface{}{ + "name": 1, + "kids": []map[string]interface{}{ + { + "name": "1.1", + "kids": []map[string]interface{}{ + { + "name": "1.1.1", + "kids": []map[string]interface{}{}, + }, + }, + }, + }, + } + + tpl := MustParse(source) + + partial := MustParse(`{{name}}{{#each kids}}{{>recursion}}{{/each}}`) + tpl.RegisterPartialTemplate("recursion", partial) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkPartial(b *testing.B) { + source := `{{#each peeps}}{{>variables}}{{/each}}` + + ctx := map[string]interface{}{ + "peeps": []map[string]interface{}{ + {"name": "Moe", "count": 15}, + {"name": "Moe", "count": 5}, + {"name": "Curly", "count": 1}, + }, + } + + tpl := MustParse(source) + + partial := MustParse(`Hello {{name}}! You have {{count}} new messages.`) + tpl.RegisterPartialTemplate("variables", partial) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkPath(b *testing.B) { + source := `{{person.name.bar.baz}}{{person.age}}{{person.foo}}{{animal.age}}` + + ctx := map[string]interface{}{ + "person": map[string]interface{}{ + "name": map[string]interface{}{ + "bar": map[string]string{ + "baz": "Larry", + }, + }, + "age": 45, + }, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkString(b *testing.B) { + source := `Hello world` + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(nil) + } +} + +func BenchmarkSubExpression(b *testing.B) { + source := `{{echo (header)}}` + + ctx := map[string]interface{}{} + + tpl := MustParse(source) + tpl.RegisterHelpers(map[string]interface{}{ + "echo": func(v string) string { return "foo " + v }, + "header": func() string { return "Colors" }, + }) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} + +func BenchmarkVariables(b *testing.B) { + source := `Hello {{name}}! You have {{count}} new messages.` + + ctx := map[string]interface{}{ + "name": "Mick", + "count": 30, + } + + tpl := MustParse(source) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + tpl.MustExec(ctx) + } +} 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/escape_test.go b/vendor/github.com/aymerick/raymond/escape_test.go new file mode 100644 index 0000000..b77bb09 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/escape_test.go @@ -0,0 +1,20 @@ +package raymond + +import "fmt" + +func ExampleEscape() { + tpl := MustParse("{{link url text}}") + + tpl.RegisterHelper("link", func(url string, text string) SafeString { + return SafeString("" + Escape(text) + "") + }) + + ctx := map[string]string{ + "url": "http://www.aymerick.com/", + "text": "This is a cool website", + } + + result := tpl.MustExec(ctx) + fmt.Print(result) + // Output: This is a <em>cool</em> website +} diff --git a/vendor/github.com/aymerick/raymond/eval.go b/vendor/github.com/aymerick/raymond/eval.go new file mode 100644 index 0000000..d49b708 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/eval.go @@ -0,0 +1,984 @@ +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) + } + 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) +} + +// 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/eval_test.go b/vendor/github.com/aymerick/raymond/eval_test.go new file mode 100644 index 0000000..b7bd82e --- /dev/null +++ b/vendor/github.com/aymerick/raymond/eval_test.go @@ -0,0 +1,215 @@ +package raymond + +import "testing" + +var evalTests = []Test{ + { + "only content", + "this is content", + nil, nil, nil, nil, + "this is content", + }, + { + "checks path in parent contexts", + "{{#a}}{{one}}{{#b}}{{one}}{{two}}{{one}}{{/b}}{{/a}}", + map[string]interface{}{"a": map[string]int{"one": 1}, "b": map[string]int{"two": 2}}, + nil, nil, nil, + "1121", + }, + { + "block params", + "{{#foo as |bar|}}{{bar}}{{/foo}}{{bar}}", + map[string]string{"foo": "baz", "bar": "bat"}, + nil, nil, nil, + "bazbat", + }, + { + "block params on array", + "{{#foo as |bar i|}}{{i}}.{{bar}} {{/foo}}", + map[string][]string{"foo": {"baz", "bar", "bat"}}, + nil, nil, nil, + "0.baz 1.bar 2.bat ", + }, + { + "nested block params", + "{{#foos as |foo iFoo|}}{{#wats as |wat iWat|}}{{iFoo}}.{{iWat}}.{{foo}}-{{wat}} {{/wats}}{{/foos}}", + map[string][]string{"foos": {"baz", "bar"}, "wats": {"the", "phoque"}}, + nil, nil, nil, + "0.0.baz-the 0.1.baz-phoque 1.0.bar-the 1.1.bar-phoque ", + }, + { + "block params with path reference", + "{{#foo as |bar|}}{{bar.baz}}{{/foo}}", + map[string]map[string]string{"foo": {"baz": "bat"}}, + nil, nil, nil, + "bat", + }, + { + "falsy block evaluation", + "{{#foo}}bar{{/foo}} baz", + map[string]interface{}{"foo": false}, + nil, nil, nil, + " baz", + }, + { + "block helper returns a SafeString", + "{{title}} - {{#bold}}{{body}}{{/bold}}", + map[string]string{ + "title": "My new blog post", + "body": "I have so many things to say!", + }, + nil, + map[string]interface{}{"bold": func(options *Options) SafeString { + return SafeString(`
` + options.Fn() + "
") + }}, + nil, + `My new blog post -
I have so many things to say!
`, + }, + { + "chained blocks", + "{{#if a}}A{{else if b}}B{{else}}C{{/if}}", + map[string]interface{}{"b": false}, + nil, nil, nil, + "C", + }, + + // @todo Test with a "../../path" (depth 2 path) while context is only depth 1 +} + +func TestEval(t *testing.T) { + t.Parallel() + + launchTests(t, evalTests) +} + +var evalErrors = []Test{ + { + "functions with wrong number of arguments", + `{{foo "bar"}}`, + map[string]interface{}{"foo": func(a string, b string) string { return "foo" }}, + nil, nil, nil, + "Helper 'foo' called with wrong number of arguments, needed 2 but got 1", + }, + { + "functions with wrong number of returned values (1)", + "{{foo}}", + map[string]interface{}{"foo": func() {}}, + nil, nil, nil, + "Helper function must return a string or a SafeString", + }, + { + "functions with wrong number of returned values (2)", + "{{foo}}", + map[string]interface{}{"foo": func() (string, bool, string) { return "foo", true, "bar" }}, + nil, nil, nil, + "Helper function must return a string or a SafeString", + }, +} + +func TestEvalErrors(t *testing.T) { + launchErrorTests(t, evalErrors) +} + +func TestEvalStruct(t *testing.T) { + t.Parallel() + + source := `
+

By {{author.FirstName}} {{Author.lastName}}

+
{{Body}}
+ +

Comments

+ + {{#each comments}} +

By {{Author.FirstName}} {{author.LastName}}

+
{{body}}
+ {{/each}} +
` + + expected := `
+

By Jean Valjean

+
Life is difficult
+ +

Comments

+ +

By Marcel Beliveau

+
LOL!
+
` + + type Person struct { + FirstName string + LastName string + } + + type Comment struct { + Author Person + Body string + } + + type Post struct { + Author Person + Body string + Comments []Comment + } + + ctx := Post{ + Person{"Jean", "Valjean"}, + "Life is difficult", + []Comment{ + Comment{ + Person{"Marcel", "Beliveau"}, + "LOL!", + }, + }, + } + + output := MustRender(source, ctx) + if output != expected { + t.Errorf("Failed to evaluate with struct context") + } +} + +type TestFoo struct { +} + +func (t *TestFoo) Subject() string { + return "foo" +} + +func TestEvalMethod(t *testing.T) { + t.Parallel() + + source := `Subject is {{subject}}! YES I SAID {{Subject}}!` + expected := `Subject is foo! YES I SAID foo!` + + ctx := &TestFoo{} + + output := MustRender(source, ctx) + if output != expected { + t.Errorf("Failed to evaluate struct method: %s", output) + } +} + +type TestBar struct { +} + +func (t *TestBar) Subject() interface{} { + return testBar +} + +func testBar() string { + return "bar" +} + +func TestEvalMethodReturningFunc(t *testing.T) { + t.Parallel() + + source := `Subject is {{subject}}! YES I SAID {{Subject}}!` + expected := `Subject is bar! YES I SAID bar!` + + ctx := &TestBar{} + + output := MustRender(source, ctx) + if output != expected { + t.Errorf("Failed to evaluate struct method: %s", output) + } +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/base_test.go b/vendor/github.com/aymerick/raymond/handlebars/base_test.go new file mode 100644 index 0000000..254b1a9 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/base_test.go @@ -0,0 +1,100 @@ +package handlebars + +import ( + "fmt" + "io/ioutil" + "path" + "strconv" + "testing" + + "github.com/aymerick/raymond" +) + +// cf. https://github.com/aymerick/go-fuzz-tests/raymond +const dumpTpl = false + +var dumpTplNb = 0 + +type Test struct { + name string + input string + data interface{} + privData map[string]interface{} + helpers map[string]interface{} + partials map[string]string + output interface{} +} + +func launchTests(t *testing.T, tests []Test) { + t.Parallel() + + for _, test := range tests { + var err error + var tpl *raymond.Template + + if dumpTpl { + filename := strconv.Itoa(dumpTplNb) + if err := ioutil.WriteFile(path.Join(".", "dump_tpl", filename), []byte(test.input), 0644); err != nil { + panic(err) + } + dumpTplNb++ + } + + // parse template + tpl, err = raymond.Parse(test.input) + if err != nil { + t.Errorf("Test '%s' failed - Failed to parse template\ninput:\n\t'%s'\nerror:\n\t%s", test.name, test.input, err) + } else { + if len(test.helpers) > 0 { + // register helpers + tpl.RegisterHelpers(test.helpers) + } + + if len(test.partials) > 0 { + // register partials + tpl.RegisterPartials(test.partials) + } + + // setup private data frame + var privData *raymond.DataFrame + if test.privData != nil { + privData = raymond.NewDataFrame() + for k, v := range test.privData { + privData.Set(k, v) + } + } + + // render template + output, err := tpl.ExecWith(test.data, privData) + if err != nil { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\nerror:\n\t%s\nAST:\n\t%s", test.name, test.input, raymond.Str(test.data), err, tpl.PrintAST()) + } else { + // check output + var expectedArr []string + expectedArr, ok := test.output.([]string) + if ok { + match := false + for _, expectedStr := range expectedArr { + if expectedStr == output { + match = true + break + } + } + + if !match { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, raymond.Str(test.data), raymond.Str(test.partials), expectedArr, output, tpl.PrintAST()) + } + } else { + expectedStr, ok := test.output.(string) + if !ok { + panic(fmt.Errorf("Erroneous test output description: %q", test.output)) + } + + if expectedStr != output { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\ndata:\n\t%s\npartials:\n\t%s\nexpected\n\t%q\ngot\n\t%q\nAST:\n%s", test.name, test.input, raymond.Str(test.data), raymond.Str(test.partials), expectedStr, output, tpl.PrintAST()) + } + } + } + } + } +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/basic_test.go b/vendor/github.com/aymerick/raymond/handlebars/basic_test.go new file mode 100644 index 0000000..6325341 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/basic_test.go @@ -0,0 +1,650 @@ +package handlebars + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aymerick/raymond" +) + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/basic.js +// +var basicTests = []Test{ + { + "most basic", + "{{foo}}", + map[string]string{"foo": "foo"}, + nil, nil, nil, + "foo", + }, + { + "escaping (1)", + "\\{{foo}}", + map[string]string{"foo": "food"}, + nil, nil, nil, + "{{foo}}", + }, + { + "escaping (2)", + "content \\{{foo}}", + map[string]string{}, + nil, nil, nil, + "content {{foo}}", + }, + { + "escaping (3)", + "\\\\{{foo}}", + map[string]string{"foo": "food"}, + nil, nil, nil, + "\\food", + }, + { + "escaping (4)", + "content \\\\{{foo}}", + map[string]string{"foo": "food"}, + nil, nil, nil, + "content \\food", + }, + { + "escaping (5)", + "\\\\ {{foo}}", + map[string]string{"foo": "food"}, + nil, nil, nil, + "\\\\ food", + }, + { + "compiling with a basic context", + "Goodbye\n{{cruel}}\n{{world}}!", + map[string]string{"cruel": "cruel", "world": "world"}, + nil, nil, nil, + "Goodbye\ncruel\nworld!", + }, + { + "compiling with an undefined context (1)", + "Goodbye\n{{cruel}}\n{{world.bar}}!", + nil, nil, nil, nil, + "Goodbye\n\n!", + }, + { + "compiling with an undefined context (2)", + "{{#unless foo}}Goodbye{{../test}}{{test2}}{{/unless}}", + nil, nil, nil, nil, + "Goodbye", + }, + { + "comments (1)", + "{{! Goodbye}}Goodbye\n{{cruel}}\n{{world}}!", + map[string]string{"cruel": "cruel", "world": "world"}, + nil, nil, nil, + "Goodbye\ncruel\nworld!", + }, + { + "comments (2)", + " {{~! comment ~}} blah", + nil, nil, nil, nil, + "blah", + }, + { + "comments (3)", + " {{~!-- long-comment --~}} blah", + nil, nil, nil, nil, + "blah", + }, + { + "comments (4)", + " {{! comment ~}} blah", + nil, nil, nil, nil, + " blah", + }, + { + "comments (5)", + " {{!-- long-comment --~}} blah", + nil, nil, nil, nil, + " blah", + }, + { + "comments (6)", + " {{~! comment}} blah", + nil, nil, nil, nil, + " blah", + }, + { + "comments (7)", + " {{~!-- long-comment --}} blah", + nil, nil, nil, nil, + " blah", + }, + { + "boolean (1)", + "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!", + map[string]interface{}{"goodbye": true, "world": "world"}, + nil, nil, nil, + "GOODBYE cruel world!", + }, + { + "boolean (2)", + "{{#goodbye}}GOODBYE {{/goodbye}}cruel {{world}}!", + map[string]interface{}{"goodbye": false, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "zeros (1)", + "num1: {{num1}}, num2: {{num2}}", + map[string]interface{}{"num1": 42, "num2": 0}, + nil, nil, nil, + "num1: 42, num2: 0", + }, + { + "zeros (2)", + "num: {{.}}", + 0, + nil, nil, nil, + "num: 0", + }, + { + "zeros (3)", + "num: {{num1/num2}}", + map[string]map[string]interface{}{"num1": {"num2": 0}}, + nil, nil, nil, + "num: 0", + }, + { + "false (1)", + "val1: {{val1}}, val2: {{val2}}", + map[string]interface{}{"val1": false, "val2": false}, + nil, nil, nil, + "val1: false, val2: false", + }, + { + "false (2)", + "val: {{.}}", + false, + nil, nil, nil, + "val: false", + }, + { + "false (3)", + "val: {{val1/val2}}", + map[string]map[string]interface{}{"val1": {"val2": false}}, + nil, nil, nil, + "val: false", + }, + { + "false (4)", + "val1: {{{val1}}}, val2: {{{val2}}}", + map[string]interface{}{"val1": false, "val2": false}, + nil, nil, nil, + "val1: false, val2: false", + }, + { + "false (5)", + "val: {{{val1/val2}}}", + map[string]map[string]interface{}{"val1": {"val2": false}}, + nil, nil, nil, + "val: false", + }, + { + "newlines (1)", + "Alan's\nTest", + nil, nil, nil, nil, + "Alan's\nTest", + }, + { + "newlines (2)", + "Alan's\rTest", + nil, nil, nil, nil, + "Alan's\rTest", + }, + { + "escaping text (1)", + "Awesome's", + map[string]string{}, + nil, nil, nil, + "Awesome's", + }, + { + "escaping text (2)", + "Awesome\\", + map[string]string{}, + nil, nil, nil, + "Awesome\\", + }, + { + "escaping text (3)", + "Awesome\\\\ foo", + map[string]string{}, + nil, nil, nil, + "Awesome\\\\ foo", + }, + { + "escaping text (4)", + "Awesome {{foo}}", + map[string]string{"foo": "\\"}, + nil, nil, nil, + "Awesome \\", + }, + { + "escaping text (5)", + " ' ' ", + map[string]string{}, + nil, nil, nil, + " ' ' ", + }, + { + "escaping expressions (6)", + "{{{awesome}}}", + map[string]string{"awesome": "&'\\<>"}, + nil, nil, nil, + "&'\\<>", + }, + { + "escaping expressions (7)", + "{{&awesome}}", + map[string]string{"awesome": "&'\\<>"}, + nil, nil, nil, + "&'\\<>", + }, + { + "escaping expressions (8)", + "{{awesome}}", + map[string]string{"awesome": "&\"'`\\<>"}, + nil, nil, nil, + "&"'`\\<>", + }, + { + "escaping expressions (9)", + "{{awesome}}", + map[string]string{"awesome": "Escaped, looks like: <b>"}, + nil, nil, nil, + "Escaped, <b> looks like: &lt;b&gt;", + }, + { + "functions returning safestrings shouldn't be escaped", + "{{awesome}}", + map[string]interface{}{"awesome": func() raymond.SafeString { return raymond.SafeString("&'\\<>") }}, + nil, nil, nil, + "&'\\<>", + }, + { + "functions (1)", + "{{awesome}}", + map[string]interface{}{"awesome": func() string { return "Awesome" }}, + nil, nil, nil, + "Awesome", + }, + { + "functions (2)", + "{{awesome}}", + map[string]interface{}{"awesome": func(options *raymond.Options) string { + return options.ValueStr("more") + }, "more": "More awesome"}, + nil, nil, nil, + "More awesome", + }, + { + "functions with context argument", + "{{awesome frank}}", + map[string]interface{}{"awesome": func(context string) string { + return context + }, "frank": "Frank"}, + nil, nil, nil, + "Frank", + }, + { + "pathed functions with context argument", + "{{bar.awesome frank}}", + map[string]interface{}{"bar": map[string]interface{}{"awesome": func(context string) string { + return context + }}, "frank": "Frank"}, + nil, nil, nil, + "Frank", + }, + { + "depthed functions with context argument", + "{{#with frank}}{{../awesome .}}{{/with}}", + map[string]interface{}{"awesome": func(context string) string { + return context + }, "frank": "Frank"}, + nil, nil, nil, + "Frank", + }, + { + "block functions with context argument", + "{{#awesome 1}}inner {{.}}{{/awesome}}", + map[string]interface{}{"awesome": func(context interface{}, options *raymond.Options) string { + return options.FnWith(context) + }}, + nil, nil, nil, + "inner 1", + }, + { + "depthed block functions with context argument", + "{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}", + map[string]interface{}{ + "awesome": func(context interface{}, options *raymond.Options) string { + return options.FnWith(context) + }, + "value": true, + }, + nil, nil, nil, + "inner 1", + }, + { + "block functions without context argument", + "{{#awesome}}inner{{/awesome}}", + map[string]interface{}{ + "awesome": func(options *raymond.Options) string { + return options.Fn() + }, + }, + nil, nil, nil, + "inner", + }, + // // @note I don't even understand why this test passes with the JS implementation... it should be + // // the responsability of the function to evaluate the block + // { + // "pathed block functions without context argument", + // "{{#foo.awesome}}inner{{/foo.awesome}}", + // map[string]map[string]interface{}{ + // "foo": { + // "awesome": func(options *raymond.Options) interface{} { + // return options.Ctx() + // }, + // }, + // }, + // nil, nil, nil, + // "inner", + // }, + // // @note I don't even understand why this test passes with the JS implementation... it should be + // // the responsability of the function to evaluate the block + // { + // "depthed block functions without context argument", + // "{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}", + // map[string]interface{}{ + // "value": true, + // "awesome": func(options *raymond.Options) interface{} { + // return options.Ctx() + // }, + // }, + // nil, nil, nil, + // "inner", + // }, + { + "paths with hyphens (1)", + "{{foo-bar}}", + map[string]string{"foo-bar": "baz"}, + nil, nil, nil, + "baz", + }, + { + "paths with hyphens (2)", + "{{foo.foo-bar}}", + map[string]map[string]string{"foo": {"foo-bar": "baz"}}, + nil, nil, nil, + "baz", + }, + { + "paths with hyphens (3)", + "{{foo/foo-bar}}", + map[string]map[string]string{"foo": {"foo-bar": "baz"}}, + nil, nil, nil, + "baz", + }, + { + "nested paths", + "Goodbye {{alan/expression}} world!", + map[string]map[string]string{"alan": {"expression": "beautiful"}}, + nil, nil, nil, + "Goodbye beautiful world!", + }, + { + "nested paths with empty string value", + "Goodbye {{alan/expression}} world!", + map[string]map[string]string{"alan": {"expression": ""}}, + nil, nil, nil, + "Goodbye world!", + }, + { + "literal paths (1)", + "Goodbye {{[@alan]/expression}} world!", + map[string]map[string]string{"@alan": {"expression": "beautiful"}}, + nil, nil, nil, + "Goodbye beautiful world!", + }, + { + "literal paths (2)", + "Goodbye {{[foo bar]/expression}} world!", + map[string]map[string]string{"foo bar": {"expression": "beautiful"}}, + nil, nil, nil, + "Goodbye beautiful world!", + }, + { + "literal references", + "Goodbye {{[foo bar]}} world!", + map[string]string{"foo bar": "beautiful"}, + nil, nil, nil, + "Goodbye beautiful world!", + }, + // @note MMm ok, well... no... I don't see the purpose of that test + { + "that current context path ({{.}}) doesn't hit helpers", + "test: {{.}}", + nil, nil, + map[string]interface{}{"helper": func() string { + panic("fail") + }}, + nil, + "test: ", + }, + { + "complex but empty paths (1)", + "{{person/name}}", + map[string]map[string]interface{}{"person": {"name": nil}}, + nil, nil, nil, + "", + }, + { + "complex but empty paths (2)", + "{{person/name}}", + map[string]map[string]string{"person": {}}, + nil, nil, nil, + "", + }, + { + "this keyword in paths (1)", + "{{#goodbyes}}{{this}}{{/goodbyes}}", + map[string]interface{}{"goodbyes": []string{"goodbye", "Goodbye", "GOODBYE"}}, + nil, nil, nil, + "goodbyeGoodbyeGOODBYE", + }, + { + "this keyword in paths (2)", + "{{#hellos}}{{this/text}}{{/hellos}}", + map[string]interface{}{"hellos": []interface{}{ + map[string]string{"text": "hello"}, + map[string]string{"text": "Hello"}, + map[string]string{"text": "HELLO"}, + }}, + nil, nil, nil, + "helloHelloHELLO", + }, + { + "this keyword nested inside path' (1)", + "{{[this]}}", + map[string]string{"this": "bar"}, + nil, nil, nil, + "bar", + }, + { + "this keyword nested inside path' (2)", + "{{text/[this]}}", + map[string]map[string]string{"text": {"this": "bar"}}, + nil, nil, nil, + "bar", + }, + { + "this keyword in helpers (1)", + "{{#goodbyes}}{{foo this}}{{/goodbyes}}", + map[string]interface{}{"goodbyes": []string{"goodbye", "Goodbye", "GOODBYE"}}, + nil, + map[string]interface{}{"foo": barSuffixHelper}, + nil, + "bar goodbyebar Goodbyebar GOODBYE", + }, + { + "this keyword in helpers (2)", + "{{#hellos}}{{foo this/text}}{{/hellos}}", + map[string]interface{}{"hellos": []map[string]string{{"text": "hello"}, {"text": "Hello"}, {"text": "HELLO"}}}, + nil, + map[string]interface{}{"foo": barSuffixHelper}, + nil, + "bar hellobar Hellobar HELLO", + }, + { + "this keyword nested inside helpers param (1)", + "{{foo [this]}}", + map[string]interface{}{"this": "bar"}, + nil, + map[string]interface{}{"foo": echoHelper}, + nil, + "bar", + }, + { + "this keyword nested inside helpers param (2)", + "{{foo text/[this]}}", + map[string]map[string]string{"text": {"this": "bar"}}, + nil, + map[string]interface{}{"foo": echoHelper}, + nil, + "bar", + }, + { + "pass string literals (1)", + `{{"foo"}}`, + map[string]string{}, + nil, nil, nil, + "", + }, + { + "pass string literals (2)", + `{{"foo"}}`, + map[string]string{"foo": "bar"}, + nil, nil, nil, + "bar", + }, + { + "pass string literals (3)", + `{{#"foo"}}{{.}}{{/"foo"}}`, + map[string]interface{}{"foo": []string{"bar", "baz"}}, + nil, nil, nil, + "barbaz", + }, + { + "pass number literals (1)", + "{{12}}", + map[string]string{}, + nil, nil, nil, + "", + }, + { + "pass number literals (2)", + "{{12}}", + map[string]string{"12": "bar"}, + nil, nil, nil, + "bar", + }, + { + "pass number literals (3)", + "{{12.34}}", + map[string]string{}, + nil, nil, nil, + "", + }, + { + "pass number literals (4)", + "{{12.34}}", + map[string]string{"12.34": "bar"}, + nil, nil, nil, + "bar", + }, + { + "pass number literals (5)", + "{{12.34 1}}", + map[string]interface{}{"12.34": func(context string) string { + return "bar" + context + }}, + nil, nil, nil, + "bar1", + }, + { + "pass boolean literals (1)", + "{{true}}", + map[string]string{}, + nil, nil, nil, + "", + }, + { + "pass boolean literals (2)", + "{{true}}", + map[string]string{"": "foo"}, + nil, nil, nil, + "", + }, + { + "pass boolean literals (3)", + "{{false}}", + map[string]string{"false": "foo"}, + nil, nil, nil, + "foo", + }, + { + "should handle literals in subexpression", + "{{foo (false)}}", + map[string]interface{}{"false": func() string { return "bar" }}, + nil, + map[string]interface{}{"foo": func(context string) string { + return context + }}, + nil, + "bar", + }, +} + +func TestBasic(t *testing.T) { + launchTests(t, basicTests) +} + +func TestBasicErrors(t *testing.T) { + t.Parallel() + + var err error + + inputs := []string{ + // this keyword nested inside path + "{{#hellos}}{{text/this/foo}}{{/hellos}}", + // this keyword nested inside helpers param + "{{#hellos}}{{foo text/this/foo}}{{/hellos}}", + } + + expectedError := regexp.QuoteMeta("Invalid path: text/this") + + for _, input := range inputs { + _, err = raymond.Parse(input) + if err == nil { + t.Errorf("Test failed - Error expected") + } + + match, errMatch := regexp.MatchString(expectedError, fmt.Sprint(err)) + if errMatch != nil { + panic("Failed to match regexp") + } + + if !match { + t.Errorf("Test failed - Expected error:\n\t%s\n\nGot:\n\t%s", expectedError, err) + } + } +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/blocks_test.go b/vendor/github.com/aymerick/raymond/handlebars/blocks_test.go new file mode 100644 index 0000000..de435ba --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/blocks_test.go @@ -0,0 +1,208 @@ +package handlebars + +import "testing" + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/blocks.js +// +var blocksTests = []Test{ + { + "array (1) - Arrays iterate over the contents when not empty", + "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "goodbye! Goodbye! GOODBYE! cruel world!", + }, + { + "array (2) - Arrays ignore the contents when empty", + "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "array without data", + "{{#goodbyes}}{{text}}{{/goodbyes}} {{#goodbyes}}{{text}}{{/goodbyes}}", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "goodbyeGoodbyeGOODBYE goodbyeGoodbyeGOODBYE", + }, + { + "array with @index - The @index variable is used", + "{{#goodbyes}}{{@index}}. {{text}}! {{/goodbyes}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", + }, + { + "empty block (1) - Arrays iterate over the contents when not empty", + "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "empty block (1) - Arrays ignore the contents when empty", + "{{#goodbyes}}{{/goodbyes}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "block with complex lookup - Templates can access variables in contexts up the stack with relative path syntax", + "{{#goodbyes}}{{text}} cruel {{../name}}! {{/goodbyes}}", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "name": "Alan"}, + nil, nil, nil, + "goodbye cruel Alan! Goodbye cruel Alan! GOODBYE cruel Alan! ", + }, + { + "multiple blocks with complex lookup", + "{{#goodbyes}}{{../name}}{{../name}}{{/goodbyes}}", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "name": "Alan"}, + nil, nil, nil, + "AlanAlanAlanAlanAlanAlan", + }, + + // @todo "{{#goodbyes}}{{text}} cruel {{foo/../name}}! {{/goodbyes}}" should throw error + + { + "block with deep nested complex lookup", + "{{#outer}}Goodbye {{#inner}}cruel {{../sibling}} {{../../omg}}{{/inner}}{{/outer}}", + map[string]interface{}{"omg": "OMG!", "outer": []map[string]interface{}{{"sibling": "sad", "inner": []map[string]string{{"text": "goodbye"}}}}}, + nil, nil, nil, + "Goodbye cruel sad OMG!", + }, + { + "inverted sections with unset value - Inverted section rendered when value isn't set.", + "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}", + map[string]interface{}{}, + nil, nil, nil, + "Right On!", + }, + { + "inverted sections with false value - Inverted section rendered when value is false.", + "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}", + map[string]interface{}{"goodbyes": false}, + nil, nil, nil, + "Right On!", + }, + { + "inverted section with empty set - Inverted section rendered when value is empty set.", + "{{#goodbyes}}{{this}}{{/goodbyes}}{{^goodbyes}}Right On!{{/goodbyes}}", + map[string]interface{}{"goodbyes": []interface{}{}}, + nil, nil, nil, + "Right On!", + }, + { + "block inverted sections", + "{{#people}}{{name}}{{^}}{{none}}{{/people}}", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people", + }, + { + "chained inverted sections (1)", + "{{#people}}{{name}}{{else if none}}{{none}}{{/people}}", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people", + }, + { + "chained inverted sections (2)", + "{{#people}}{{name}}{{else if nothere}}fail{{else unless nothere}}{{none}}{{/people}}", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people", + }, + { + "chained inverted sections (3)", + "{{#people}}{{name}}{{else if none}}{{none}}{{else}}fail{{/people}}", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people", + }, + + // @todo "{{#people}}{{name}}{{else if none}}{{none}}{{/if}}" should throw error + + { + "block inverted sections with empty arrays", + "{{#people}}{{name}}{{^}}{{none}}{{/people}}", + map[string]interface{}{"none": "No people", "people": map[string]interface{}{}}, + nil, nil, nil, + "No people", + }, + { + "block standalone else sections (1)", + "{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people\n", + }, + { + "block standalone else sections (2)", + "{{#none}}\n{{.}}\n{{^}}\n{{none}}\n{{/none}}\n", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people\n", + }, + { + "block standalone else sections (3)", + "{{#people}}\n{{name}}\n{{^}}\n{{none}}\n{{/people}}\n", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people\n", + }, + { + "block standalone chained else sections (1)", + "{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{/people}}\n", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people\n", + }, + { + "block standalone chained else sections (2)", + "{{#people}}\n{{name}}\n{{else if none}}\n{{none}}\n{{^}}\n{{/people}}\n", + map[string]interface{}{"none": "No people"}, + nil, nil, nil, + "No people\n", + }, + { + "should handle nesting", + "{{#data}}\n{{#if true}}\n{{.}}\n{{/if}}\n{{/data}}\nOK.", + map[string]interface{}{"data": []int{1, 3, 5}}, + nil, nil, nil, + "1\n3\n5\nOK.", + }, + // // @todo compat mode + // { + // "block with deep recursive lookup lookup", + // "{{#outer}}Goodbye {{#inner}}cruel {{omg}}{{/inner}}{{/outer}}", + // map[string]interface{}{"omg": "OMG!", "outer": []map[string]interface{}{{"inner": []map[string]string{{"text": "goodbye"}}}}}, + // nil, + // nil, + // nil, + // "Goodbye cruel OMG!", + // }, + // // @todo compat mode + // { + // "block with deep recursive pathed lookup", + // "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}", + // map[string]interface{}{"omg": map[string]string{"yes": "OMG!"}, "outer": []map[string]interface{}{{"inner": []map[string]string{{"yes": "no", "text": "goodbye"}}}}}, + // nil, + // nil, + // nil, + // "Goodbye cruel OMG!", + // }, + { + "block with missed recursive lookup", + "{{#outer}}Goodbye {{#inner}}cruel {{omg.yes}}{{/inner}}{{/outer}}", + map[string]interface{}{"omg": map[string]string{"no": "OMG!"}, "outer": []map[string]interface{}{{"inner": []map[string]string{{"yes": "no", "text": "goodbye"}}}}}, + nil, nil, nil, + "Goodbye cruel ", + }, +} + +func TestBlocks(t *testing.T) { + launchTests(t, blocksTests) +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/builtins_test.go b/vendor/github.com/aymerick/raymond/handlebars/builtins_test.go new file mode 100644 index 0000000..1f986e4 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/builtins_test.go @@ -0,0 +1,341 @@ +package handlebars + +import "testing" + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/builtin.js +// +var builtinsTests = []Test{ + { + "#if - if with boolean argument shows the contents when true", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"goodbye": true, "world": "world"}, + nil, nil, nil, + "GOODBYE cruel world!", + }, + { + "#if - if with string argument shows the contents", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"goodbye": "dummy", "world": "world"}, + nil, nil, nil, + "GOODBYE cruel world!", + }, + { + "#if - if with boolean argument does not show the contents when false", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"goodbye": false, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "#if - if with undefined does not show the contents", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "#if - if with non-empty array shows the contents", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"goodbye": []string{"foo"}, "world": "world"}, + nil, nil, nil, + "GOODBYE cruel world!", + }, + { + "#if - if with empty array does not show the contents", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"goodbye": []string{}, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "#if - if with zero does not show the contents", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"goodbye": 0, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "#if - if with zero and includeZero option shows the contents", + "{{#if goodbye includeZero=true}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{"goodbye": 0, "world": "world"}, + nil, nil, nil, + "GOODBYE cruel world!", + }, + { + "#if - if with function shows the contents when function returns true", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{ + "goodbye": func() bool { return true }, + "world": "world", + }, + nil, nil, nil, + "GOODBYE cruel world!", + }, + { + "#if - if with function shows the contents when function returns string", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{ + "goodbye": func() string { return "world" }, + "world": "world", + }, + nil, nil, nil, + "GOODBYE cruel world!", + }, + { + "#if - if with function does not show the contents when returns false", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{ + "goodbye": func() bool { return false }, + "world": "world", + }, + nil, nil, nil, + "cruel world!", + }, + { + "#if - if with function does not show the contents when returns undefined", + "{{#if goodbye}}GOODBYE {{/if}}cruel {{world}}!", + map[string]interface{}{ + "goodbye": func() interface{} { return nil }, + "world": "world", + }, + nil, nil, nil, + "cruel world!", + }, + { + "#with", + "{{#with person}}{{first}} {{last}}{{/with}}", + map[string]interface{}{"person": map[string]string{"first": "Alan", "last": "Johnson"}}, + nil, nil, nil, + "Alan Johnson", + }, + { + "#with - with with function argument", + "{{#with person}}{{first}} {{last}}{{/with}}", + map[string]interface{}{ + "person": func() map[string]string { return map[string]string{"first": "Alan", "last": "Johnson"} }, + }, nil, nil, nil, + "Alan Johnson", + }, + { + "#with - with with else", + "{{#with person}}Person is present{{else}}Person is not present{{/with}}", + map[string]interface{}{}, + nil, nil, nil, + "Person is not present", + }, + + { + "#each - each with array argument iterates over the contents when not empty", + "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "goodbye! Goodbye! GOODBYE! cruel world!", + }, + { + "#each - each with array argument ignores the contents when empty", + "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "#each - each without data (1)", + "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "goodbye! Goodbye! GOODBYE! cruel world!", + }, + { + "#each - each without data (2)", + "{{#each .}}{{.}}{{/each}}", + map[string]interface{}{"goodbyes": "cruel", "world": "world"}, + nil, nil, nil, + // note: a go hash is not ordered, so result may vary, this behaviour differs from the JS implementation + []string{"cruelworld", "worldcruel"}, + }, + { + "#each - each without context", + "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!", + nil, nil, nil, nil, + "cruel !", + }, + + // NOTE: we test with a map instead of an object + { + "#each - each with an object and @key (map)", + "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": map[interface{}]map[string]string{"#1": {"text": "goodbye"}, 2: {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + []string{"<b>#1</b>. goodbye! 2. GOODBYE! cruel world!", "2. GOODBYE! <b>#1</b>. goodbye! cruel world!"}, + }, + // NOTE: An additional test with a struct, but without an html stuff for the key, because it is impossible + { + "#each - each with an object and @key (struct)", + "{{#each goodbyes}}{{@key}}. {{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{ + "goodbyes": struct { + Foo map[string]string + Bar map[string]int + }{map[string]string{"text": "baz"}, map[string]int{"text": 10}}, + "world": "world", + }, + nil, nil, nil, + []string{"Foo. baz! Bar. 10! cruel world!", "Bar. 10! Foo. baz! cruel world!"}, + }, + { + "#each - each with @index", + "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "0. goodbye! 1. Goodbye! 2. GOODBYE! cruel world!", + }, + { + "#each - each with nested @index", + "{{#each goodbyes}}{{@index}}. {{text}}! {{#each ../goodbyes}}{{@index}} {{/each}}After {{@index}} {{/each}}{{@index}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "0. goodbye! 0 1 2 After 0 1. Goodbye! 0 1 2 After 1 2. GOODBYE! 0 1 2 After 2 cruel world!", + }, + { + "#each - each with block params", + "{{#each goodbyes as |value index|}}{{index}}. {{value.text}}! {{#each ../goodbyes as |childValue childIndex|}} {{index}} {{childIndex}}{{/each}} After {{index}} {{/each}}{{index}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}}, "world": "world"}, + nil, nil, nil, + "0. goodbye! 0 0 0 1 After 0 1. Goodbye! 1 0 1 1 After 1 cruel world!", + }, + // @note: That test differs from JS impl because maps and structs are not ordered in go + { + "#each - each object with @index", + "{{#each goodbyes}}{{@index}}. {{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": map[string]map[string]string{"a": {"text": "goodbye"}, "b": {"text": "Goodbye"}}, "world": "world"}, + nil, nil, nil, + []string{"0. goodbye! 1. Goodbye! cruel world!", "0. Goodbye! 1. goodbye! cruel world!"}, + }, + { + "#each - each with nested @first", + "{{#each goodbyes}}({{#if @first}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @first}}{{text}}!{{/if}}{{/each}}{{#if @first}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "(goodbye! goodbye! goodbye!) (goodbye!) (goodbye!) cruel world!", + }, + // @note: That test differs from JS impl because maps and structs are not ordered in go + { + "#each - each object with @first", + "{{#each goodbyes}}{{#if @first}}{{text}}! {{/if}}{{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": map[string]map[string]string{"foo": {"text": "goodbye"}, "bar": {"text": "Goodbye"}}, "world": "world"}, + nil, nil, nil, + []string{"goodbye! cruel world!", "Goodbye! cruel world!"}, + }, + { + "#each - each with @last", + "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "GOODBYE! cruel world!", + }, + // @note: That test differs from JS impl because maps and structs are not ordered in go + { + "#each - each object with @last", + "{{#each goodbyes}}{{#if @last}}{{text}}! {{/if}}{{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": map[string]map[string]string{"foo": {"text": "goodbye"}, "bar": {"text": "Goodbye"}}, "world": "world"}, + nil, nil, nil, + []string{"goodbye! cruel world!", "Goodbye! cruel world!"}, + }, + { + "#each - each with nested @last", + "{{#each goodbyes}}({{#if @last}}{{text}}! {{/if}}{{#each ../goodbyes}}{{#if @last}}{{text}}!{{/if}}{{/each}}{{#if @last}} {{text}}!{{/if}}) {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}}, "world": "world"}, + nil, nil, nil, + "(GOODBYE!) (GOODBYE!) (GOODBYE! GOODBYE! GOODBYE!) cruel world!", + }, + + { + "#each - each with function argument (1)", + "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": func() []map[string]string { + return []map[string]string{{"text": "goodbye"}, {"text": "Goodbye"}, {"text": "GOODBYE"}} + }, "world": "world"}, + nil, nil, nil, + "goodbye! Goodbye! GOODBYE! cruel world!", + }, + { + "#each - each with function argument (2)", + "{{#each goodbyes}}{{text}}! {{/each}}cruel {{world}}!", + map[string]interface{}{"goodbyes": []map[string]string{}, "world": "world"}, + nil, nil, nil, + "cruel world!", + }, + { + "#each - data passed to helpers", + "{{#each letters}}{{this}}{{detectDataInsideEach}}{{/each}}", + map[string][]string{"letters": {"a", "b", "c"}}, + map[string]interface{}{"exclaim": "!"}, + map[string]interface{}{"detectDataInsideEach": detectDataHelper}, + nil, + "a!b!c!", + }, + + // @todo "each on implicit context" should throw error + + // SKIP: #log - "should call logger at default level" + // SKIP: #log - "should call logger at data level" + // SKIP: #log - "should output to info" + // SKIP: #log - "should log at data level" + // SKIP: #log - "should handle missing logger" + + // @note Test added + // @todo Check log output + { + "#log", + "{{log blah}}", + map[string]string{"blah": "whee"}, + nil, nil, nil, + "", + }, + + // @note Test added + { + "#lookup - should lookup array element", + "{{#each goodbyes}}{{lookup ../data @index}}{{/each}}", + map[string]interface{}{"goodbyes": []int{0, 1}, "data": []string{"foo", "bar"}}, + nil, nil, nil, + "foobar", + }, + { + "#lookup - should lookup map element", + "{{#each goodbyes}}{{lookup ../data .}}{{/each}}", + map[string]interface{}{"goodbyes": []string{"foo", "bar"}, "data": map[string]string{"foo": "baz", "bar": "bat"}}, + nil, nil, nil, + "bazbat", + }, + { + "#lookup - should lookup struct field", + "{{#each goodbyes}}{{lookup ../data .}}{{/each}}", + map[string]interface{}{"goodbyes": []string{"Foo", "Bar"}, "data": struct { + Foo string + Bar string + }{"baz", "bat"}}, + nil, nil, nil, + "bazbat", + }, + { + "#lookup - should lookup arbitrary content", + "{{#each goodbyes}}{{lookup ../data .}}{{/each}}", + map[string]interface{}{"goodbyes": []int{0, 1}, "data": []string{"foo", "bar"}}, + nil, nil, nil, + "foobar", + }, + { + "#lookup - should not fail on undefined value", + "{{#each goodbyes}}{{lookup ../bar .}}{{/each}}", + map[string]interface{}{"goodbyes": []int{0, 1}, "data": []string{"foo", "bar"}}, + nil, nil, nil, + "", + }, +} + +func TestBuiltins(t *testing.T) { + launchTests(t, builtinsTests) +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/data_test.go b/vendor/github.com/aymerick/raymond/handlebars/data_test.go new file mode 100644 index 0000000..fb80020 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/data_test.go @@ -0,0 +1,300 @@ +package handlebars + +import ( + "testing" + + "github.com/aymerick/raymond" +) + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/data.js +// +var dataTests = []Test{ + { + "passing in data to a compiled function that expects data - works with helpers", + "{{hello}}", + map[string]string{"noun": "cat"}, + map[string]interface{}{"adjective": "happy"}, + map[string]interface{}{"hello": func(options *raymond.Options) string { + return options.DataStr("adjective") + " " + options.ValueStr("noun") + }}, + nil, + "happy cat", + }, + { + "data can be looked up via @foo", + "{{@hello}}", + nil, + map[string]interface{}{"hello": "hello"}, + nil, nil, + "hello", + }, + { + "deep @foo triggers automatic top-level data", + `{{#let world="world"}}{{#if foo}}{{#if foo}}Hello {{@world}}{{/if}}{{/if}}{{/let}}`, + map[string]bool{"foo": true}, + map[string]interface{}{"hello": "hello"}, + map[string]interface{}{"let": func(options *raymond.Options) string { + frame := options.NewDataFrame() + + for k, v := range options.Hash() { + frame.Set(k, v) + } + + return options.FnData(frame) + }}, + nil, + "Hello world", + }, + { + "parameter data can be looked up via @foo", + `{{hello @world}}`, + nil, + map[string]interface{}{"world": "world"}, + map[string]interface{}{"hello": func(context string) string { + return "Hello " + context + }}, + nil, + "Hello world", + }, + { + "hash values can be looked up via @foo", + `{{hello noun=@world}}`, + nil, + map[string]interface{}{"world": "world"}, + map[string]interface{}{"hello": func(options *raymond.Options) string { + return "Hello " + options.HashStr("noun") + }}, + nil, + "Hello world", + }, + { + "nested parameter data can be looked up via @foo.bar", + `{{hello @world.bar}}`, + nil, + map[string]interface{}{"world": map[string]string{"bar": "world"}}, + map[string]interface{}{"hello": func(context string) string { + return "Hello " + context + }}, + nil, + "Hello world", + }, + { + "nested parameter data does not fail with @world.bar", + `{{hello @world.bar}}`, + nil, + map[string]interface{}{"foo": map[string]string{"bar": "world"}}, + map[string]interface{}{"hello": func(context string) string { + return "Hello " + context + }}, + nil, + // @todo Test differs with JS implementation: we don't output `undefined` + "Hello ", + }, + + // @todo "parameter data throws when using complex scope references", + + { + "data can be functions", + `{{@hello}}`, + nil, + map[string]interface{}{"hello": func() string { return "hello" }}, + nil, nil, + "hello", + }, + { + "data can be functions with params", + `{{@hello "hello"}}`, + nil, + map[string]interface{}{"hello": func(context string) string { return context }}, + nil, nil, + "hello", + }, + + { + "data is inherited downstream", + `{{#let foo=1 bar=2}}{{#let foo=bar.baz}}{{@bar}}{{@foo}}{{/let}}{{@foo}}{{/let}}`, + map[string]map[string]string{"bar": {"baz": "hello world"}}, + nil, + map[string]interface{}{"let": func(options *raymond.Options) string { + frame := options.NewDataFrame() + + for k, v := range options.Hash() { + frame.Set(k, v) + } + + return options.FnData(frame) + }}, + nil, + "2hello world1", + }, + { + "passing in data to a compiled function that expects data - works with helpers in partials", + `{{>myPartial}}`, + map[string]string{"noun": "cat"}, + map[string]interface{}{"adjective": "happy"}, + map[string]interface{}{"hello": func(options *raymond.Options) string { + return options.DataStr("adjective") + " " + options.ValueStr("noun") + }}, + map[string]string{ + "myPartial": "{{hello}}", + }, + "happy cat", + }, + { + "passing in data to a compiled function that expects data - works with helpers and parameters", + `{{hello world}}`, + map[string]interface{}{"exclaim": true, "world": "world"}, + map[string]interface{}{"adjective": "happy"}, + map[string]interface{}{"hello": func(context string, options *raymond.Options) string { + str := "error" + if b, ok := options.Value("exclaim").(bool); ok { + if b { + str = "!" + } else { + str = "" + } + } + + return options.DataStr("adjective") + " " + context + str + }}, + nil, + "happy world!", + }, + { + "passing in data to a compiled function that expects data - works with block helpers", + `{{#hello}}{{world}}{{/hello}}`, + map[string]bool{"exclaim": true}, + map[string]interface{}{"adjective": "happy"}, + map[string]interface{}{ + "hello": func(options *raymond.Options) string { + return options.Fn() + }, + "world": func(options *raymond.Options) string { + str := "error" + if b, ok := options.Value("exclaim").(bool); ok { + if b { + str = "!" + } else { + str = "" + } + } + + return options.DataStr("adjective") + " world" + str + }, + }, + nil, + "happy world!", + }, + { + "passing in data to a compiled function that expects data - works with block helpers that use ..", + `{{#hello}}{{world ../zomg}}{{/hello}}`, + map[string]interface{}{"exclaim": true, "zomg": "world"}, + map[string]interface{}{"adjective": "happy"}, + map[string]interface{}{ + "hello": func(options *raymond.Options) string { + return options.FnWith(map[string]string{"exclaim": "?"}) + }, + "world": func(context string, options *raymond.Options) string { + return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim") + }, + }, + nil, + "happy world?", + }, + { + "passing in data to a compiled function that expects data - data is passed to with block helpers where children use ..", + `{{#hello}}{{world ../zomg}}{{/hello}}`, + map[string]interface{}{"exclaim": true, "zomg": "world"}, + map[string]interface{}{"adjective": "happy", "accessData": "#win"}, + map[string]interface{}{ + "hello": func(options *raymond.Options) string { + return options.DataStr("accessData") + " " + options.FnWith(map[string]string{"exclaim": "?"}) + }, + "world": func(context string, options *raymond.Options) string { + return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim") + }, + }, + nil, + "#win happy world?", + }, + { + "you can override inherited data when invoking a helper", + `{{#hello}}{{world zomg}}{{/hello}}`, + map[string]interface{}{"exclaim": true, "zomg": "planet"}, + map[string]interface{}{"adjective": "happy"}, + map[string]interface{}{ + "hello": func(options *raymond.Options) string { + ctx := map[string]string{"exclaim": "?", "zomg": "world"} + data := options.NewDataFrame() + data.Set("adjective", "sad") + + return options.FnCtxData(ctx, data) + }, + "world": func(context string, options *raymond.Options) string { + return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim") + }, + }, + nil, + "sad world?", + }, + { + "you can override inherited data when invoking a helper with depth", + `{{#hello}}{{world ../zomg}}{{/hello}}`, + map[string]interface{}{"exclaim": true, "zomg": "world"}, + map[string]interface{}{"adjective": "happy"}, + map[string]interface{}{ + "hello": func(options *raymond.Options) string { + ctx := map[string]string{"exclaim": "?"} + data := options.NewDataFrame() + data.Set("adjective", "sad") + + return options.FnCtxData(ctx, data) + }, + "world": func(context string, options *raymond.Options) string { + return options.DataStr("adjective") + " " + context + options.ValueStr("exclaim") + }, + }, + nil, + "sad world?", + }, + { + "@root - the root context can be looked up via @root", + `{{@root.foo}}`, + map[string]interface{}{"foo": "hello"}, + nil, nil, nil, + "hello", + }, + { + "@root - passed root values take priority", + `{{@root.foo}}`, + nil, + map[string]interface{}{"root": map[string]string{"foo": "hello"}}, + nil, nil, + "hello", + }, + { + "nesting - the root context can be looked up via @root", + `{{#helper}}{{#helper}}{{@./depth}} {{@../depth}} {{@../../depth}}{{/helper}}{{/helper}}`, + map[string]interface{}{"foo": "hello"}, + map[string]interface{}{"depth": 0}, + map[string]interface{}{ + "helper": func(options *raymond.Options) string { + data := options.NewDataFrame() + + if depth, ok := options.Data("depth").(int); ok { + data.Set("depth", depth+1) + } + + return options.FnData(data) + }, + }, + nil, + "2 1 0", + }, +} + +func TestData(t *testing.T) { + launchTests(t, dataTests) +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/doc.go b/vendor/github.com/aymerick/raymond/handlebars/doc.go new file mode 100644 index 0000000..6a4c50c --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/doc.go @@ -0,0 +1,2 @@ +// Package handlebars contains all the tests that come from handlebars.js project. +package handlebars diff --git a/vendor/github.com/aymerick/raymond/handlebars/helpers_test.go b/vendor/github.com/aymerick/raymond/handlebars/helpers_test.go new file mode 100644 index 0000000..45c5411 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/helpers_test.go @@ -0,0 +1,665 @@ +package handlebars + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "github.com/aymerick/raymond" +) + +// +// Helpers +// + +func barSuffixHelper(context string) string { + return "bar " + context +} + +func echoHelper(str string) string { + return str +} + +func echoNbHelper(str string, nb int) string { + result := "" + for i := 0; i < nb; i++ { + result += str + } + + return result +} + +func linkHelper(prefix string, options *raymond.Options) string { + return fmt.Sprintf(`%s`, prefix, options.ValueStr("url"), options.ValueStr("text")) +} + +func rawHelper(options *raymond.Options) string { + return options.Fn() +} + +func rawThreeHelper(a, b, c string, options *raymond.Options) string { + return options.Fn() + a + b + c +} + +func formHelper(options *raymond.Options) string { + return "
" + options.Fn() + "
" +} + +func formCtxHelper(context interface{}, options *raymond.Options) string { + return "
" + options.FnWith(context) + "
" +} + +func listHelper(context interface{}, options *raymond.Options) string { + val := reflect.ValueOf(context) + switch val.Kind() { + case reflect.Array, reflect.Slice: + if val.Len() > 0 { + result := "
    " + for i := 0; i < val.Len(); i++ { + result += "
  • " + result += options.FnWith(val.Index(i).Interface()) + result += "
  • " + } + result += "
" + + return result + } + } + + return "

" + options.Inverse() + "

" +} + +func blogHelper(val string) string { + return "val is " + val +} + +func equalHelper(a, b string) string { + return raymond.Str(a == b) +} + +func dashHelper(a, b string) string { + return a + "-" + b +} + +func concatHelper(a, b string) string { + return a + b +} + +func detectDataHelper(options *raymond.Options) string { + if val, ok := options.DataFrame().Get("exclaim").(string); ok { + return val + } + + return "" +} + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/helper.js +// +var helpersTests = []Test{ + { + "helper with complex lookup", + "{{#goodbyes}}{{{link ../prefix}}}{{/goodbyes}}", + map[string]interface{}{"prefix": "/root", "goodbyes": []map[string]string{{"text": "Goodbye", "url": "goodbye"}}}, + nil, + map[string]interface{}{"link": linkHelper}, + nil, + `Goodbye`, + }, + { + "helper for raw block gets raw content", + "{{{{raw}}}} {{test}} {{{{/raw}}}}", + map[string]interface{}{"test": "hello"}, + nil, + map[string]interface{}{"raw": rawHelper}, + nil, + " {{test}} ", + }, + { + "helper for raw block gets parameters", + "{{{{raw 1 2 3}}}} {{test}} {{{{/raw}}}}", + map[string]interface{}{"test": "hello"}, + nil, + map[string]interface{}{"raw": rawThreeHelper}, + nil, + " {{test}} 123", + }, + { + "helper block with complex lookup expression", + "{{#goodbyes}}{{../name}}{{/goodbyes}}", + map[string]interface{}{"name": "Alan"}, + nil, + map[string]interface{}{"goodbyes": func(options *raymond.Options) string { + out := "" + for _, str := range []string{"Goodbye", "goodbye", "GOODBYE"} { + out += str + " " + options.FnWith(str) + "! " + } + return out + }}, + nil, + "Goodbye Alan! goodbye Alan! GOODBYE Alan! ", + }, + { + "helper with complex lookup and nested template", + "{{#goodbyes}}{{#link ../prefix}}{{text}}{{/link}}{{/goodbyes}}", + map[string]interface{}{"prefix": "/root", "goodbyes": []map[string]string{{"text": "Goodbye", "url": "goodbye"}}}, + nil, + map[string]interface{}{"link": linkHelper}, + nil, + `Goodbye`, + }, + { + // note: The JS implementation returns undefined, we return empty string + "helper returning undefined value (1)", + " {{nothere}}", + map[string]interface{}{}, + nil, + map[string]interface{}{"nothere": func() string { + return "" + }}, + nil, + " ", + }, + { + // note: The JS implementation returns undefined, we return empty string + "helper returning undefined value (2)", + " {{#nothere}}{{/nothere}}", + map[string]interface{}{}, + nil, + map[string]interface{}{"nothere": func() string { + return "" + }}, + nil, + " ", + }, + { + "block helper", + "{{#goodbyes}}{{text}}! {{/goodbyes}}cruel {{world}}!", + map[string]interface{}{"world": "world"}, + nil, + map[string]interface{}{"goodbyes": func(options *raymond.Options) string { + return options.FnWith(map[string]string{"text": "GOODBYE"}) + }}, + nil, + "GOODBYE! cruel world!", + }, + { + "block helper staying in the same context", + "{{#form}}

{{name}}

{{/form}}", + map[string]interface{}{"name": "Yehuda"}, + nil, + map[string]interface{}{"form": formHelper}, + nil, + "

Yehuda

", + }, + { + "block helper should have context in this", + "
    {{#people}}
  • {{#link}}{{name}}{{/link}}
  • {{/people}}
", + map[string]interface{}{"people": []map[string]interface{}{{"name": "Alan", "id": 1}, {"name": "Yehuda", "id": 2}}}, + nil, + map[string]interface{}{"link": func(options *raymond.Options) string { + return fmt.Sprintf("%s", options.ValueStr("id"), options.Fn()) + }}, + nil, + ``, + }, + { + "block helper for undefined value", + "{{#empty}}shouldn't render{{/empty}}", + nil, nil, nil, nil, + "", + }, + { + "block helper passing a new context", + "{{#form yehuda}}

{{name}}

{{/form}}", + map[string]map[string]string{"yehuda": {"name": "Yehuda"}}, + nil, + map[string]interface{}{"form": formCtxHelper}, + nil, + "

Yehuda

", + }, + { + "block helper passing a complex path context", + "{{#form yehuda/cat}}

{{name}}

{{/form}}", + map[string]map[string]interface{}{"yehuda": {"name": "Yehuda", "cat": map[string]string{"name": "Harold"}}}, + nil, + map[string]interface{}{"form": formCtxHelper}, + nil, + "

Harold

", + }, + { + "nested block helpers", + "{{#form yehuda}}

{{name}}

{{#link}}Hello{{/link}}{{/form}}", + map[string]map[string]string{"yehuda": {"name": "Yehuda"}}, + nil, + map[string]interface{}{"link": func(options *raymond.Options) string { + return fmt.Sprintf("%s", options.ValueStr("name"), options.Fn()) + }, "form": formCtxHelper}, + nil, + `

Yehuda

Hello
`, + }, + { + "block helper inverted sections (1) - an inverse wrapper is passed in as a new context", + "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}", + map[string][]map[string]string{"people": {{"name": "Alan"}, {"name": "Yehuda"}}}, + nil, + map[string]interface{}{"list": listHelper}, + nil, + `
  • Alan
  • Yehuda
`, + }, + { + "block helper inverted sections (2) - an inverse wrapper can be optionally called", + "{{#list people}}{{name}}{{^}}Nobody's here{{/list}}", + map[string][]map[string]string{"people": {}}, + nil, + map[string]interface{}{"list": listHelper}, + nil, + `

Nobody's here

`, + }, + { + "block helper inverted sections (3) - the context of an inverse is the parent of the block", + "{{#list people}}Hello{{^}}{{message}}{{/list}}", + map[string]interface{}{"people": []interface{}{}, "message": "Nobody's here"}, + nil, + map[string]interface{}{"list": listHelper}, + nil, + `

Nobody's here

`, + }, + + { + "pathed lambdas with parameters (1)", + "{{./helper 1}}", + map[string]interface{}{ + "helper": func(param int) string { return "winning" }, + "hash": map[string]interface{}{ + "helper": func(param int) string { return "winning" }, + }}, + nil, + map[string]interface{}{"./helper": func(param int) string { return "fail" }}, + nil, + "winning", + }, + { + "pathed lambdas with parameters (2)", + "{{hash/helper 1}}", + map[string]interface{}{ + "helper": func(param int) string { return "winning" }, + "hash": map[string]interface{}{ + "helper": func(param int) string { return "winning" }, + }}, + nil, + map[string]interface{}{"./helper": func(param int) string { return "fail" }}, + nil, + "winning", + }, + + { + "helpers hash - providing a helpers hash (1)", + "Goodbye {{cruel}} {{world}}!", + map[string]interface{}{"cruel": "cruel"}, + nil, + map[string]interface{}{"world": func() string { return "world" }}, + nil, + "Goodbye cruel world!", + }, + { + "helpers hash - providing a helpers hash (2)", + "Goodbye {{#iter}}{{cruel}} {{world}}{{/iter}}!", + map[string]interface{}{"iter": []map[string]string{{"cruel": "cruel"}}}, + nil, + map[string]interface{}{"world": func() string { return "world" }}, + nil, + "Goodbye cruel world!", + }, + { + "helpers hash - in cases of conflict, helpers win (1)", + "{{{lookup}}}", + map[string]interface{}{"lookup": "Explicit"}, + nil, + map[string]interface{}{"lookup": func() string { return "helpers" }}, + nil, + "helpers", + }, + { + "helpers hash - in cases of conflict, helpers win (2)", + "{{lookup}}", + map[string]interface{}{"lookup": "Explicit"}, + nil, + map[string]interface{}{"lookup": func() string { return "helpers" }}, + nil, + "helpers", + }, + { + "helpers hash - the helpers hash is available is nested contexts", + "{{#outer}}{{#inner}}{{helper}}{{/inner}}{{/outer}}", + map[string]interface{}{"outer": map[string]interface{}{"inner": map[string]interface{}{"unused": []string{}}}}, + nil, + map[string]interface{}{"helper": func() string { return "helper" }}, + nil, + "helper", + }, + + // @todo "helpers hash - the helper hash should augment the global hash" + + // @todo "registration" + + { + "decimal number literals work", + "Message: {{hello -1.2 1.2}}", + nil, nil, + map[string]interface{}{"hello": func(times, times2 interface{}) string { + ts, t2s := "NaN", "NaN" + + if v, ok := times.(float64); ok { + ts = raymond.Str(v) + } + + if v, ok := times2.(float64); ok { + t2s = raymond.Str(v) + } + + return "Hello " + ts + " " + t2s + " times" + }}, + nil, + "Message: Hello -1.2 1.2 times", + }, + { + "negative number literals work", + "Message: {{hello -12}}", + nil, nil, + map[string]interface{}{"hello": func(times interface{}) string { + ts := "NaN" + + if v, ok := times.(int); ok { + ts = raymond.Str(v) + } + + return "Hello " + ts + " times" + }}, + nil, + "Message: Hello -12 times", + }, + + { + "String literal parameters - simple literals work", + `Message: {{hello "world" 12 true false}}`, + nil, nil, + map[string]interface{}{"hello": func(p, t, b, b2 interface{}) string { + times, bool1, bool2 := "NaN", "NaB", "NaB" + + param, ok := p.(string) + if !ok { + param = "NaN" + } + + if v, ok := t.(int); ok { + times = raymond.Str(v) + } + + if v, ok := b.(bool); ok { + bool1 = raymond.Str(v) + } + + if v, ok := b2.(bool); ok { + bool2 = raymond.Str(v) + } + + return "Hello " + param + " " + times + " times: " + bool1 + " " + bool2 + }}, + nil, + "Message: Hello world 12 times: true false", + }, + + // @todo "using a quote in the middle of a parameter raises an error" + + { + "String literal parameters - escaping a String is possible", + "Message: {{{hello \"\\\"world\\\"\"}}}", + nil, nil, + map[string]interface{}{"hello": func(param string) string { + return "Hello " + param + }}, + nil, + `Message: Hello "world"`, + }, + { + "String literal parameters - it works with ' marks", + "Message: {{{hello \"Alan's world\"}}}", + nil, nil, + map[string]interface{}{"hello": func(param string) string { + return "Hello " + param + }}, + nil, + `Message: Hello Alan's world`, + }, + + { + "multiple parameters - simple multi-params work", + "Message: {{goodbye cruel world}}", + map[string]string{"cruel": "cruel", "world": "world"}, + nil, + map[string]interface{}{"goodbye": func(cruel, world string) string { + return "Goodbye " + cruel + " " + world + }}, + nil, + "Message: Goodbye cruel world", + }, + { + "multiple parameters - block multi-params work", + "Message: {{#goodbye cruel world}}{{greeting}} {{adj}} {{noun}}{{/goodbye}}", + map[string]string{"cruel": "cruel", "world": "world"}, + nil, + map[string]interface{}{"goodbye": func(cruel, world string, options *raymond.Options) string { + return options.FnWith(map[string]interface{}{"greeting": "Goodbye", "adj": cruel, "noun": world}) + }}, + nil, + "Message: Goodbye cruel world", + }, + + { + "hash - helpers can take an optional hash", + `{{goodbye cruel="CRUEL" world="WORLD" times=12}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + return "GOODBYE " + options.HashStr("cruel") + " " + options.HashStr("world") + " " + options.HashStr("times") + " TIMES" + }}, + nil, + "GOODBYE CRUEL WORLD 12 TIMES", + }, + { + "hash - helpers can take an optional hash with booleans (1)", + `{{goodbye cruel="CRUEL" world="WORLD" print=true}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + p, ok := options.HashProp("print").(bool) + if ok { + if p { + return "GOODBYE " + options.HashStr("cruel") + " " + options.HashStr("world") + } + return "NOT PRINTING" + } + + return "THIS SHOULD NOT HAPPEN" + }}, + nil, + "GOODBYE CRUEL WORLD", + }, + { + "hash - helpers can take an optional hash with booleans (2)", + `{{goodbye cruel="CRUEL" world="WORLD" print=false}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + p, ok := options.HashProp("print").(bool) + if ok { + if p { + return "GOODBYE " + options.HashStr("cruel") + " " + options.HashStr("world") + } + return "NOT PRINTING" + } + + return "THIS SHOULD NOT HAPPEN" + }}, + nil, + "NOT PRINTING", + }, + { + "block helpers can take an optional hash", + `{{#goodbye cruel="CRUEL" times=12}}world{{/goodbye}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn() + " " + options.HashStr("times") + " TIMES" + }}, + nil, + "GOODBYE CRUEL world 12 TIMES", + }, + { + "block helpers can take an optional hash with single quoted stings", + `{{#goodbye cruel='CRUEL' times=12}}world{{/goodbye}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn() + " " + options.HashStr("times") + " TIMES" + }}, + nil, + "GOODBYE CRUEL world 12 TIMES", + }, + { + "block helpers can take an optional hash with booleans (1)", + `{{#goodbye cruel="CRUEL" print=true}}world{{/goodbye}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + p, ok := options.HashProp("print").(bool) + if ok { + if p { + return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn() + } + return "NOT PRINTING" + } + + return "THIS SHOULD NOT HAPPEN" + }}, + nil, + "GOODBYE CRUEL world", + }, + { + "block helpers can take an optional hash with booleans (1)", + `{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + p, ok := options.HashProp("print").(bool) + if ok { + if p { + return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn() + } + return "NOT PRINTING" + } + + return "THIS SHOULD NOT HAPPEN" + }}, + nil, + "NOT PRINTING", + }, + + // @todo "helperMissing - if a context is not found, helperMissing is used" throw error + + // @todo "helperMissing - if a context is not found, custom helperMissing is used" + + // @todo "helperMissing - if a value is not found, custom helperMissing is used" + + { + "block helpers can take an optional hash with booleans (1)", + `{{#goodbye cruel="CRUEL" print=false}}world{{/goodbye}}`, + nil, nil, + map[string]interface{}{"goodbye": func(options *raymond.Options) string { + p, ok := options.HashProp("print").(bool) + if ok { + if p { + return "GOODBYE " + options.HashStr("cruel") + " " + options.Fn() + } + return "NOT PRINTING" + } + + return "THIS SHOULD NOT HAPPEN" + }}, + nil, + "NOT PRINTING", + }, + + // @todo "knownHelpers/knownHelpersOnly" tests + + // @todo "blockHelperMissing" tests + + // @todo "name field" tests + + { + "name conflicts - helpers take precedence over same-named context properties", + `{{goodbye}} {{cruel world}}`, + map[string]string{"goodbye": "goodbye", "world": "world"}, + nil, + map[string]interface{}{ + "goodbye": func(options *raymond.Options) string { + return strings.ToUpper(options.ValueStr("goodbye")) + }, + "cruel": func(world string) string { + return "cruel " + strings.ToUpper(world) + }, + }, + nil, + "GOODBYE cruel WORLD", + }, + { + "name conflicts - helpers take precedence over same-named context properties", + `{{#goodbye}} {{cruel world}}{{/goodbye}}`, + map[string]string{"goodbye": "goodbye", "world": "world"}, + nil, + map[string]interface{}{ + "goodbye": func(options *raymond.Options) string { + return strings.ToUpper(options.ValueStr("goodbye")) + options.Fn() + }, + "cruel": func(world string) string { + return "cruel " + strings.ToUpper(world) + }, + }, + nil, + "GOODBYE cruel WORLD", + }, + { + "name conflicts - Scoped names take precedence over helpers", + `{{this.goodbye}} {{cruel world}} {{cruel this.goodbye}}`, + map[string]string{"goodbye": "goodbye", "world": "world"}, + nil, + map[string]interface{}{ + "goodbye": func(options *raymond.Options) string { + return strings.ToUpper(options.ValueStr("goodbye")) + }, + "cruel": func(world string) string { + return "cruel " + strings.ToUpper(world) + }, + }, + nil, + "goodbye cruel WORLD cruel GOODBYE", + }, + { + "name conflicts - Scoped names take precedence over block helpers", + `{{#goodbye}} {{cruel world}}{{/goodbye}} {{this.goodbye}}`, + map[string]string{"goodbye": "goodbye", "world": "world"}, + nil, + map[string]interface{}{ + "goodbye": func(options *raymond.Options) string { + return strings.ToUpper(options.ValueStr("goodbye")) + options.Fn() + }, + "cruel": func(world string) string { + return "cruel " + strings.ToUpper(world) + }, + }, + nil, + "GOODBYE cruel WORLD goodbye", + }, + + // @todo "block params" tests +} + +func TestHelpers(t *testing.T) { + launchTests(t, helpersTests) +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/partials_test.go b/vendor/github.com/aymerick/raymond/handlebars/partials_test.go new file mode 100644 index 0000000..0c26662 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/partials_test.go @@ -0,0 +1,182 @@ +package handlebars + +import "testing" + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/partials.js +// +var partialsTests = []Test{ + { + "basic partials", + "Dudes: {{#dudes}}{{> dude}}{{/dudes}}", + map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, nil, + map[string]string{"dude": "{{name}} ({{url}}) "}, + "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + }, + { + "dynamic partials", + "Dudes: {{#dudes}}{{> (partial)}}{{/dudes}}", + map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, + map[string]interface{}{"partial": func() string { + return "dude" + }}, + map[string]string{"dude": "{{name}} ({{url}}) "}, + "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + }, + + // @todo "failing dynamic partials" + + { + "partials with context", + "Dudes: {{>dude dudes}}", + map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, nil, + map[string]string{"dude": "{{#this}}{{name}} ({{url}}) {{/this}}"}, + "Dudes: Yehuda (http://yehuda) Alan (http://alan) ", + }, + { + "partials with undefined context", + "Dudes: {{>dude dudes}}", + map[string]interface{}{}, + nil, nil, + map[string]string{"dude": "{{foo}} Empty"}, + "Dudes: Empty", + }, + + // @todo "partials with duplicate parameters" + + { + "partials with parameters", + "Dudes: {{#dudes}}{{> dude others=..}}{{/dudes}}", + map[string]interface{}{"foo": "bar", "dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, nil, + map[string]string{"dude": "{{others.foo}}{{name}} ({{url}}) "}, + "Dudes: barYehuda (http://yehuda) barAlan (http://alan) ", + }, + { + "partial in a partial", + "Dudes: {{#dudes}}{{>dude}}{{/dudes}}", + map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, nil, + map[string]string{"dude": "{{name}} {{> url}} ", "url": `{{url}}`}, + `Dudes: Yehuda http://yehuda Alan http://alan `, + }, + + // @todo "rendering undefined partial throws an exception" + + // @todo "registering undefined partial throws an exception" + + // SKIP: "rendering template partial in vm mode throws an exception" + // SKIP: "rendering function partial in vm mode" + + { + "GH-14: a partial preceding a selector", + "Dudes: {{>dude}} {{anotherDude}}", + map[string]string{"name": "Jeepers", "anotherDude": "Creepers"}, + nil, nil, + map[string]string{"dude": "{{name}}"}, + "Dudes: Jeepers Creepers", + }, + { + "Partials with slash paths", + "Dudes: {{> shared/dude}}", + map[string]string{"name": "Jeepers", "anotherDude": "Creepers"}, + nil, nil, + map[string]string{"shared/dude": "{{name}}"}, + "Dudes: Jeepers", + }, + { + "Partials with slash and point paths", + "Dudes: {{> shared/dude.thing}}", + map[string]string{"name": "Jeepers", "anotherDude": "Creepers"}, + nil, nil, + map[string]string{"shared/dude.thing": "{{name}}"}, + "Dudes: Jeepers", + }, + + // @todo "Global Partials" + + // @todo "Multiple partial registration" + + { + "Partials with integer path", + "Dudes: {{> 404}}", + map[string]string{"name": "Jeepers", "anotherDude": "Creepers"}, + nil, nil, + map[string]string{"404": "{{name}}"}, // @note Difference with JS test: partial name is a string + "Dudes: Jeepers", + }, + // @note This is not supported by our implementation. But really... who cares ? + // { + // "Partials with complex path", + // "Dudes: {{> 404/asdf?.bar}}", + // map[string]string{"name": "Jeepers", "anotherDude": "Creepers"}, + // nil, nil, + // map[string]string{"404/asdf?.bar": "{{name}}"}, + // "Dudes: Jeepers", + // }, + { + "Partials with escaped", + "Dudes: {{> [+404/asdf?.bar]}}", + map[string]string{"name": "Jeepers", "anotherDude": "Creepers"}, + nil, nil, + map[string]string{"+404/asdf?.bar": "{{name}}"}, + "Dudes: Jeepers", + }, + { + "Partials with string", + "Dudes: {{> '+404/asdf?.bar'}}", + map[string]string{"name": "Jeepers", "anotherDude": "Creepers"}, + nil, nil, + map[string]string{"+404/asdf?.bar": "{{name}}"}, + "Dudes: Jeepers", + }, + { + "should handle empty partial", + "Dudes: {{#dudes}}{{> dude}}{{/dudes}}", + map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, nil, + map[string]string{"dude": ""}, + "Dudes: ", + }, + + // @todo "throw on missing partial" + + // SKIP: "should pass compiler flags" + + { + "standalone partials (1) - indented partials", + "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}", + map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, nil, + map[string]string{"dude": "{{name}}\n"}, + "Dudes:\n Yehuda\n Alan\n", + }, + { + "standalone partials (2) - nested indented partials", + "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}", + map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + nil, nil, + map[string]string{"dude": "{{name}}\n {{> url}}", "url": "{{url}}!\n"}, + "Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n", + }, + + // // @todo preventIndent option + // { + // "standalone partials (3) - prevent nested indented partials", + // "Dudes:\n{{#dudes}}\n {{>dude}}\n{{/dudes}}", + // map[string]interface{}{"dudes": []map[string]string{{"name": "Yehuda", "url": "http://yehuda"}, {"name": "Alan", "url": "http://alan"}}}, + // nil, nil, + // map[string]string{"dude": "{{name}}\n {{> url}}", "url": "{{url}}!\n"}, + // "Dudes:\n Yehuda\n http://yehuda!\n Alan\n http://alan!\n", + // }, + + // @todo "compat mode" +} + +func TestPartials(t *testing.T) { + launchTests(t, partialsTests) +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/subexpressions_test.go b/vendor/github.com/aymerick/raymond/handlebars/subexpressions_test.go new file mode 100644 index 0000000..31a12a7 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/subexpressions_test.go @@ -0,0 +1,209 @@ +package handlebars + +import ( + "testing" + + "github.com/aymerick/raymond" +) + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/subexpression.js +// +var subexpressionsTests = []Test{ + { + "arg-less helper", + "{{foo (bar)}}!", + map[string]interface{}{}, + nil, + map[string]interface{}{ + "foo": func(val string) string { + return val + val + }, + "bar": func() string { + return "LOL" + }, + }, + nil, + "LOLLOL!", + }, + { + "helper w args", + "{{blog (equal a b)}}", + map[string]interface{}{"bar": "LOL"}, + nil, + map[string]interface{}{ + "blog": blogHelper, + "equal": equalHelper, + }, + nil, + "val is true", + }, + { + "mixed paths and helpers", + "{{blog baz.bat (equal a b) baz.bar}}", + map[string]interface{}{"bar": "LOL", "baz": map[string]string{"bat": "foo!", "bar": "bar!"}}, + nil, + map[string]interface{}{ + "blog": func(p, p2, p3 string) string { + return "val is " + p + ", " + p2 + " and " + p3 + }, + "equal": equalHelper, + }, + nil, + "val is foo!, true and bar!", + }, + { + "supports much nesting", + "{{blog (equal (equal true true) true)}}", + map[string]interface{}{"bar": "LOL"}, + nil, + map[string]interface{}{ + "blog": blogHelper, + "equal": equalHelper, + }, + nil, + "val is true", + }, + + { + "GH-800 : Complex subexpressions (1)", + "{{dash 'abc' (concat a b)}}", + map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}}, + nil, + map[string]interface{}{"dash": dashHelper, "concat": concatHelper}, + nil, + "abc-ab", + }, + { + "GH-800 : Complex subexpressions (2)", + "{{dash d (concat a b)}}", + map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}}, + nil, + map[string]interface{}{"dash": dashHelper, "concat": concatHelper}, + nil, + "d-ab", + }, + { + "GH-800 : Complex subexpressions (3)", + "{{dash c.c (concat a b)}}", + map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}}, + nil, + map[string]interface{}{"dash": dashHelper, "concat": concatHelper}, + nil, + "c-ab", + }, + { + "GH-800 : Complex subexpressions (4)", + "{{dash (concat a b) c.c}}", + map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}}, + nil, + map[string]interface{}{"dash": dashHelper, "concat": concatHelper}, + nil, + "ab-c", + }, + { + "GH-800 : Complex subexpressions (5)", + "{{dash (concat a e.e) c.c}}", + map[string]interface{}{"a": "a", "b": "b", "c": map[string]string{"c": "c"}, "d": "d", "e": map[string]string{"e": "e"}}, + nil, + map[string]interface{}{"dash": dashHelper, "concat": concatHelper}, + nil, + "ae-c", + }, + + { + // note: test not relevant + "provides each nested helper invocation its own options hash", + "{{equal (equal true true) true}}", + map[string]interface{}{}, + nil, + map[string]interface{}{ + "equal": equalHelper, + }, + nil, + "true", + }, + { + "with hashes", + "{{blog (equal (equal true true) true fun='yes')}}", + map[string]interface{}{"bar": "LOL"}, + nil, + map[string]interface{}{ + "blog": blogHelper, + "equal": equalHelper, + }, + nil, + "val is true", + }, + { + "as hashes", + "{{blog fun=(equal (blog fun=1) 'val is 1')}}", + map[string]interface{}{}, + nil, + map[string]interface{}{ + "blog": func(options *raymond.Options) string { + return "val is " + options.HashStr("fun") + }, + "equal": equalHelper, + }, + nil, + "val is true", + }, + { + "multiple subexpressions in a hash", + `{{input aria-label=(t "Name") placeholder=(t "Example User")}}`, + map[string]interface{}{}, + nil, + map[string]interface{}{ + "input": func(options *raymond.Options) raymond.SafeString { + return raymond.SafeString(``) + }, + "t": func(param string) raymond.SafeString { + return raymond.SafeString(param) + }, + }, + nil, + ``, + }, + { + "multiple subexpressions in a hash with context", + `{{input aria-label=(t item.field) placeholder=(t item.placeholder)}}`, + map[string]map[string]string{"item": {"field": "Name", "placeholder": "Example User"}}, + nil, + map[string]interface{}{ + "input": func(options *raymond.Options) raymond.SafeString { + return raymond.SafeString(``) + }, + "t": func(param string) raymond.SafeString { + return raymond.SafeString(param) + }, + }, + nil, + ``, + }, + + // @todo "in string params mode" + + // @todo "as hashes in string params mode" + + { + "subexpression functions on the context", + "{{foo (bar)}}!", + map[string]interface{}{"bar": func() string { return "LOL" }}, + nil, + map[string]interface{}{ + "foo": func(val string) string { + return val + val + }, + }, + nil, + "LOLLOL!", + }, + + // @todo "subexpressions can't just be property lookups" should raise error +} + +func TestSubexpressions(t *testing.T) { + launchTests(t, subexpressionsTests) +} diff --git a/vendor/github.com/aymerick/raymond/handlebars/whitespace_test.go b/vendor/github.com/aymerick/raymond/handlebars/whitespace_test.go new file mode 100644 index 0000000..f11e16e --- /dev/null +++ b/vendor/github.com/aymerick/raymond/handlebars/whitespace_test.go @@ -0,0 +1,259 @@ +package handlebars + +import "testing" + +// +// Those tests come from: +// https://github.com/wycats/handlebars.js/blob/master/spec/whitespace-control.js +// +var whitespaceControlTests = []Test{ + { + "should strip whitespace around mustache calls (1)", + " {{~foo~}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar<", + }, + { + "should strip whitespace around mustache calls (2)", + " {{~foo}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar< ", + }, + { + "should strip whitespace around mustache calls (3)", + " {{foo~}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + " bar<", + }, + { + "should strip whitespace around mustache calls (4)", + " {{~&foo~}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar<", + }, + { + "should strip whitespace around mustache calls (5)", + " {{~{foo}~}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar<", + }, + { + "should strip whitespace around mustache calls (6)", + "1\n{{foo~}} \n\n 23\n{{bar}}4", + nil, nil, nil, nil, + "1\n23\n4", + }, + + { + "blocks - should strip whitespace around simple block calls (1)", + " {{~#if foo~}} bar {{~/if~}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar", + }, + { + "blocks - should strip whitespace around simple block calls (2)", + " {{#if foo~}} bar {{/if~}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + " bar ", + }, + { + "blocks - should strip whitespace around simple block calls (3)", + " {{~#if foo}} bar {{~/if}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + " bar ", + }, + { + "blocks - should strip whitespace around simple block calls (4)", + " {{#if foo}} bar {{/if}} ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + " bar ", + }, + { + "blocks - should strip whitespace around simple block calls (5)", + " \n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\n ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar", + }, + { + "blocks - should strip whitespace around simple block calls (6)", + " a\n\n{{~#if foo~}} \n\nbar \n\n{{~/if~}}\n\na ", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + " abara ", + }, + + { + "should strip whitespace around inverse block calls (1)", + " {{~^if foo~}} bar {{~/if~}} ", + nil, nil, nil, nil, + "bar", + }, + { + "should strip whitespace around inverse block calls (2)", + " {{^if foo~}} bar {{/if~}} ", + nil, nil, nil, nil, + " bar ", + }, + { + "should strip whitespace around inverse block calls (3)", + " {{~^if foo}} bar {{~/if}} ", + nil, nil, nil, nil, + " bar ", + }, + { + "should strip whitespace around inverse block calls (4)", + " {{^if foo}} bar {{/if}} ", + nil, nil, nil, nil, + " bar ", + }, + { + "should strip whitespace around inverse block calls (5)", + " \n\n{{~^if foo~}} \n\nbar \n\n{{~/if~}}\n\n ", + nil, nil, nil, nil, + "bar", + }, + + { + "should strip whitespace around complex block calls (1)", + "{{#if foo~}} bar {{~^~}} baz {{~/if}}", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar", + }, + { + "should strip whitespace around complex block calls (2)", + "{{#if foo~}} bar {{^~}} baz {{/if}}", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar ", + }, + { + "should strip whitespace around complex block calls (3)", + "{{#if foo}} bar {{~^~}} baz {{~/if}}", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + " bar", + }, + { + "should strip whitespace around complex block calls (4)", + "{{#if foo}} bar {{^~}} baz {{/if}}", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + " bar ", + }, + { + "should strip whitespace around complex block calls (5)", + "{{#if foo~}} bar {{~else~}} baz {{~/if}}", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar", + }, + { + "should strip whitespace around complex block calls (6)", + "\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar", + }, + { + "should strip whitespace around complex block calls (7)", + "\n\n{{~#if foo~}} \n\n{{{foo}}} \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n", + map[string]string{"foo": "bar<"}, + nil, nil, nil, + "bar<", + }, + { + "should strip whitespace around complex block calls (8)", + "{{#if foo~}} bar {{~^~}} baz {{~/if}}", + nil, nil, nil, nil, + "baz", + }, + { + "should strip whitespace around complex block calls (9)", + "{{#if foo}} bar {{~^~}} baz {{/if}}", + nil, nil, nil, nil, + "baz ", + }, + { + "should strip whitespace around complex block calls (10)", + "{{#if foo~}} bar {{~^}} baz {{~/if}}", + nil, nil, nil, nil, + " baz", + }, + { + "should strip whitespace around complex block calls (11)", + "{{#if foo~}} bar {{~^}} baz {{/if}}", + nil, nil, nil, nil, + " baz ", + }, + { + "should strip whitespace around complex block calls (12)", + "{{#if foo~}} bar {{~else~}} baz {{~/if}}", + nil, nil, nil, nil, + "baz", + }, + { + "should strip whitespace around complex block calls (13)", + "\n\n{{~#if foo~}} \n\nbar \n\n{{~^~}} \n\nbaz \n\n{{~/if~}}\n\n", + nil, nil, nil, nil, + "baz", + }, + + { + "should strip whitespace around partials (1)", + "foo {{~> dude~}} ", + nil, nil, nil, + map[string]string{"dude": "bar"}, + "foobar", + }, + { + "should strip whitespace around partials (2)", + "foo {{> dude~}} ", + nil, nil, nil, + map[string]string{"dude": "bar"}, + "foo bar", + }, + { + "should strip whitespace around partials (3)", + "foo {{> dude}} ", + nil, nil, nil, + map[string]string{"dude": "bar"}, + "foo bar ", + }, + { + "should strip whitespace around partials (4)", + "foo\n {{~> dude}} ", + nil, nil, nil, + map[string]string{"dude": "bar"}, + "foobar", + }, + { + "should strip whitespace around partials (5)", + "foo\n {{> dude}} ", + nil, nil, nil, + map[string]string{"dude": "bar"}, + "foo\n bar", + }, + + { + "should only strip whitespace once", + " {{~foo~}} {{foo}} {{foo}} ", + map[string]string{"foo": "bar"}, + nil, nil, nil, + "barbar bar ", + }, +} + +func TestWhitespaceControl(t *testing.T) { + launchTests(t, whitespaceControlTests) +} diff --git a/vendor/github.com/aymerick/raymond/helper.go b/vendor/github.com/aymerick/raymond/helper.go new file mode 100644 index 0000000..0e5ed6e --- /dev/null +++ b/vendor/github.com/aymerick/raymond/helper.go @@ -0,0 +1,371 @@ +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 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)) +} diff --git a/vendor/github.com/aymerick/raymond/helper_test.go b/vendor/github.com/aymerick/raymond/helper_test.go new file mode 100644 index 0000000..b867e3f --- /dev/null +++ b/vendor/github.com/aymerick/raymond/helper_test.go @@ -0,0 +1,193 @@ +package raymond + +import "testing" + +const ( + VERBOSE = false +) + +// +// Helpers +// + +func barHelper(options *Options) string { return "bar" } + +func echoHelper(str string, nb int) string { + result := "" + for i := 0; i < nb; i++ { + result += str + } + + return result +} + +func boolHelper(b bool) string { + if b { + return "yes it is" + } + + return "absolutely not" +} + +func gnakHelper(nb int) string { + result := "" + for i := 0; i < nb; i++ { + result += "GnAK!" + } + + return result +} + +// +// Tests +// + +var helperTests = []Test{ + { + "simple helper", + `{{foo}}`, + nil, nil, + map[string]interface{}{"foo": barHelper}, + nil, + `bar`, + }, + { + "helper with literal string param", + `{{echo "foo" 1}}`, + nil, nil, + map[string]interface{}{"echo": echoHelper}, + nil, + `foo`, + }, + { + "helper with identifier param", + `{{echo foo 1}}`, + map[string]interface{}{"foo": "bar"}, + nil, + map[string]interface{}{"echo": echoHelper}, + nil, + `bar`, + }, + { + "helper with literal boolean param", + `{{bool true}}`, + nil, nil, + map[string]interface{}{"bool": boolHelper}, + nil, + `yes it is`, + }, + { + "helper with literal boolean param", + `{{bool false}}`, + nil, nil, + map[string]interface{}{"bool": boolHelper}, + nil, + `absolutely not`, + }, + { + "helper with literal boolean param", + `{{gnak 5}}`, + nil, nil, + map[string]interface{}{"gnak": gnakHelper}, + nil, + `GnAK!GnAK!GnAK!GnAK!GnAK!`, + }, + { + "helper with several parameters", + `{{echo "GnAK!" 3}}`, + nil, nil, + map[string]interface{}{"echo": echoHelper}, + nil, + `GnAK!GnAK!GnAK!`, + }, + { + "#if helper with true literal", + `{{#if true}}YES MAN{{/if}}`, + nil, nil, nil, nil, + `YES MAN`, + }, + { + "#if helper with false literal", + `{{#if false}}YES MAN{{/if}}`, + nil, nil, nil, nil, + ``, + }, + { + "#if helper with truthy identifier", + `{{#if ok}}YES MAN{{/if}}`, + map[string]interface{}{"ok": true}, + nil, nil, nil, + `YES MAN`, + }, + { + "#if helper with falsy identifier", + `{{#if ok}}YES MAN{{/if}}`, + map[string]interface{}{"ok": false}, + nil, nil, nil, + ``, + }, + { + "#unless helper with true literal", + `{{#unless true}}YES MAN{{/unless}}`, + nil, nil, nil, nil, + ``, + }, + { + "#unless helper with false literal", + `{{#unless false}}YES MAN{{/unless}}`, + nil, nil, nil, nil, + `YES MAN`, + }, + { + "#unless helper with truthy identifier", + `{{#unless ok}}YES MAN{{/unless}}`, + map[string]interface{}{"ok": true}, + nil, nil, nil, + ``, + }, + { + "#unless helper with falsy identifier", + `{{#unless ok}}YES MAN{{/unless}}`, + map[string]interface{}{"ok": false}, + nil, nil, nil, + `YES MAN`, + }, +} + +// +// Let's go +// + +func TestHelper(t *testing.T) { + t.Parallel() + + launchTests(t, helperTests) +} + +// +// Fixes: https://github.com/aymerick/raymond/issues/2 +// + +type Author struct { + FirstName string + LastName string +} + +func TestHelperCtx(t *testing.T) { + RegisterHelper("template", func(name string, options *Options) SafeString { + context := options.Ctx() + + template := name + " - {{ firstName }} {{ lastName }}" + result, _ := Render(template, context) + + return SafeString(result) + }) + + template := `By {{ template "namefile" }}` + context := Author{"Alan", "Johnson"} + + result, _ := Render(template, context) + if result != "By namefile - Alan Johnson" { + t.Errorf("Failed to render template in helper: %q", result) + } +} 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/lexer_test.go b/vendor/github.com/aymerick/raymond/lexer/lexer_test.go new file mode 100644 index 0000000..71e8bf0 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/lexer/lexer_test.go @@ -0,0 +1,541 @@ +package lexer + +import ( + "fmt" + "testing" +) + +type lexTest struct { + name string + input string + tokens []Token +} + +// helpers +func tokContent(val string) Token { return Token{TokenContent, val, 0, 1} } +func tokID(val string) Token { return Token{TokenID, val, 0, 1} } +func tokSep(val string) Token { return Token{TokenSep, val, 0, 1} } +func tokString(val string) Token { return Token{TokenString, val, 0, 1} } +func tokNumber(val string) Token { return Token{TokenNumber, val, 0, 1} } +func tokInverse(val string) Token { return Token{TokenInverse, val, 0, 1} } +func tokBool(val string) Token { return Token{TokenBoolean, val, 0, 1} } +func tokError(val string) Token { return Token{TokenError, val, 0, 1} } +func tokComment(val string) Token { return Token{TokenComment, val, 0, 1} } + +var tokEOF = Token{TokenEOF, "", 0, 1} +var tokEquals = Token{TokenEquals, "=", 0, 1} +var tokData = Token{TokenData, "@", 0, 1} +var tokOpen = Token{TokenOpen, "{{", 0, 1} +var tokOpenAmp = Token{TokenOpen, "{{&", 0, 1} +var tokOpenPartial = Token{TokenOpenPartial, "{{>", 0, 1} +var tokClose = Token{TokenClose, "}}", 0, 1} +var tokOpenStrip = Token{TokenOpen, "{{~", 0, 1} +var tokCloseStrip = Token{TokenClose, "~}}", 0, 1} +var tokOpenUnescaped = Token{TokenOpenUnescaped, "{{{", 0, 1} +var tokCloseUnescaped = Token{TokenCloseUnescaped, "}}}", 0, 1} +var tokOpenUnescapedStrip = Token{TokenOpenUnescaped, "{{~{", 0, 1} +var tokCloseUnescapedStrip = Token{TokenCloseUnescaped, "}~}}", 0, 1} +var tokOpenBlock = Token{TokenOpenBlock, "{{#", 0, 1} +var tokOpenEndBlock = Token{TokenOpenEndBlock, "{{/", 0, 1} +var tokOpenInverse = Token{TokenOpenInverse, "{{^", 0, 1} +var tokOpenInverseChain = Token{TokenOpenInverseChain, "{{else", 0, 1} +var tokOpenSexpr = Token{TokenOpenSexpr, "(", 0, 1} +var tokCloseSexpr = Token{TokenCloseSexpr, ")", 0, 1} +var tokOpenBlockParams = Token{TokenOpenBlockParams, "as |", 0, 1} +var tokCloseBlockParams = Token{TokenCloseBlockParams, "|", 0, 1} +var tokOpenRawBlock = Token{TokenOpenRawBlock, "{{{{", 0, 1} +var tokCloseRawBlock = Token{TokenCloseRawBlock, "}}}}", 0, 1} +var tokOpenEndRawBlock = Token{TokenOpenEndRawBlock, "{{{{/", 0, 1} + +var lexTests = []lexTest{ + {"empty", "", []Token{tokEOF}}, + {"spaces", " \t\n", []Token{tokContent(" \t\n"), tokEOF}}, + {"content", `now is the time`, []Token{tokContent(`now is the time`), tokEOF}}, + + { + `does not tokenizes identifier starting with true as boolean`, + `{{ foo truebar }}`, + []Token{tokOpen, tokID("foo"), tokID("truebar"), tokClose, tokEOF}, + }, + { + `does not tokenizes identifier starting with false as boolean`, + `{{ foo falsebar }}`, + []Token{tokOpen, tokID("foo"), tokID("falsebar"), tokClose, tokEOF}, + }, + { + `tokenizes raw block`, + `{{{{foo}}}} {{{{/foo}}}}`, + []Token{tokOpenRawBlock, tokID("foo"), tokCloseRawBlock, tokContent(" "), tokOpenEndRawBlock, tokID("foo"), tokCloseRawBlock, tokEOF}, + }, + { + `tokenizes raw block with mustaches in content`, + `{{{{foo}}}}{{bar}}{{{{/foo}}}}`, + []Token{tokOpenRawBlock, tokID("foo"), tokCloseRawBlock, tokContent("{{bar}}"), tokOpenEndRawBlock, tokID("foo"), tokCloseRawBlock, tokEOF}, + }, + { + `tokenizes @../foo`, + `{{@../foo}}`, + []Token{tokOpen, tokData, tokID(".."), tokSep("/"), tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes escaped mustaches`, + "\\{{bar}}", + []Token{tokContent("{{bar}}"), tokEOF}, + }, + { + `tokenizes strip mustaches`, + `{{~ foo ~}}`, + []Token{tokOpenStrip, tokID("foo"), tokCloseStrip, tokEOF}, + }, + { + `tokenizes unescaped strip mustaches`, + `{{~{ foo }~}}`, + []Token{tokOpenUnescapedStrip, tokID("foo"), tokCloseUnescapedStrip, tokEOF}, + }, + + // + // Next tests come from: + // https://github.com/wycats/handlebars.js/blob/master/spec/tokenizer.js + // + { + `tokenizes a simple mustache as "OPEN ID CLOSE"`, + `{{foo}}`, + []Token{tokOpen, tokID("foo"), tokClose, tokEOF}, + }, + { + `supports unescaping with &`, + `{{&bar}}`, + []Token{tokOpenAmp, tokID("bar"), tokClose, tokEOF}, + }, + { + `supports unescaping with {{{`, + `{{{bar}}}`, + []Token{tokOpenUnescaped, tokID("bar"), tokCloseUnescaped, tokEOF}, + }, + { + `supports escaping delimiters`, + "{{foo}} \\{{bar}} {{baz}}", + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} "), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `supports escaping multiple delimiters`, + "{{foo}} \\{{bar}} \\{{baz}}", + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} "), tokContent("{{baz}}"), tokEOF}, + }, + { + `supports escaping a triple stash`, + "{{foo}} \\{{{bar}}} {{baz}}", + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{{bar}}} "), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `supports escaping escape character`, + "{{foo}} \\\\{{bar}} {{baz}}", + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpen, tokID("bar"), tokClose, tokContent(" "), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `supports escaping multiple escape characters`, + "{{foo}} \\\\{{bar}} \\\\{{baz}}", + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpen, tokID("bar"), tokClose, tokContent(" \\"), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `supports escaped mustaches after escaped escape characters`, + "{{foo}} \\\\{{bar}} \\{{baz}}", + // NOTE: JS implementation returns: + // ['OPEN', 'ID', 'CLOSE', 'CONTENT', 'OPEN', 'ID', 'CLOSE', 'CONTENT', 'CONTENT', 'CONTENT'], + // WTF is the last CONTENT ? + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpen, tokID("bar"), tokClose, tokContent(" "), tokContent("{{baz}}"), tokEOF}, + }, + { + `supports escaped escape characters after escaped mustaches`, + "{{foo}} \\{{bar}} \\\\{{baz}}", + // NOTE: JS implementation returns: + // []Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} "), tokContent("\\"), tokOpen, tokID("baz"), tokClose, tokEOF}, + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" "), tokContent("{{bar}} \\"), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `supports escaped escape character on a triple stash`, + "{{foo}} \\\\{{{bar}}} {{baz}}", + []Token{tokOpen, tokID("foo"), tokClose, tokContent(" \\"), tokOpenUnescaped, tokID("bar"), tokCloseUnescaped, tokContent(" "), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `tokenizes a simple path`, + `{{foo/bar}}`, + []Token{tokOpen, tokID("foo"), tokSep("/"), tokID("bar"), tokClose, tokEOF}, + }, + { + `allows dot notation (1)`, + `{{foo.bar}}`, + []Token{tokOpen, tokID("foo"), tokSep("."), tokID("bar"), tokClose, tokEOF}, + }, + { + `allows dot notation (2)`, + `{{foo.bar.baz}}`, + []Token{tokOpen, tokID("foo"), tokSep("."), tokID("bar"), tokSep("."), tokID("baz"), tokClose, tokEOF}, + }, + { + `allows path literals with []`, + `{{foo.[bar]}}`, + []Token{tokOpen, tokID("foo"), tokSep("."), tokID("[bar]"), tokClose, tokEOF}, + }, + { + `allows multiple path literals on a line with []`, + `{{foo.[bar]}}{{foo.[baz]}}`, + []Token{tokOpen, tokID("foo"), tokSep("."), tokID("[bar]"), tokClose, tokOpen, tokID("foo"), tokSep("."), tokID("[baz]"), tokClose, tokEOF}, + }, + { + `tokenizes {{.}} as OPEN ID CLOSE`, + `{{.}}`, + []Token{tokOpen, tokID("."), tokClose, tokEOF}, + }, + { + `tokenizes a path as "OPEN (ID SEP)* ID CLOSE"`, + `{{../foo/bar}}`, + []Token{tokOpen, tokID(".."), tokSep("/"), tokID("foo"), tokSep("/"), tokID("bar"), tokClose, tokEOF}, + }, + { + `tokenizes a path with .. as a parent path`, + `{{../foo.bar}}`, + []Token{tokOpen, tokID(".."), tokSep("/"), tokID("foo"), tokSep("."), tokID("bar"), tokClose, tokEOF}, + }, + { + `tokenizes a path with this/foo as OPEN ID SEP ID CLOSE`, + `{{this/foo}}`, + []Token{tokOpen, tokID("this"), tokSep("/"), tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes a simple mustache with spaces as "OPEN ID CLOSE"`, + `{{ foo }}`, + []Token{tokOpen, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes a simple mustache with line breaks as "OPEN ID ID CLOSE"`, + "{{ foo \n bar }}", + []Token{tokOpen, tokID("foo"), tokID("bar"), tokClose, tokEOF}, + }, + { + `tokenizes raw content as "CONTENT"`, + `foo {{ bar }} baz`, + []Token{tokContent("foo "), tokOpen, tokID("bar"), tokClose, tokContent(" baz"), tokEOF}, + }, + { + `tokenizes a partial as "OPEN_PARTIAL ID CLOSE"`, + `{{> foo}}`, + []Token{tokOpenPartial, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes a partial with context as "OPEN_PARTIAL ID ID CLOSE"`, + `{{> foo bar }}`, + []Token{tokOpenPartial, tokID("foo"), tokID("bar"), tokClose, tokEOF}, + }, + { + `tokenizes a partial without spaces as "OPEN_PARTIAL ID CLOSE"`, + `{{>foo}}`, + []Token{tokOpenPartial, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"`, + `{{>foo }}`, + []Token{tokOpenPartial, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes a partial space at the }); as "OPEN_PARTIAL ID CLOSE"`, + `{{>foo/bar.baz }}`, + []Token{tokOpenPartial, tokID("foo"), tokSep("/"), tokID("bar"), tokSep("."), tokID("baz"), tokClose, tokEOF}, + }, + { + `tokenizes a comment as "COMMENT"`, + `foo {{! this is a comment }} bar {{ baz }}`, + []Token{tokContent("foo "), tokComment("{{! this is a comment }}"), tokContent(" bar "), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `tokenizes a block comment as "COMMENT"`, + `foo {{!-- this is a {{comment}} --}} bar {{ baz }}`, + []Token{tokContent("foo "), tokComment("{{!-- this is a {{comment}} --}}"), tokContent(" bar "), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `tokenizes a block comment with whitespace as "COMMENT"`, + "foo {{!-- this is a\n{{comment}}\n--}} bar {{ baz }}", + []Token{tokContent("foo "), tokComment("{{!-- this is a\n{{comment}}\n--}}"), tokContent(" bar "), tokOpen, tokID("baz"), tokClose, tokEOF}, + }, + { + `tokenizes open and closing blocks as OPEN_BLOCK, ID, CLOSE ..., OPEN_ENDBLOCK ID CLOSE`, + `{{#foo}}content{{/foo}}`, + []Token{tokOpenBlock, tokID("foo"), tokClose, tokContent("content"), tokOpenEndBlock, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes inverse sections as "INVERSE"`, + `{{^}}`, + []Token{tokInverse("{{^}}"), tokEOF}, + }, + { + `tokenizes inverse sections as "INVERSE" with alternate format`, + `{{else}}`, + []Token{tokInverse("{{else}}"), tokEOF}, + }, + { + `tokenizes inverse sections as "INVERSE" with spaces`, + `{{ else }}`, + []Token{tokInverse("{{ else }}"), tokEOF}, + }, + { + `tokenizes inverse sections with ID as "OPEN_INVERSE ID CLOSE"`, + `{{^foo}}`, + []Token{tokOpenInverse, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes inverse sections with ID and spaces as "OPEN_INVERSE ID CLOSE"`, + `{{^ foo }}`, + []Token{tokOpenInverse, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes mustaches with params as "OPEN ID ID ID CLOSE"`, + `{{ foo bar baz }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokClose, tokEOF}, + }, + { + `tokenizes mustaches with String params as "OPEN ID ID STRING CLOSE"`, + `{{ foo bar "baz" }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokString("baz"), tokClose, tokEOF}, + }, + { + `tokenizes mustaches with String params using single quotes as "OPEN ID ID STRING CLOSE"`, + `{{ foo bar 'baz' }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokString("baz"), tokClose, tokEOF}, + }, + { + `tokenizes String params with spaces inside as "STRING"`, + `{{ foo bar "baz bat" }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokString("baz bat"), tokClose, tokEOF}, + }, + { + `tokenizes String params with escapes quotes as STRING`, + `{{ foo "bar\"baz" }}`, + []Token{tokOpen, tokID("foo"), tokString(`bar"baz`), tokClose, tokEOF}, + }, + { + `tokenizes String params using single quotes with escapes quotes as STRING`, + `{{ foo 'bar\'baz' }}`, + []Token{tokOpen, tokID("foo"), tokString(`bar'baz`), tokClose, tokEOF}, + }, + { + `tokenizes numbers`, + `{{ foo 1 }}`, + []Token{tokOpen, tokID("foo"), tokNumber("1"), tokClose, tokEOF}, + }, + { + `tokenizes floats`, + `{{ foo 1.1 }}`, + []Token{tokOpen, tokID("foo"), tokNumber("1.1"), tokClose, tokEOF}, + }, + { + `tokenizes negative numbers`, + `{{ foo -1 }}`, + []Token{tokOpen, tokID("foo"), tokNumber("-1"), tokClose, tokEOF}, + }, + { + `tokenizes negative floats`, + `{{ foo -1.1 }}`, + []Token{tokOpen, tokID("foo"), tokNumber("-1.1"), tokClose, tokEOF}, + }, + { + `tokenizes boolean true`, + `{{ foo true }}`, + []Token{tokOpen, tokID("foo"), tokBool("true"), tokClose, tokEOF}, + }, + { + `tokenizes boolean false`, + `{{ foo false }}`, + []Token{tokOpen, tokID("foo"), tokBool("false"), tokClose, tokEOF}, + }, + // SKIP: 'tokenizes undefined and null' + { + `tokenizes hash arguments (1)`, + `{{ foo bar=baz }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokEquals, tokID("baz"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (2)`, + `{{ foo bar baz=bat }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokID("bat"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (3)`, + `{{ foo bar baz=1 }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokNumber("1"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (4)`, + `{{ foo bar baz=true }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokBool("true"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (5)`, + `{{ foo bar baz=false }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokBool("false"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (6)`, + "{{ foo bar\n baz=bat }}", + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokID("bat"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (7)`, + `{{ foo bar baz="bat" }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokString("bat"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (8)`, + `{{ foo bar baz="bat" bam=wot }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokID("baz"), tokEquals, tokString("bat"), tokID("bam"), tokEquals, tokID("wot"), tokClose, tokEOF}, + }, + { + `tokenizes hash arguments (9)`, + `{{foo omg bar=baz bat="bam"}}`, + []Token{tokOpen, tokID("foo"), tokID("omg"), tokID("bar"), tokEquals, tokID("baz"), tokID("bat"), tokEquals, tokString("bam"), tokClose, tokEOF}, + }, + { + `tokenizes special @ identifiers (1)`, + `{{ @foo }}`, + []Token{tokOpen, tokData, tokID("foo"), tokClose, tokEOF}, + }, + { + `tokenizes special @ identifiers (2)`, + `{{ foo @bar }}`, + []Token{tokOpen, tokID("foo"), tokData, tokID("bar"), tokClose, tokEOF}, + }, + { + `tokenizes special @ identifiers (3)`, + `{{ foo bar=@baz }}`, + []Token{tokOpen, tokID("foo"), tokID("bar"), tokEquals, tokData, tokID("baz"), tokClose, tokEOF}, + }, + { + `does not time out in a mustache with a single } followed by EOF`, + `{{foo}`, + []Token{tokOpen, tokID("foo"), tokError("Unexpected character in expression: '}'")}, + }, + { + `does not time out in a mustache when invalid ID characters are used`, + `{{foo & }}`, + []Token{tokOpen, tokID("foo"), tokError("Unexpected character in expression: '&'")}, + }, + { + `tokenizes subexpressions (1)`, + `{{foo (bar)}}`, + []Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("bar"), tokCloseSexpr, tokClose, tokEOF}, + }, + { + `tokenizes subexpressions (2)`, + `{{foo (a-x b-y)}}`, + []Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("a-x"), tokID("b-y"), tokCloseSexpr, tokClose, tokEOF}, + }, + { + `tokenizes nested subexpressions`, + `{{foo (bar (lol rofl)) (baz)}}`, + []Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("bar"), tokOpenSexpr, tokID("lol"), tokID("rofl"), tokCloseSexpr, tokCloseSexpr, tokOpenSexpr, tokID("baz"), tokCloseSexpr, tokClose, tokEOF}, + }, + { + `tokenizes nested subexpressions: literals`, + `{{foo (bar (lol true) false) (baz 1) (blah 'b') (blorg "c")}}`, + []Token{tokOpen, tokID("foo"), tokOpenSexpr, tokID("bar"), tokOpenSexpr, tokID("lol"), tokBool("true"), tokCloseSexpr, tokBool("false"), tokCloseSexpr, tokOpenSexpr, tokID("baz"), tokNumber("1"), tokCloseSexpr, tokOpenSexpr, tokID("blah"), tokString("b"), tokCloseSexpr, tokOpenSexpr, tokID("blorg"), tokString("c"), tokCloseSexpr, tokClose, tokEOF}, + }, + { + `tokenizes block params (1)`, + `{{#foo as |bar|}}`, + []Token{tokOpenBlock, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokCloseBlockParams, tokClose, tokEOF}, + }, + { + `tokenizes block params (2)`, + `{{#foo as |bar baz|}}`, + []Token{tokOpenBlock, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF}, + }, + { + `tokenizes block params (3)`, + `{{#foo as | bar baz |}}`, + []Token{tokOpenBlock, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF}, + }, + { + `tokenizes block params (4)`, + `{{#foo as as | bar baz |}}`, + []Token{tokOpenBlock, tokID("foo"), tokID("as"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF}, + }, + { + `tokenizes block params (5)`, + `{{else foo as |bar baz|}}`, + []Token{tokOpenInverseChain, tokID("foo"), tokOpenBlockParams, tokID("bar"), tokID("baz"), tokCloseBlockParams, tokClose, tokEOF}, + }, +} + +func collect(t *lexTest) []Token { + var result []Token + + l := scanWithName(t.input, t.name) + for { + token := l.NextToken() + result = append(result, token) + + if token.Kind == TokenEOF || token.Kind == TokenError { + break + } + } + + return result +} + +func equal(i1, i2 []Token, checkPos bool) bool { + if len(i1) != len(i2) { + return false + } + + for k := range i1 { + if i1[k].Kind != i2[k].Kind { + return false + } + + if checkPos && i1[k].Pos != i2[k].Pos { + return false + } + + if i1[k].Val != i2[k].Val { + return false + } + } + + return true +} + +func TestLexer(t *testing.T) { + t.Parallel() + + for _, test := range lexTests { + tokens := collect(&test) + if !equal(tokens, test.tokens, false) { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\nexpected\n\t%v\ngot\n\t%+v\n", test.name, test.input, test.tokens, tokens) + } + } +} + +// @todo Test errors: +// `{{{{raw foo` + +// package example +func Example() { + source := "You know {{nothing}} John Snow" + + output := "" + + lex := 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 == TokenEOF || token.Kind == TokenError { + break + } + } + + fmt.Print(output) + // Output: Content{"You know "} Open{"{{"} ID{"nothing"} Close{"}}"} Content{" John Snow"} EOF +} 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/mustache_test.go b/vendor/github.com/aymerick/raymond/mustache_test.go new file mode 100644 index 0000000..fadced5 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/mustache_test.go @@ -0,0 +1,234 @@ +package raymond + +import ( + "io/ioutil" + "path" + "regexp" + "strings" + "testing" + + "gopkg.in/yaml.v2" +) + +// +// Note, as the JS implementation, the divergences from mustache spec: +// - we don't support alternative delimeters +// - the mustache lambda spec differs +// + +type mustacheTest struct { + Name string + Desc string + Data interface{} + Template string + Expected string + Partials map[string]string +} + +type mustacheTestFile struct { + Overview string + Tests []mustacheTest +} + +var ( + rAltDelim = regexp.MustCompile(regexp.QuoteMeta("{{=")) +) + +var ( + musTestLambdaInterMult = 0 +) + +func TestMustache(t *testing.T) { + skipFiles := map[string]bool{ + // mustache lambdas differ from handlebars lambdas + "~lambdas.yml": true, + } + + for _, fileName := range mustacheTestFiles() { + if skipFiles[fileName] { + // fmt.Printf("Skipped file: %s\n", fileName) + continue + } + + launchTests(t, testsFromMustacheFile(fileName)) + } +} + +func testsFromMustacheFile(fileName string) []Test { + result := []Test{} + + fileData, err := ioutil.ReadFile(path.Join("mustache", "specs", fileName)) + if err != nil { + panic(err) + } + + var testFile mustacheTestFile + if err := yaml.Unmarshal(fileData, &testFile); err != nil { + panic(err) + } + + for _, mustacheTest := range testFile.Tests { + if mustBeSkipped(mustacheTest, fileName) { + // fmt.Printf("Skipped test: %s\n", mustacheTest.Name) + continue + } + + test := Test{ + name: mustacheTest.Name, + input: mustacheTest.Template, + data: mustacheTest.Data, + partials: mustacheTest.Partials, + output: mustacheTest.Expected, + } + + result = append(result, test) + } + + return result +} + +// returns true if test must be skipped +func mustBeSkipped(test mustacheTest, fileName string) bool { + // handlebars does not support alternative delimiters + return haveAltDelimiter(test) || + // the JS implementation skips those tests + fileName == "partials.yml" && (test.Name == "Failed Lookup" || test.Name == "Standalone Indentation") +} + +// returns true if test have alternative delimeter in template or in partials +func haveAltDelimiter(test mustacheTest) bool { + // check template + if rAltDelim.MatchString(test.Template) { + return true + } + + // check partials + for _, partial := range test.Partials { + if rAltDelim.MatchString(partial) { + return true + } + } + + return false +} + +func mustacheTestFiles() []string { + var result []string + + files, err := ioutil.ReadDir(path.Join("mustache", "specs")) + if err != nil { + panic(err) + } + + for _, file := range files { + fileName := file.Name() + + if !file.IsDir() && strings.HasSuffix(fileName, ".yml") { + result = append(result, fileName) + } + } + + return result +} + +// +// Following tests come fron ~lambdas.yml +// + +var mustacheLambdasTests = []Test{ + { + "Interpolation", + "Hello, {{lambda}}!", + map[string]interface{}{"lambda": func() string { return "world" }}, + nil, nil, nil, + "Hello, world!", + }, + + // // SKIP: lambda return value is not parsed + // { + // "Interpolation - Expansion", + // "Hello, {{lambda}}!", + // map[string]interface{}{"lambda": func() string { return "{{planet}}" }}, + // nil, nil, nil, + // "Hello, world!", + // }, + + // SKIP "Interpolation - Alternate Delimiters" + + { + "Interpolation - Multiple Calls", + "{{lambda}} == {{{lambda}}} == {{lambda}}", + map[string]interface{}{"lambda": func() string { + musTestLambdaInterMult++ + return Str(musTestLambdaInterMult) + }}, + nil, nil, nil, + "1 == 2 == 3", + }, + + { + "Escaping", + "<{{lambda}}{{{lambda}}}", + map[string]interface{}{"lambda": func() string { return ">" }}, + nil, nil, nil, + "<>>", + }, + + // // SKIP: "Lambdas used for sections should receive the raw section string." + // { + // "Section", + // "<{{#lambda}}{{x}}{{/lambda}}>", + // map[string]interface{}{"lambda": func(param string) string { + // if param == "{{x}}" { + // return "yes" + // } + + // return "false" + // }, "x": "Error!"}, + // nil, nil, nil, + // "", + // }, + + // // SKIP: lambda return value is not parsed + // { + // "Section - Expansion", + // "<{{#lambda}}-{{/lambda}}>", + // map[string]interface{}{"lambda": func(param string) string { + // return param + "{{planet}}" + param + // }, "planet": "Earth"}, + // nil, nil, nil, + // "<-Earth->", + // }, + + // SKIP: "Section - Alternate Delimiters" + + { + "Section - Multiple Calls", + "{{#lambda}}FILE{{/lambda}} != {{#lambda}}LINE{{/lambda}}", + map[string]interface{}{"lambda": func(options *Options) string { + return "__" + options.Fn() + "__" + }}, + nil, nil, nil, + "__FILE__ != __LINE__", + }, + + // // SKIP: "Lambdas used for inverted sections should be considered truthy." + // { + // "Inverted Section", + // "<{{^lambda}}{{static}}{{/lambda}}>", + // map[string]interface{}{ + // "lambda": func() interface{} { + // return false + // }, + // "static": "static", + // }, + // nil, nil, nil, + // "<>", + // }, +} + +func TestMustacheLambdas(t *testing.T) { + t.Parallel() + + launchTests(t, mustacheLambdasTests) +} 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/parser_test.go b/vendor/github.com/aymerick/raymond/parser/parser_test.go new file mode 100644 index 0000000..0baafbc --- /dev/null +++ b/vendor/github.com/aymerick/raymond/parser/parser_test.go @@ -0,0 +1,200 @@ +package parser + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aymerick/raymond/ast" + "github.com/aymerick/raymond/lexer" +) + +type parserTest struct { + name string + input string + output string +} + +var parserTests = []parserTest{ + // + // Next tests come from: + // https://github.com/wycats/handlebars.js/blob/master/spec/parser.js + // + {"parses simple mustaches (1)", `{{123}}`, "{{ NUMBER{123} [] }}\n"}, + {"parses simple mustaches (2)", `{{"foo"}}`, "{{ \"foo\" [] }}\n"}, + {"parses simple mustaches (3)", `{{false}}`, "{{ BOOLEAN{false} [] }}\n"}, + {"parses simple mustaches (4)", `{{true}}`, "{{ BOOLEAN{true} [] }}\n"}, + {"parses simple mustaches (5)", `{{foo}}`, "{{ PATH:foo [] }}\n"}, + {"parses simple mustaches (6)", `{{foo?}}`, "{{ PATH:foo? [] }}\n"}, + {"parses simple mustaches (7)", `{{foo_}}`, "{{ PATH:foo_ [] }}\n"}, + {"parses simple mustaches (8)", `{{foo-}}`, "{{ PATH:foo- [] }}\n"}, + {"parses simple mustaches (9)", `{{foo:}}`, "{{ PATH:foo: [] }}\n"}, + + {"parses simple mustaches with data", `{{@foo}}`, "{{ @PATH:foo [] }}\n"}, + {"parses simple mustaches with data paths", `{{@../foo}}`, "{{ @PATH:foo [] }}\n"}, + {"parses mustaches with paths", `{{foo/bar}}`, "{{ PATH:foo/bar [] }}\n"}, + {"parses mustaches with this/foo", `{{this/foo}}`, "{{ PATH:foo [] }}\n"}, + {"parses mustaches with - in a path", `{{foo-bar}}`, "{{ PATH:foo-bar [] }}\n"}, + {"parses mustaches with parameters", `{{foo bar}}`, "{{ PATH:foo [PATH:bar] }}\n"}, + {"parses mustaches with string parameters", `{{foo bar "baz" }}`, "{{ PATH:foo [PATH:bar, \"baz\"] }}\n"}, + {"parses mustaches with NUMBER parameters", `{{foo 1}}`, "{{ PATH:foo [NUMBER{1}] }}\n"}, + {"parses mustaches with BOOLEAN parameters (1)", `{{foo true}}`, "{{ PATH:foo [BOOLEAN{true}] }}\n"}, + {"parses mustaches with BOOLEAN parameters (2)", `{{foo false}}`, "{{ PATH:foo [BOOLEAN{false}] }}\n"}, + {"parses mustaches with DATA parameters", `{{foo @bar}}`, "{{ PATH:foo [@PATH:bar] }}\n"}, + + {"parses mustaches with hash arguments (01)", `{{foo bar=baz}}`, "{{ PATH:foo [] HASH{bar=PATH:baz} }}\n"}, + {"parses mustaches with hash arguments (02)", `{{foo bar=1}}`, "{{ PATH:foo [] HASH{bar=NUMBER{1}} }}\n"}, + {"parses mustaches with hash arguments (03)", `{{foo bar=true}}`, "{{ PATH:foo [] HASH{bar=BOOLEAN{true}} }}\n"}, + {"parses mustaches with hash arguments (04)", `{{foo bar=false}}`, "{{ PATH:foo [] HASH{bar=BOOLEAN{false}} }}\n"}, + {"parses mustaches with hash arguments (05)", `{{foo bar=@baz}}`, "{{ PATH:foo [] HASH{bar=@PATH:baz} }}\n"}, + {"parses mustaches with hash arguments (06)", `{{foo bar=baz bat=bam}}`, "{{ PATH:foo [] HASH{bar=PATH:baz, bat=PATH:bam} }}\n"}, + {"parses mustaches with hash arguments (07)", `{{foo bar=baz bat="bam"}}`, "{{ PATH:foo [] HASH{bar=PATH:baz, bat=\"bam\"} }}\n"}, + {"parses mustaches with hash arguments (08)", `{{foo bat='bam'}}`, "{{ PATH:foo [] HASH{bat=\"bam\"} }}\n"}, + {"parses mustaches with hash arguments (09)", `{{foo omg bar=baz bat="bam"}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\"} }}\n"}, + {"parses mustaches with hash arguments (10)", `{{foo omg bar=baz bat="bam" baz=1}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\", baz=NUMBER{1}} }}\n"}, + {"parses mustaches with hash arguments (11)", `{{foo omg bar=baz bat="bam" baz=true}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\", baz=BOOLEAN{true}} }}\n"}, + {"parses mustaches with hash arguments (12)", `{{foo omg bar=baz bat="bam" baz=false}}`, "{{ PATH:foo [PATH:omg] HASH{bar=PATH:baz, bat=\"bam\", baz=BOOLEAN{false}} }}\n"}, + + {"parses contents followed by a mustache", `foo bar {{baz}}`, "CONTENT[ 'foo bar ' ]\n{{ PATH:baz [] }}\n"}, + + {"parses a partial (1)", `{{> foo }}`, "{{> PARTIAL:foo }}\n"}, + {"parses a partial (2)", `{{> "foo" }}`, "{{> PARTIAL:foo }}\n"}, + {"parses a partial (3)", `{{> 1 }}`, "{{> PARTIAL:1 }}\n"}, + {"parses a partial with context", `{{> foo bar}}`, "{{> PARTIAL:foo PATH:bar }}\n"}, + {"parses a partial with hash", `{{> foo bar=bat}}`, "{{> PARTIAL:foo HASH{bar=PATH:bat} }}\n"}, + {"parses a partial with context and hash", `{{> foo bar bat=baz}}`, "{{> PARTIAL:foo PATH:bar HASH{bat=PATH:baz} }}\n"}, + {"parses a partial with a complex name", `{{> shared/partial?.bar}}`, "{{> PARTIAL:shared/partial?.bar }}\n"}, + + {"parses a comment", `{{! this is a comment }}`, "{{! ' this is a comment ' }}\n"}, + {"parses a multi-line comment", "{{!\nthis is a multi-line comment\n}}", "{{! '\nthis is a multi-line comment\n' }}\n"}, + + {"parses an inverse section", `{{#foo}} bar {{^}} baz {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"}, + {"parses an inverse (else-style) section", `{{#foo}} bar {{else}} baz {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n CONTENT[ ' baz ' ]\n"}, + {"parses multiple inverse sections", `{{#foo}} bar {{else if bar}}{{else}} baz {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n BLOCK:\n PATH:if [PATH:bar]\n PROGRAM:\n {{^}}\n CONTENT[ ' baz ' ]\n"}, + {"parses empty blocks", `{{#foo}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n"}, + {"parses empty blocks with empty inverse section", `{{#foo}}{{^}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n"}, + {"parses empty blocks with empty inverse (else-style) section", `{{#foo}}{{else}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n"}, + {"parses non-empty blocks with empty inverse section", `{{#foo}} bar {{^}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"}, + {"parses non-empty blocks with empty inverse (else-style) section", `{{#foo}} bar {{else}}{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n CONTENT[ ' bar ' ]\n {{^}}\n"}, + {"parses empty blocks with non-empty inverse section", `{{#foo}}{{^}} bar {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"}, + {"parses empty blocks with non-empty inverse (else-style) section", `{{#foo}}{{else}} bar {{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n CONTENT[ ' bar ' ]\n"}, + {"parses a standalone inverse section", `{{^foo}}bar{{/foo}}`, "BLOCK:\n PATH:foo []\n {{^}}\n CONTENT[ 'bar' ]\n"}, + {"parses block with block params", `{{#foo as |bar baz|}}content{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"}, + {"parses inverse block with block params", `{{^foo as |bar baz|}}content{{/foo}}`, "BLOCK:\n PATH:foo []\n {{^}}\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"}, + {"parses chained inverse block with block params", `{{#foo}}{{else foo as |bar baz|}}content{{/foo}}`, "BLOCK:\n PATH:foo []\n PROGRAM:\n {{^}}\n BLOCK:\n PATH:foo []\n PROGRAM:\n BLOCK PARAMS: [ bar baz ]\n CONTENT[ 'content' ]\n"}, +} + +func TestParser(t *testing.T) { + t.Parallel() + + for _, test := range parserTests { + output := "" + + node, err := Parse(test.input) + if err == nil { + output = ast.Print(node) + } + + if (err != nil) || (test.output != output) { + t.Errorf("Test '%s' failed\ninput:\n\t'%s'\nexpected\n\t%q\ngot\n\t%q\nerror:\n\t%s", test.name, test.input, test.output, output, err) + } + } +} + +var parserErrorTests = []parserTest{ + {"lexer error", `{{! unclosed comment`, "Lexer error"}, + {"syntax error", `foo{{^}}`, "Syntax error"}, + + {"open raw block must be closed", `{{{{raw foo}} bar {{{{/raw}}}}`, "Expecting CloseRawBlock"}, + {"end raw block must be closed", `{{{{raw foo}}}} bar {{{{/raw}}`, "Expecting CloseRawBlock"}, + + {"raw block names must match (1)", `{{{{1}}}}{{foo}}{{{{/raw}}}}`, "1 doesn't match raw"}, + {"raw block names must match (2)", `{{{{raw}}}}{{foo}}{{{{/1}}}}`, "raw doesn't match 1"}, + {"raw block names must match (3)", `{{{{goodbyes}}}}test{{{{/hellos}}}}`, "goodbyes doesn't match hellos"}, + + {"open block must be closed", `{{#foo bar}}}{{/foo}}`, "Expecting Close"}, + {"end block must be closed", `{{#foo bar}}{{/foo}}}`, "Expecting Close"}, + {"an open block must have a end block", `{{#foo}}test`, "Expecting OpenEndBlock"}, + + {"block names must match (1)", `{{#1 bar}}{{/foo}}`, "1 doesn't match foo"}, + {"block names must match (2)", `{{#foo bar}}{{/1}}`, "foo doesn't match 1"}, + {"block names must match (3)", `{{#foo}}test{{/bar}}`, "foo doesn't match bar"}, + + {"an mustache must terminate with a close mustache", `{{foo}}}`, "Expecting Close"}, + {"an unescaped mustache must terminate with a close unescaped mustache", `{{{foo}}`, "Expecting CloseUnescaped"}, + + {"an partial must terminate with a close mustache", `{{> foo}}}`, "Expecting Close"}, + {"a subexpression must terminate with a close subexpression", `{{foo (false}}`, "Expecting CloseSexpr"}, + + {"raises on missing hash value (1)", `{{foo bar=}}`, "Parse error on line 1"}, + {"raises on missing hash value (2)", `{{foo bar=baz bim=}}`, "Parse error on line 1"}, + + {"block param must have at least one param", `{{#foo as ||}}content{{/foo}}`, "Expecting ID"}, + {"open block params must be closed", `{{#foo as |}}content{{/foo}}`, "Expecting ID"}, + + {"a path must start with an ID", `{{#/}}content{{/foo}}`, "Expecting ID"}, + {"a path must end with an ID", `{{foo/bar/}}`, "Expecting ID"}, + + // + // Next tests come from: + // https://github.com/wycats/handlebars.js/blob/master/spec/parser.js + // + {"throws on old inverse section", `{{else foo}}bar{{/foo}}`, ""}, + + {"raises if there's a parser error (1)", `foo{{^}}bar`, "Parse error on line 1"}, + {"raises if there's a parser error (2)", `{{foo}`, "Parse error on line 1"}, + {"raises if there's a parser error (3)", `{{foo &}}`, "Parse error on line 1"}, + {"raises if there's a parser error (4)", `{{#goodbyes}}{{/hellos}}`, "Parse error on line 1"}, + {"raises if there's a parser error (5)", `{{#goodbyes}}{{/hellos}}`, "goodbyes doesn't match hellos"}, + + {"should handle invalid paths (1)", `{{foo/../bar}}`, `Invalid path: foo/..`}, + {"should handle invalid paths (2)", `{{foo/./bar}}`, `Invalid path: foo/.`}, + {"should handle invalid paths (3)", `{{foo/this/bar}}`, `Invalid path: foo/this`}, + + {"knows how to report the correct line number in errors (1)", "hello\nmy\n{{foo}", "Parse error on line 3"}, + {"knows how to report the correct line number in errors (2)", "hello\n\nmy\n\n{{foo}", "Parse error on line 5"}, + + {"knows how to report the correct line number in errors when the first character is a newline", "\n\nhello\n\nmy\n\n{{foo}", "Parse error on line 7"}, +} + +func TestParserErrors(t *testing.T) { + t.Parallel() + + for _, test := range parserErrorTests { + node, err := Parse(test.input) + if err == nil { + output := ast.Print(node) + tokens := lexer.Collect(test.input) + + t.Errorf("Test '%s' failed - Error expected\ninput:\n\t'%s'\ngot\n\t%q\ntokens:\n\t%q", test.name, test.input, output, tokens) + } else if test.output != "" { + matched, errMatch := regexp.MatchString(regexp.QuoteMeta(test.output), fmt.Sprint(err)) + if errMatch != nil { + panic("Failed to match regexp") + } + + if !matched { + t.Errorf("Test '%s' failed - Incorrect error returned\ninput:\n\t'%s'\nexpected\n\t%q\ngot\n\t%q", test.name, test.input, test.output, err) + } + } + } +} + +// package example +func Example() { + source := "You know {{nothing}} John Snow" + + // parse template + program, err := Parse(source) + if err != nil { + panic(err) + } + + // print AST + output := ast.Print(program) + + fmt.Print(output) + // CONTENT[ 'You know ' ] + // {{ PATH:nothing [] }} + // CONTENT[ ' John Snow' ] +} 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 0000000000000000000000000000000000000000..6a7c942e57561ce3462447a74560c48bdbb3fe10 GIT binary patch literal 13661 zcmZ|019T_Bvp*Uen`~^`++brH-{2eD#>U#%`Nqb^cCxW;+qSK@zkBby|M%WGZ_erI z?&?ofb=CBl>Y1qtS5lBfM!-V=0|P^rmJ(C>S1bLez`^`G$DrL-{;QzOh2@38z-nU= z-wmPv^+}ASROG?HJgLCI{DZ*2UjLE&kHNrPS-`+f4Zy&7Q^CNn9Wq;$`Tq^TJ4gYY z!N3r||EGY1rDx)Rfq^er0yJGT<>h#d?QNM1P3(TMTK*}>EL2%Z%6VUxrRpet}Z{x$o`|~e~Gm!Z|Bh0K! zEX@Ba`yVU+f2h2o_O=dArq0g)^n{-4*JERCh@Tuhz*Df=I8w*S%g|Bv_|S^UiZG5r7Gi2wHTe^LLrSpb2b`F}5& z00P9a|GyQ&b}KC=3~&c;%z{rPSa2;-bXBXauTGc8lg`Ht2ogGP14ePb4=j^X#MBC! zlQ+G@KfeqpoVWLfJp268dymJyQpoIwfwhT3ZVE($g6*e5vJmzUdbGu|d^}0pY%4qa zIQq3DH>C+7Jd8QlxZ!n{?L6f>#mMk-#nee0KY<=!KJb0)_DKKeP5pl0-JgC^NJ^$$ zR^1|+@)L}cSNvUzXepfXp{SF&o+E3If8gW0@9--AZ6ze-4Jyy><=7VS26Hp8@U+vT zc!E#1-FfHJJDB^b-}A0jYgX0&uQh|)=ZJGD3LjqQdYwoL_$nodMOILj#8_U)r^mbU znns-Lc54AOfhd<>vL-20BT-6G_M?uAc}ZOc?Q5ZaUG^hF3!{_r1$=hmm~rRbR`@VC zaeyq^Q!24@`g(aq+=BpEhu)t@r*Mc2va;dY;c#ePQff$6_uZUb+nZ$)Mb-RwS$rD& zTO)}62qk?``cl^}$phESyFhb~hmJas=Q84*o7CxsWFlj88@1hZnJV{hrLX8RtuLI= zrx3vrL3xNT`1Dj;1jI6M3lhB>e5vQh?>$-=G%5cG8X9sndlk}7YLP7xMEyfj{Cmo zN>z*J#0$LXy?0x)vuqwlyJ$f=1BSB^5)(=;9J6Wnl}@JUZ|utlb@Sj$)4g{6q25F4 zL^Su~U0J@z1XGGqRtLMEv6M)&Y*xLVfui6MoiW)kNJ*+T;SshjF|OelIMM`iHSOhi zLbAQ-546CjeV-s#DP2l*dZWBEqM(fxRUe0!POS&JaR(G*`jUz*M98C)N7? z04r-43;$3@&wZce<)O+m?ymo!9l7!WMu>`{TUmW8&eN-CA{wdEp)XW}jMuGKI9%nW zz>p1_-n`9~!gkWI;=sUduZNHaDkSgR`wW`#sCSV2&Sv>neZg6^56xHnK54iS8d!?$ zuJpwT&@nkAFfV>nkv!B+LwlC9)+}5!=@&_$?;0{3g4lni&#aP1+~) zvm{tzMwpqgUBZ@kS#1Kv%U#Lp^Mfhe?|{8XIrFr3lPJ8m$W^)3kct1(Ta$giB>MNa zMp1Odeu}|$O1G;-ANDYg?1IfFG}?`KRuKDC9%0|Kv{sN9VPp$zY1w@y>`K&%70di{ ziT3zsvW}7Z$ zJMkP#Lj_dIh_x$hTq!BKsz9D5=eClZl($!^n`+PE=h3^n#Nj}39keKC-zd*rtws{Z zD);`nk26I4Kh>E`lrSA9+6sCQqiZ1Dli=Q1naYk)#TIBfdLji~7fm{oTz;$rMEsH5&Bua5&IDCdxdVWz>Rkw1! zzYjeCeQyRz$4(lH?|r&Is-1;{F%&khp8)0GjY;NYJ(6V^_fJIB%OA*DbpQ5yEd&zQ zKT-`J*PJ2fkRUZX_t~x7<^+LE?zo&RTlk^ft~jmb$AaNuw*JpC zRB6#Ipc596v^J76B^Jm#j|A;oO1GT$B7nLQxsm1?A}m6h4CPFsH-6M;kp(Tn1XX;e zC40YKe8fzbmL(Q9uEU&xQ8CKI#ORC2vD&9AQQ*s!mb3emri=|e@Foo{%`q#Jj$TTx zg)d^@d@WkeyJA$jq@h9}6EDE(sMHqfjn~lad+%fAXVgpsi!MuU?#ryTUS0)$uxl(q z{P4rkp64`-`wrORJJ__rhw61OCykT!iP?qe3 zbQpYEmb47(m55I#g*p3h56?)?=H5WfIUa5bHgzPM>vQh2zK>9QbEkF?JX-=!H2~wj z(0I{H>-U=qAT7+>598t;B|i&NCy}1%wbEiz{3+3sq2=ROAZFYXWng=Ou%6n-3$}V! z`#MO47_P47=6s0Q#Daa+?~oVR%s@)`fL7Ym6-(+gPdONp+?t-+(71^Ih@3<~3-LF& zSL%lnO~N-d$Pj^iUop1Q_N|}Jry#~b5S-nv=dXq@&M_+n^m(K81 z79ApW_@ozXr?qX*yf%af9!V6L<5zYQT=mJM6uY=2+eQ`pwpoO0#%Na+RoIN*p*Ruw zrMUTNacVfpF2V-zWk~eUeY@A36=yOimHa8=!}BhyH4a$oas@iL=|L0LRtLk+4g9Q` z3HRH+BR<9n?qy1e(T(Gxc$rYfOoDpPoKs^pw{~6I9a}hS#Rdm-L4ek4LtO~SnUr^d zsF>=s5&AWcIDmhtYKP9-Hg2>(P^8Um8@;XWuotnQ9@BV(HJNlpi0C{62j%Ahc zeprzOT~%{yJdPH8moA0+rv)LTc35CJ5t;q7mP<<4U`-vFqckw8n=K75XPUmnFOb3F zMok6ZIXGrvEr(gH_)zaaYpePk5!1szoC+*;U!Na){UqDj3|be8LNtC6Z40KxZ_8(3dLpzDwQK(_*e)3cSv{Rspte^`y5dt;JKx>T^+*>skPFrdLOvphu zIIn$j9Z1-S6KP>j&&e-9$>v;_#bC>#Yx}dcDQ;pgpQHG--d`x1q8#yVY{ZjeQ$K@+ zZ!bTlvtzxxhUKWI);mStN3RM}9(SxqP=IFfE~CI_4`w;CxJ2P64JU1Iuu_+p*gITP zZmW%9%;28CyhjMcP)UrKcOskucXpTp0)=+uud34MH?(DaQ*+iF$d-oSEZq`K(q5fJ zQ=02v5Z{Tumlh@B<|;#|)r|MmvV1~o%5|{O&k)_;~9wlDMEAEMZG~DJOcGJ%c8}n#AZC9mUHocU$DKe8ReOxP#b9LM!<-$+`4* z*&8|8-49OE5*#X!$N6Se&~zZLmIr@byH8Rq|Cx0^X{PG0v%(KL+;1fXIDL?{!s&w_ zw>+Ez2G%C&b$xKOVSa>;Mz;qDF~Q0qq^}lb30`u4{IMA z@2l7CJ~d$BNCY+meH@z6u*AWbbCq=B@)EKcX_87b1JqiW&>EWVOW2}5eO{I57?t58 z`VPk&6_XHk1EGkKR>A^8LU_O72oIJH1c)flNg4-cmeXfW{{4-DWxm8kMPnUTKIq;8 zAg{wK8a4J=5t$in3a_Hh1lK)cjim$N#IKF_#!!cT;DBu!9ouEU{>Emyf3z|$;)?G` z?9;);!$4LwP3pa^sY&pEjfn4Eet1ry-xpE>?^+y`uP;dm_5@fZq7_$YLKqZEU45Ohc*N2BlO6dUC@7ZT69)D1@bjOpQJDNH^uB0m>;lkvKcqtWxl z;kIQ`6%lk4N|8)T;r}>xlGV|p%Dx(qMa^Cdh18YP;T86iVUz&MwHj<3B+=lZj6Hmo z`{4~q$hQ*tK%nid1M2>>$Q2;%A8(Fb$H$MYMA@dV*}qZQqpG6eh{i>uw4-D{M9mz> z;d6`~3K?@nObr8p{;e})G78{b^$W7{ASh5pM3hnoPi9y675vcl zF@=2l=^j z@>=j?QuV2sifq8m;OUWrMht0PE8;fusQ2HyS^&Ji?6FZ3;J$hrSe=CV_)URO#gkUb zDduPtrU{2u0Sp`u0a4(?+gUgdyjWV$$mQlF?v@X%?(7(a7?3q-VU+a1trEZ6Vz#yU zusK0`APqAr=S1NgiE1Nleb7!hSR08KN-XJ6BeLtCEJu<kSt{PFz7lqw= zKf^7n4t<_`hsm)u)0J$1R*hFG|eBQb+aPkkmx-%hp-{X4+;syzV7G^{i^U9yjt z$&2!CBV1aTS$Ky719AartPbpOcTzSj^IQ!zG(i>htp4PV{1N=4y-7iY!PG9FB2X&f z-6dQx&oo5)cx(~1yxB8D zgBR2n4_eBrmw*DmYw-T=0D?Od$Abu_e0)uIevU~LPxv^mE(=@AQt>ppVN7j|EaU!7e1g2Bf zPg@$&@b=kjfG{-w?d3rq`L1d0ff7vXAuC@Nh{mbZ85lV49~P2Iezxc`Hh}*|0l|5( z$xgHOh%`o$=S6%m?RA)@vNX?x7U$~iErLRf3j)=$;{Ft1Ea$ad1YFz1$vK|)b5sbz z{y7|kEzGmb`tbk-1Q#`J{M`Z;+>6nTO(J_`3Dgqqbda(ClE|Jg-8Ym1*WfPG$c1YjhDZRQ?-BM0ZXm{V=?UGK%p0 z!9`4_bI;4{>^en0`4A<~Px2ENiEKZk>L^7DQ(M2#`8;BT;VI+3noAJv$)$D5xVuXB z7B@q}a2?FPZCGkDjDDjc6EYYU=*&HFIw)NssO|QJbp%O=zRxv?3k+OXO;{F85iEzO z5vqnUSU=~5G?1Qc?9bKM6JOQE1GzO`SnF}Y@jf6aH-CF^*19#YtdHh5+3M5!ZVWlBL?u_Qv;?{n`H%*7U&y0U!4(QeJD zn%>aJWsGuALqgIr1)13ev{M+1AJR+7=;i(BtS1Rz%}N5-(xcxB#^Fr1+%=f^;tvxz z^(swj+IC5Gd3N-6>pMhjASCV;uwIYp@6H~&-+`AC{}$!q~OR5*O@-ZCii(NglF*z-Ni_oPxyXzE>t{@~7ptyCTsLC$hcaNR`Fb@)zW0oHMsac=lJ%f{Se2NUnaRrqVZbmdxO^c zcMb5vK-h()*FW2>-hd{!J!l)CPjW@e?i1zB!IYHY#=lK36uzk|a(`02##48Z4;v3C z0HOFAa4mvB$rED6Xcj-awMG41b&f3pes71|8cr4x*^X2eikggZW1=pvOQR^EK7M>M zAPg$K4PFd=V?gr3kE77nG~)XE#3uE)BEi^1%cSIQACb+WeUyq02m z^BZWBOC8uwH9}@Qg1`vuy`h&4EQTx2kwJ824J)6+L+2N#G-OV|^ zDM^bw%4E@qXP^Jn3)@eiP)%`RDJ2tqid(ux{owtd8z?;~oIZCHWBh{LI2k;L?)tEk zR~7_PzT~7CWsZk3vOiHNK|q5gFmMDb6gO7HNYQaQkyKw-hSp?)WVze0D7SX#3sIid z@?<0wyIo?>8p+L5LdBDnq13P+#j~^Vr9fTaJT9kR|C|qBbfczRs6{x#J<~vb^+?4C zN$6wMKhRL1t8~S3VOy5Y{Y4CCa(Y-SL9g}&-2vD_oGj0oL4aC~5ZZMKUCNGC%mA52 zGZYxlzg7&&Pi*HQp~?ol0rxW@fE4ExsboCKRBl~0ifkk-APW;swD9fKg>PZDWtEWN z@bF8VBa4J@CUf7@yBSy~k7A1OhsdQ#C6eNVG$>c}Pv<0*sFp@W+>o^2+TFSWaBU%cSLr2%{_*soFXP0Sz!2(;&v4r z4WG}7i>FdKUs}2&b=3Ow)g>**DUSmpxq-lcREP zGUum9?T%2K45GlbCTl9rb6?~%XvA6tEN ziHKh=wU!1sRV)=U<(QKTU77>OuRSac;CCM9l2<6}5DFvcnkU@h*p}KLZR^$`hRn`Lb(L& zw+gJ(Lf;n0f@&}Zw;$zL9spsyxHhtvr8l8=2rq?@$sK(@&6xGX{+EG7V+9Qb6DNA< z*8+){J(Fi2x8{Xrmw)$}+Vs+Y{iL1|&A7o5VH3j{ySoc2|5MjmDb34i1)+U*s)HC1 z$V5~AeNi`Lc6#6`(@+}uB-SV_Hh#7U@dz!g+l(MBEopCf5olum(<5Y?*pq|{m>xHX zsX^*iYkN{Zh+{A;5* zLwm9R*n+b<=S)F?CUy+wNLU$-B#pg0qUCQS(?70q(vB1x6CXsBOcORKB%i>*7Cni1 z0CvtN5=R;tnn0~H$KK=Ka7yjp8#m9=v6q?S)P+{Fe7QzXYR0Y@Avb%5H=!V{^ zO)GUV?%@}@ZN60Vo`Y?b?iSD}1#I=5d1Z+p71#Vzmy`NiFqaU*63dvzo%fa$>HE<$ zO23sg3eO!iB@=b4*z>rU$A!AgX?1-sdNYNfU`LV1s7km-Q-c`*U8HUbduJ7uI+oC= z96Ds?!xq%1ozHJUU5IX5Sp)q;VKb-#D=tx8=g|8qO-Aw{ZtC3b>B9AS+PvgZ;Yu4k3+BY_ ze?f|1Oc8r&VMlnBgjL*W#(hdA?N}5s$2BjJOOGiWq--hhS6l={rM???HZNb>UDe`$ zKtgxY4pxf$K0yQep^PVwJVSAZ*E?%fCN><+g^T(t)7@CF=sJt>G`hn{j>+fgt)RoY z#pZJ=RPOL}o!OVc)p`=E%a1VR$9u6s@VTLp_4mZqIZ-^mPW}_n&MoMk9gj4)H#IM zq7Ds76r7B^It`{RhlHYLuySt;P8r>fd!JH;HwaQL4x}V_iiAKVoRx29F=MXI1mrZN zzJ)N&6GE74H>98RAc|*Gkb1{`7|WonejgyobJFu6T(9;tNrzhUM1skiD=gEE6u*@p zX&x3BuGFJfUFw))HhFwwSLi?r@W0`xh?LyvGkSP{?^RJE2j`y}do6WAx9WqF6us$y zB3sJ#vv5(+u&`dKBQ;aEsGKn(pJE>3flGeK5|>jdS31 z+-=9slGQ!iAGdt5k<9rSao~5X629eEELL31VIG7ETY@xmkn2Ig2zFoYK&I_(GZJ{% zhQzOdrA{YC_xx1r?T2`@u38KPVU8oXZ7G#DEcMWieZz_V_CC%ZCFjJ>hI^7wiblqg z{uNrAIaAZNkIs7dZ!_@9!S{J8{^K@h>^72#G8HR+hBoH{rNBxE-fGO&;{cA{ z2D$&+h>qztV}V%-m^_uL6G6_?@iE4XX>ZB!umn~X2aT7>mhj6p^(Xqo8mgOzlg)7r z?!P1lIeGbXaf*%VBl*~HwK)o3?Lha~4dfvtf7gI@*AWBE+o+jh3zw}#mU}g%R5&hD_KuvTkqE3(;{GD_+*jRdnzue zJ99s8)%)F+tjU~PY3Pe^w}I^yLK>gI@NXpv^mLBNE`~HM$>K3>?7CF;`jtQ%2?{yX z;^5(5m3^7(5mPr2Uvi;AOz&ddiMfcOORI@h;)v*~n~($XF(Sm99EI#P28 z!g}=6W6nC3+l#4i=OH(W_mpPooiexZwMDyRLs1$*-PaPO{3fp6OujEUI|BumF-Wh- zq~IgjS}t#m9|ykcyT)iHCRR2s@R%@C2r+l^Jf$Q|xx8jN2N6GA03wR~44+@0QY|n& z@;!=8=QS=akHdxbE$W|lp>8{R!6Vb7xPnb+i?$|L4u9%b#Ej=fNnaw8XK=Wsn#bY4wOD-9_K@7*?Uj z4End4A0@X)CsPC%AEs!uFjLqK(&vb^k|E6 z9|)MIsdUd3NIfoE*(yXgNRAPEqh!zpU!w}}8!w#jAMbo-=@poeWbCSNx5_ub@@e!z zFM|W@(eSZ>6y5L~FAu?(ZQbrRgzST@asQfm#-SRy_J|c z?&)a$MnY56DY?2)-(IFnU4e8dlz%a5PY(8{ydER``IcWX|}Fg#rMQ)c8#fX@&W;|AkDS5QMoev_i4=od z$fgzq{XnDD0n@)wGvi?6BuDfzKy{JsW>M8ZC%>OF%E~?iPRLXOGojmHpj-)AF9T-< zs2|i5@Ym?K3vBBIM4%Ek-1*%+?f!b&q{7cg2smeF-Zcjg@H~3IOWN({V}8xQj=b!c z-CpnKMvFUUU^^xOWLRlt1_ocz?oo@eh%)?6>a8L3RO>oae>-NYIC6?k~mei{yiBHtOE5W4&Qor=Eunsw241JCp$N5^ca9kdsc|C zLH2UyWNf-tznzI;PN*}ja3A3#3QQyTKn{urdL!F4vPWZ784Fa&SGwCFt+?kbpYi>M zprWBMIP(|`)6{p&T}E;xjl=r@GDYkuV}@Wo_Z{&%qXUGO&#a5v+eJonYYMb1`Jk?s zaK*7C4PAP|4U=Z0HCor8G&oURxFt)I!#za-Gm=>(jlv%jm#AKb=aQn|4|M~$)C*1* z5eAaVSJmzPwKi~-iqu_nNUwn3PinT@)CbdvzM@Xvo7QTy?8;p%E*bKKI*Rd^zs0}Y z{o5sz?AH0ro6NK+LL7C%jpRA2)-8`TDra)d0Z^{K54 zA<2an^=2aC(M)T(yah&6p@?R+%IAS6&+j?&{TiN|`pL}4qS*O-#ytk>cV;F>#6^Q{ z$@;AvDv;LY?hl(We67Rwwm{3?<&~6%SMxZiwlJ~qyu2-ETmZv|x_U_(pej9-@1&Fz zZue?N|Lsa5Q;3+!>t5>V&b8hoJS$I+<1;OMW6C}u!>%rcOmRn3F2GZAY(b(77<~6< ztGeZ*&Hk>L|FtbI^lF}g%jqB>-#WTf{kOQxetDu9)%F`(o2FQbt^aM5jp1}eKLVK= z*NIT$af@Am5|kCpZzhCIudR+-^PD#hjPM(|mz1_HX72VU?tEi$vI_;D_;PB9I-N5XMmxun&JgmNlF-wI z`an?o;~=17=41~_^D|7Ao!oQ5y6*O=vdNy2cBb#Ja*v`Vf9!)q1(Bc4{Xwuasl>zqQVJ<)~QhNGtw zlvS3O-&{iZj>qreP1w(S3GVnGhr}!2;U^RfheY7<_(E+PX}sw8(pC;qpO&+NY-YSh zYhH^v?|H-e7M8flj-{8n`IWaG_Htf=ewhZn+R({sT{+?&Ytv^CeW1R)Rr>{hzsHd!K0LF>jyae~sWa<~PbW3*_@4^p#>hTR^Ef~;A6=9QSd6vcvQIr|o%!1} z6}v-s#UZv$R7%d#FRlN$srHlo99ckNRc&Nf&{BvNs_a~W8+W+8?BYAEBKRwu{G_-_ zxiGw(`)&b~L{&-ddqiukcg^sJ_CI|M{-&#c{A32A8s^ zIKz~LBbht2u*+WPb4UA&nR|Rxy%{slo||F8Dxtenhgs`oP5Qq4nvI>uVp*41T}O12oHmT<$vxQF=Nk zt?*#GGrRo>I(|tJj0SfL%=!09Iap+wbx@HlkX@>bgX=sm*B^>8_r7m9H}|<9JB)wla5l+j+LWI4O+Pzq_)_|dl$hhp3O7Y zQ(oFFxZoYqh*N{$_Ad_z`Sv!FIj{t;<+}auAnFsG$N3_y&gsDFu05o2^G`+svbiqB zQrFo}nuMo#{%la*j$wiaOLTGV$xf);#<#=*uX6}GcA#ea77es&U=Owi*luMpqF|eV}!E-E-Iq`L}ZT9SZ!z>iO zj@;vhYB^6W+qOwE14x@+KUgtwg?sB-+_^{=Y zamMLz)pgdteI8!3>yyDLPkY1*H^r8(X-rqJ6+f_TBMe+T`xCmZvOS=!7vlaqziOH4 zqUjb9;_^AK2-RQPthII5vrQFmvpJNqvPB-oedVbrU~I8=ZvS0h3*!^vgxvm_A>j;h{K@1yuXP@SgFT^VNmkN=3jKR+)oSAE2T6kBr35>T~thKZ+|mw*~`8n zLFA(po!8p)p{uv@;DR3Dd~%J~mnvf>8mISq9Dcfb1K*l6stlxXsSjRkmrSc4Z;klz zi2?MEh{V)6z^IVyB<~{#BAZ=IbN>F|m%Q4wSMC(1S!lIpo1u?=i{V&I57}tv>KJW0 znRWhYs}JgQO?kQa84H{VvTcE8vy8?A{QIsbj6z25y5eMJH@oGaAP62LL7sx4EC~a9 z@c8w%R8;k120>O*S}RUf49gldw6c=~S+W?hlQBn&q zLJnCbW{_S?DK_qVMoL<3Bf5KA)HjPOj*iNDI$c#S&#GuwhBBhYTa#inF^?lW$?I5p z9rLtfY;;8if__L(K}XT=#ax5qFWNbv(mgk*KR)ihC9gQTSc!BxKM|VQY!HsEl*Pu5 zx8ELUs&IN9z;1oT-Me6Tu*Ca+m9kHI4Uep_sU((x>R z)?UtZ&!^!HvKdvTALq3sE%zmaGpUAbqFZ>!5@^Q><>W-$&66YsdHH%)-#=C)&fJ81 z{82-&BKv7vBy@`-k!Ll7ftE?gd`VGiA*Sxa#2tPT!SSIg!!9M7r>rB-ypwMhGhvd9 zw=Y zFZadN(nQ>J6z_w>LW&Hbbic+d=C&N(Vy}2?-*rnlh9X{pYvhO`QJF4`w?AI57-S7S z$@|MD&K_Q2boe>dX7je-@t^IhUQAorj&L1iOJNW<{3>5@y?9Q6y&y-}l>)a=jwV0q zkMknZm0~gTl9)yNDAUHk(_8(*?$b^H9CY2xq(GYojkCki5j(byuW+gP!9?Dp>C@4k zt19|c!|GZJ&)f*^kvXbB?$wiaMutHngJm!$kynI|KyIMrei>m2S?|`%?3k00)4HI; zbKKYT;n($QhSthJk`tzOo=7%M;889a0e5nZj69>Nuk+i-KousjRQ6L0qKa3uk3jjb z=saM*NktcE;B>3cXpau9j%=)#T>`0~^hUi!NyF@RmZRggXN61DLiHV9|7JE%6kFhB zoc*IAs%9NOLi{C%d;98kf~gKh(^0e~2&nl-^MnB3p$^x2EvC}bcE|`Ge?%e-@jFd6 z&9{PO#f(Z{)wS>Hsz-aOv763H^rMke$IN?w!y%n6^#<>#QiS9?M>f>m+_a@8zudH& zY_%Ee1@1@Et3H@3v$FB7%sDSI|K)O;TL7P4|77Shj^I8Hn~@{m)NrynNqt`D6F)Jx z?AP^TcRoJc2jqPZZMCu2Xw9>Qn!6OQF17V@GTqaA*?daPe=#zgGfaNRV|!}ij$S?V ds^)$9Dk#_i;U!=Y|K}$rX>kRyDiMR<{}%w$L^%Ke literal 0 HcmV?d00001 diff --git a/vendor/github.com/aymerick/raymond/raymond_test.go b/vendor/github.com/aymerick/raymond/raymond_test.go new file mode 100644 index 0000000..bd34eeb --- /dev/null +++ b/vendor/github.com/aymerick/raymond/raymond_test.go @@ -0,0 +1,115 @@ +package raymond + +import "fmt" + +func Example() { + source := "

{{title}}

{{body.content}}

" + + ctx := map[string]interface{}{ + "title": "foo", + "body": map[string]string{"content": "bar"}, + } + + // parse template + tpl := MustParse(source) + + // evaluate template with context + output := tpl.MustExec(ctx) + + // alternatively, for one shots: + // output := MustRender(source, ctx) + + fmt.Print(output) + // Output:

foo

bar

+} + +func Example_struct() { + source := `
+

By {{fullName author}}

+
{{body}}
+ +

Comments

+ + {{#each comments}} +

By {{fullName author}}

+
{{body}}
+ {{/each}} +
` + + type Person struct { + FirstName string + LastName string + } + + type Comment struct { + Author Person + Body string + } + + type Post struct { + Author Person + Body string + Comments []Comment + } + + 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 + }) + + output := MustRender(source, ctx) + + fmt.Print(output) + // Output:
+ //

By Jean Valjean

+ //
Life is difficult
+ // + //

Comments

+ // + //

By Marcel Beliveau

+ //
LOL!
+ //
+} + +func ExampleRender() { + tpl := "

{{title}}

{{body.content}}

" + + ctx := map[string]interface{}{ + "title": "foo", + "body": map[string]string{"content": "bar"}, + } + + // render template with context + output, err := Render(tpl, ctx) + if err != nil { + panic(err) + } + + fmt.Print(output) + // Output:

foo

bar

+} + +func ExampleMustRender() { + tpl := "

{{title}}

{{body.content}}

" + + ctx := map[string]interface{}{ + "title": "foo", + "body": map[string]string{"content": "bar"}, + } + + // render template with context + output := MustRender(tpl, ctx) + + fmt.Print(output) + // Output:

foo

bar

+} 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/string_test.go b/vendor/github.com/aymerick/raymond/string_test.go new file mode 100644 index 0000000..eaec630 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/string_test.go @@ -0,0 +1,59 @@ +package raymond + +import ( + "fmt" + "testing" +) + +type strTest struct { + name string + input interface{} + output string +} + +var strTests = []strTest{ + {"String", "foo", "foo"}, + {"Boolean true", true, "true"}, + {"Boolean false", false, "false"}, + {"Integer", 25, "25"}, + {"Float", 25.75, "25.75"}, + {"Nil", nil, ""}, + {"[]string", []string{"foo", "bar"}, "foobar"}, + {"[]interface{} (strings)", []interface{}{"foo", "bar"}, "foobar"}, + {"[]Boolean", []bool{true, false}, "truefalse"}, +} + +func TestStr(t *testing.T) { + t.Parallel() + + for _, test := range strTests { + if res := Str(test.input); res != test.output { + t.Errorf("Failed to stringify: %s\nexpected:\n\t'%s'got:\n\t%q", test.name, test.output, res) + } + } +} + +func ExampleStr() { + output := Str(3) + " foos are " + Str(true) + " and " + Str(-1.25) + " bars are " + Str(false) + "\n" + output += "But you know '" + Str(nil) + "' John Snow\n" + output += "map: " + Str(map[string]string{"foo": "bar"}) + "\n" + output += "array: " + Str([]interface{}{true, 10, "foo", 5, "bar"}) + + fmt.Println(output) + // Output: 3 foos are true and -1.25 bars are false + // But you know '' John Snow + // map: map[foo:bar] + // array: true10foo5bar +} + +func ExampleSafeString() { + RegisterHelper("em", func() SafeString { + return SafeString("FOO BAR") + }) + + tpl := MustParse("{{em}}") + + result := tpl.MustExec(nil) + fmt.Print(result) + // Output: FOO BAR +} 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/template_test.go b/vendor/github.com/aymerick/raymond/template_test.go new file mode 100644 index 0000000..c194b28 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/template_test.go @@ -0,0 +1,166 @@ +package raymond + +import ( + "fmt" + "testing" +) + +var sourceBasic = `
+

{{title}}

+
+ {{body}} +
+
` + +var basicAST = `CONTENT[ '
+

' ] +{{ PATH:title [] }} +CONTENT[ '

+
+ ' ] +{{ PATH:body [] }} +CONTENT[ ' +
+
' ] +` + +func TestNewTemplate(t *testing.T) { + t.Parallel() + + tpl := newTemplate(sourceBasic) + if tpl.source != sourceBasic { + t.Errorf("Failed to instantiate template") + } +} + +func TestParse(t *testing.T) { + t.Parallel() + + tpl, err := Parse(sourceBasic) + if err != nil || (tpl.source != sourceBasic) { + t.Errorf("Failed to parse template") + } + + if str := tpl.PrintAST(); str != basicAST { + t.Errorf("Template parsing incorrect: %s", str) + } +} + +func TestClone(t *testing.T) { + t.Parallel() + + sourcePartial := `I am a {{wat}} partial` + sourcePartial2 := `Partial for the {{wat}}` + + tpl := MustParse(sourceBasic) + tpl.RegisterPartial("p", sourcePartial) + + if (len(tpl.partials) != 1) || (tpl.partials["p"] == nil) { + t.Errorf("What?") + } + + cloned := tpl.Clone() + + if (len(cloned.partials) != 1) || (cloned.partials["p"] == nil) { + t.Errorf("Template partials must be cloned") + } + + cloned.RegisterPartial("p2", sourcePartial2) + + if (len(cloned.partials) != 2) || (cloned.partials["p"] == nil) || (cloned.partials["p2"] == nil) { + t.Errorf("Failed to register a partial on cloned template") + } + + if (len(tpl.partials) != 1) || (tpl.partials["p"] == nil) { + t.Errorf("Modification of a cloned template MUST NOT affect original template") + } +} + +func ExampleTemplate_Exec() { + source := "

{{title}}

{{body.content}}

" + + ctx := map[string]interface{}{ + "title": "foo", + "body": map[string]string{"content": "bar"}, + } + + // parse template + tpl := MustParse(source) + + // evaluate template with context + output, err := tpl.Exec(ctx) + if err != nil { + panic(err) + } + + fmt.Print(output) + // Output:

foo

bar

+} + +func ExampleTemplate_MustExec() { + source := "

{{title}}

{{body.content}}

" + + ctx := map[string]interface{}{ + "title": "foo", + "body": map[string]string{"content": "bar"}, + } + + // parse template + tpl := MustParse(source) + + // evaluate template with context + output := tpl.MustExec(ctx) + + fmt.Print(output) + // Output:

foo

bar

+} + +func ExampleTemplate_ExecWith() { + source := "

{{title}}

{{#body}}{{content}} and {{@baz.bat}}{{/body}}

" + + ctx := map[string]interface{}{ + "title": "foo", + "body": map[string]string{"content": "bar"}, + } + + // parse template + tpl := MustParse(source) + + // computes private data frame + frame := NewDataFrame() + frame.Set("baz", map[string]string{"bat": "unicorns"}) + + // evaluate template + output, err := tpl.ExecWith(ctx, frame) + if err != nil { + panic(err) + } + + fmt.Print(output) + // Output:

foo

bar and unicorns

+} + +func ExampleTemplate_PrintAST() { + source := "

{{title}}

{{#body}}{{content}} and {{@baz.bat}}{{/body}}

" + + // parse template + tpl := MustParse(source) + + // print AST + output := tpl.PrintAST() + + fmt.Print(output) + // Output: CONTENT[ '

' ] + // {{ PATH:title [] }} + // CONTENT[ '

' ] + // BLOCK: + // PATH:body [] + // PROGRAM: + // {{ PATH:content [] + // }} + // CONTENT[ ' and ' ] + // {{ @PATH:baz/bat [] + // }} + // CONTENT[ '

' ] + // +} 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/aymerick/raymond/utils_test.go b/vendor/github.com/aymerick/raymond/utils_test.go new file mode 100644 index 0000000..cecac37 --- /dev/null +++ b/vendor/github.com/aymerick/raymond/utils_test.go @@ -0,0 +1,51 @@ +package raymond + +import "fmt" + +func ExampleIsTrue() { + output := "Empty array: " + Str(IsTrue([0]string{})) + "\n" + output += "Non empty array: " + Str(IsTrue([1]string{"foo"})) + "\n" + + output += "Empty slice: " + Str(IsTrue([]string{})) + "\n" + output += "Non empty slice: " + Str(IsTrue([]string{"foo"})) + "\n" + + output += "Empty map: " + Str(IsTrue(map[string]string{})) + "\n" + output += "Non empty map: " + Str(IsTrue(map[string]string{"foo": "bar"})) + "\n" + + output += "Empty string: " + Str(IsTrue("")) + "\n" + output += "Non empty string: " + Str(IsTrue("foo")) + "\n" + + output += "true bool: " + Str(IsTrue(true)) + "\n" + output += "false bool: " + Str(IsTrue(false)) + "\n" + + output += "0 integer: " + Str(IsTrue(0)) + "\n" + output += "positive integer: " + Str(IsTrue(10)) + "\n" + output += "negative integer: " + Str(IsTrue(-10)) + "\n" + + output += "0 float: " + Str(IsTrue(0.0)) + "\n" + output += "positive float: " + Str(IsTrue(10.0)) + "\n" + output += "negative integer: " + Str(IsTrue(-10.0)) + "\n" + + output += "struct: " + Str(IsTrue(struct{}{})) + "\n" + output += "nil: " + Str(IsTrue(nil)) + "\n" + + fmt.Println(output) + // Output: Empty array: false + // Non empty array: true + // Empty slice: false + // Non empty slice: true + // Empty map: false + // Non empty map: true + // Empty string: false + // Non empty string: true + // true bool: true + // false bool: false + // 0 integer: false + // positive integer: true + // negative integer: true + // 0 float: false + // positive float: true + // negative integer: true + // struct: true + // nil: false +} diff --git a/vendor/github.com/matrix-org/gomatrix/.gitignore b/vendor/github.com/matrix-org/gomatrix/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/matrix-org/gomatrix/.travis.yml b/vendor/github.com/matrix-org/gomatrix/.travis.yml new file mode 100644 index 0000000..fadc326 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/.travis.yml @@ -0,0 +1,9 @@ +language: go +go: + - 1.8 +install: + - go get github.com/golang/lint/golint + - go get github.com/fzipp/gocyclo + - go get github.com/client9/misspell/... + - go get github.com/gordonklaus/ineffassign +script: ./hooks/pre-commit diff --git a/vendor/github.com/matrix-org/gomatrix/LICENSE b/vendor/github.com/matrix-org/gomatrix/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/matrix-org/gomatrix/README.md b/vendor/github.com/matrix-org/gomatrix/README.md new file mode 100644 index 0000000..ea9109a --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/README.md @@ -0,0 +1,6 @@ +# gomatrix +[![GoDoc](https://godoc.org/github.com/matrix-org/gomatrix?status.svg)](https://godoc.org/github.com/matrix-org/gomatrix) + +A Golang Matrix client. + +**THIS IS UNDER ACTIVE DEVELOPMENT: BREAKING CHANGES ARE FREQUENT.** diff --git a/vendor/github.com/matrix-org/gomatrix/client.go b/vendor/github.com/matrix-org/gomatrix/client.go new file mode 100644 index 0000000..90a07c6 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/client.go @@ -0,0 +1,703 @@ +// Package gomatrix implements the Matrix Client-Server API. +// +// Specification can be found at http://matrix.org/docs/spec/client_server/r0.2.0.html +package gomatrix + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "path" + "strconv" + "sync" + "time" +) + +// Client represents a Matrix client. +type Client struct { + HomeserverURL *url.URL // The base homeserver URL + Prefix string // The API prefix eg '/_matrix/client/r0' + UserID string // The user ID of the client. Used for forming HTTP paths which use the client's user ID. + AccessToken string // The access_token for the client. + Client *http.Client // The underlying HTTP client which will be used to make HTTP requests. + Syncer Syncer // The thing which can process /sync responses + Store Storer // The thing which can store rooms/tokens/ids + + // The ?user_id= query parameter for application services. This must be set *prior* to calling a method. If this is empty, + // no user_id parameter will be sent. + // See http://matrix.org/docs/spec/application_service/unstable.html#identity-assertion + AppServiceUserID string + + syncingMutex sync.Mutex // protects syncingID + syncingID uint32 // Identifies the current Sync. Only one Sync can be active at any given time. +} + +// HTTPError An HTTP Error response, which may wrap an underlying native Go Error. +type HTTPError struct { + WrappedError error + Message string + Code int +} + +func (e HTTPError) Error() string { + var wrappedErrMsg string + if e.WrappedError != nil { + wrappedErrMsg = e.WrappedError.Error() + } + return fmt.Sprintf("msg=%s code=%d wrapped=%s", e.Message, e.Code, wrappedErrMsg) +} + +// BuildURL builds a URL with the Client's homserver/prefix/access_token set already. +func (cli *Client) BuildURL(urlPath ...string) string { + ps := []string{cli.Prefix} + for _, p := range urlPath { + ps = append(ps, p) + } + return cli.BuildBaseURL(ps...) +} + +// BuildBaseURL builds a URL with the Client's homeserver/access_token set already. You must +// supply the prefix in the path. +func (cli *Client) BuildBaseURL(urlPath ...string) string { + // copy the URL. Purposefully ignore error as the input is from a valid URL already + hsURL, _ := url.Parse(cli.HomeserverURL.String()) + parts := []string{hsURL.Path} + parts = append(parts, urlPath...) + hsURL.Path = path.Join(parts...) + query := hsURL.Query() + if cli.AccessToken != "" { + query.Set("access_token", cli.AccessToken) + } + if cli.AppServiceUserID != "" { + query.Set("user_id", cli.AppServiceUserID) + } + hsURL.RawQuery = query.Encode() + return hsURL.String() +} + +// BuildURLWithQuery builds a URL with query parameters in addition to the Client's homeserver/prefix/access_token set already. +func (cli *Client) BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string { + u, _ := url.Parse(cli.BuildURL(urlPath...)) + q := u.Query() + for k, v := range urlQuery { + q.Set(k, v) + } + u.RawQuery = q.Encode() + return u.String() +} + +// SetCredentials sets the user ID and access token on this client instance. +func (cli *Client) SetCredentials(userID, accessToken string) { + cli.AccessToken = accessToken + cli.UserID = userID +} + +// ClearCredentials removes the user ID and access token on this client instance. +func (cli *Client) ClearCredentials() { + cli.AccessToken = "" + cli.UserID = "" +} + +// Sync starts syncing with the provided Homeserver. If Sync() is called twice then the first sync will be stopped and the +// error will be nil. +// +// This function will block until a fatal /sync error occurs, so it should almost always be started as a new goroutine. +// Fatal sync errors can be caused by: +// - The failure to create a filter. +// - Client.Syncer.OnFailedSync returning an error in response to a failed sync. +// - Client.Syncer.ProcessResponse returning an error. +// If you wish to continue retrying in spite of these fatal errors, call Sync() again. +func (cli *Client) Sync() error { + // Mark the client as syncing. + // We will keep syncing until the syncing state changes. Either because + // Sync is called or StopSync is called. + syncingID := cli.incrementSyncingID() + nextBatch := cli.Store.LoadNextBatch(cli.UserID) + filterID := cli.Store.LoadFilterID(cli.UserID) + if filterID == "" { + filterJSON := cli.Syncer.GetFilterJSON(cli.UserID) + resFilter, err := cli.CreateFilter(filterJSON) + if err != nil { + return err + } + filterID = resFilter.FilterID + cli.Store.SaveFilterID(cli.UserID, filterID) + } + + for { + resSync, err := cli.SyncRequest(30000, nextBatch, filterID, false, "") + if err != nil { + duration, err2 := cli.Syncer.OnFailedSync(resSync, err) + if err2 != nil { + return err2 + } + time.Sleep(duration) + continue + } + + // Check that the syncing state hasn't changed + // Either because we've stopped syncing or another sync has been started. + // We discard the response from our sync. + if cli.getSyncingID() != syncingID { + return nil + } + + // Save the token now *before* processing it. This means it's possible + // to not process some events, but it means that we won't get constantly stuck processing + // a malformed/buggy event which keeps making us panic. + cli.Store.SaveNextBatch(cli.UserID, resSync.NextBatch) + if err = cli.Syncer.ProcessResponse(resSync, nextBatch); err != nil { + return err + } + + nextBatch = resSync.NextBatch + } +} + +func (cli *Client) incrementSyncingID() uint32 { + cli.syncingMutex.Lock() + defer cli.syncingMutex.Unlock() + cli.syncingID++ + return cli.syncingID +} + +func (cli *Client) getSyncingID() uint32 { + cli.syncingMutex.Lock() + defer cli.syncingMutex.Unlock() + return cli.syncingID +} + +// StopSync stops the ongoing sync started by Sync. +func (cli *Client) StopSync() { + // Advance the syncing state so that any running Syncs will terminate. + cli.incrementSyncingID() +} + +// MakeRequest makes a JSON HTTP request to the given URL. +// If "resBody" is not nil, the response body will be json.Unmarshalled into it. +// +// Returns the HTTP body as bytes on 2xx with a nil error. Returns an error if the response is not 2xx along +// with the HTTP body bytes if it got that far. This error is an HTTPError which includes the returned +// HTTP status code and possibly a RespError as the WrappedError, if the HTTP body could be decoded as a RespError. +func (cli *Client) MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error) { + var req *http.Request + var err error + if reqBody != nil { + var jsonStr []byte + jsonStr, err = json.Marshal(reqBody) + if err != nil { + return nil, err + } + req, err = http.NewRequest(method, httpURL, bytes.NewBuffer(jsonStr)) + } else { + req, err = http.NewRequest(method, httpURL, nil) + } + + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + res, err := cli.Client.Do(req) + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + contents, err := ioutil.ReadAll(res.Body) + if res.StatusCode/100 != 2 { // not 2xx + var wrap error + var respErr RespError + if _ = json.Unmarshal(contents, &respErr); respErr.ErrCode != "" { + wrap = respErr + } + + // If we failed to decode as RespError, don't just drop the HTTP body, include it in the + // HTTP error instead (e.g proxy errors which return HTML). + msg := "Failed to " + method + " JSON to " + req.URL.Path + if wrap == nil { + msg = msg + ": " + string(contents) + } + + return contents, HTTPError{ + Code: res.StatusCode, + Message: msg, + WrappedError: wrap, + } + } + if err != nil { + return nil, err + } + + if resBody != nil { + if err = json.Unmarshal(contents, &resBody); err != nil { + return nil, err + } + } + + return contents, nil +} + +// CreateFilter makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter +func (cli *Client) CreateFilter(filter json.RawMessage) (resp *RespCreateFilter, err error) { + urlPath := cli.BuildURL("user", cli.UserID, "filter") + _, err = cli.MakeRequest("POST", urlPath, &filter, &resp) + return +} + +// SyncRequest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync +func (cli *Client) SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *RespSync, err error) { + query := map[string]string{ + "timeout": strconv.Itoa(timeout), + } + if since != "" { + query["since"] = since + } + if filterID != "" { + query["filter"] = filterID + } + if setPresence != "" { + query["set_presence"] = setPresence + } + if fullState { + query["full_state"] = "true" + } + urlPath := cli.BuildURLWithQuery([]string{"sync"}, query) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func (cli *Client) register(u string, req *ReqRegister) (resp *RespRegister, uiaResp *RespUserInteractive, err error) { + var bodyBytes []byte + bodyBytes, err = cli.MakeRequest("POST", u, req, nil) + if err != nil { + httpErr, ok := err.(HTTPError) + if !ok { // network error + return + } + if httpErr.Code == 401 { + // body should be RespUserInteractive, if it isn't, fail with the error + err = json.Unmarshal(bodyBytes, &uiaResp) + return + } + return + } + // body should be RespRegister + err = json.Unmarshal(bodyBytes, &resp) + return +} + +// Register makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// +// Registers with kind=user. For kind=guest, see RegisterGuest. +func (cli *Client) Register(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { + u := cli.BuildURL("register") + return cli.register(u, req) +} + +// RegisterGuest makes an HTTP request according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +// with kind=guest. +// +// For kind=user, see Register. +func (cli *Client) RegisterGuest(req *ReqRegister) (*RespRegister, *RespUserInteractive, error) { + query := map[string]string{ + "kind": "guest", + } + u := cli.BuildURLWithQuery([]string{"register"}, query) + return cli.register(u, req) +} + +// RegisterDummy performs m.login.dummy registration according to https://matrix.org/docs/spec/client_server/r0.2.0.html#dummy-auth +// +// Only a username and password need to be provided on the ReqRegister struct. Most local/developer homeservers will allow registration +// this way. If the homeserver does not, an error is returned. +// +// This does not set credentials on the client instance. See SetCredentials() instead. +// +// res, err := cli.RegisterDummy(&gomatrix.ReqRegister{ +// Username: "alice", +// Password: "wonderland", +// }) +// if err != nil { +// panic(err) +// } +// token := res.AccessToken +func (cli *Client) RegisterDummy(req *ReqRegister) (*RespRegister, error) { + res, uia, err := cli.Register(req) + if err != nil && uia == nil { + return nil, err + } + if uia != nil && uia.HasSingleStageFlow("m.login.dummy") { + req.Auth = struct { + Type string `json:"type"` + Session string `json:"session,omitempty"` + }{"m.login.dummy", uia.Session} + res, _, err = cli.Register(req) + if err != nil { + return nil, err + } + } + if res == nil { + return nil, fmt.Errorf("registration failed: does this server support m.login.dummy?") + } + return res, nil +} + +// Login a user to the homeserver according to http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +// This does not set credentials on this client instance. See SetCredentials() instead. +func (cli *Client) Login(req *ReqLogin) (resp *RespLogin, err error) { + urlPath := cli.BuildURL("login") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +// Logout the current user. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout +// This does not clear the credentials from the client instance. See ClearCredentials() instead. +func (cli *Client) Logout() (resp *RespLogout, err error) { + urlPath := cli.BuildURL("logout") + _, err = cli.MakeRequest("POST", urlPath, nil, &resp) + return +} + +// Versions returns the list of supported Matrix versions on this homeserver. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions +func (cli *Client) Versions() (resp *RespVersions, err error) { + urlPath := cli.BuildBaseURL("_matrix", "client", "versions") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// JoinRoom joins the client to a room ID or alias. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-join-roomidoralias +// +// If serverName is specified, this will be added as a query param to instruct the homeserver to join via that server. If content is specified, it will +// be JSON encoded and used as the request body. +func (cli *Client) JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *RespJoinRoom, err error) { + var urlPath string + if serverName != "" { + urlPath = cli.BuildURLWithQuery([]string{"join", roomIDorAlias}, map[string]string{ + "server_name": serverName, + }) + } else { + urlPath = cli.BuildURL("join", roomIDorAlias) + } + _, err = cli.MakeRequest("POST", urlPath, content, &resp) + return +} + +// GetDisplayName returns the display name of the user from the specified MXID. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname +func (cli *Client) GetDisplayName(mxid string) (resp *RespUserDisplayName, err error) { + urlPath := cli.BuildURL("profile", mxid, "displayname") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// GetOwnDisplayName returns the user's display name. See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname +func (cli *Client) GetOwnDisplayName() (resp *RespUserDisplayName, err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "displayname") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// SetDisplayName sets the user's profile display name. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-displayname +func (cli *Client) SetDisplayName(displayName string) (err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "displayname") + s := struct { + DisplayName string `json:"displayname"` + }{displayName} + _, err = cli.MakeRequest("PUT", urlPath, &s, nil) + return +} + +// GetAvatarURL gets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) GetAvatarURL() (url string, err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") + s := struct { + AvatarURL string `json:"avatar_url"` + }{} + + _, err = cli.MakeRequest("GET", urlPath, nil, &s) + if err != nil { + return "", err + } + + return s.AvatarURL, nil +} + +// SetAvatarURL sets the user's avatar URL. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-profile-userid-avatar-url +func (cli *Client) SetAvatarURL(url string) (err error) { + urlPath := cli.BuildURL("profile", cli.UserID, "avatar_url") + s := struct { + AvatarURL string `json:"avatar_url"` + }{url} + _, err = cli.MakeRequest("PUT", urlPath, &s, nil) + if err != nil { + return err + } + + return nil +} + +// SendMessageEvent sends a message event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid +// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. +func (cli *Client) SendMessageEvent(roomID string, eventType string, contentJSON interface{}) (resp *RespSendEvent, err error) { + txnID := txnID() + urlPath := cli.BuildURL("rooms", roomID, "send", eventType, txnID) + _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) + return +} + +// SendStateEvent sends a state event into a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-state-eventtype-statekey +// contentJSON should be a pointer to something that can be encoded as JSON using json.Marshal. +func (cli *Client) SendStateEvent(roomID, eventType, stateKey string, contentJSON interface{}) (resp *RespSendEvent, err error) { + urlPath := cli.BuildURL("rooms", roomID, "state", eventType, stateKey) + _, err = cli.MakeRequest("PUT", urlPath, contentJSON, &resp) + return +} + +// SendText sends an m.room.message event into the given room with a msgtype of m.text +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-text +func (cli *Client) SendText(roomID, text string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, "m.room.message", + TextMessage{"m.text", text}) +} + +// SendImage sends an m.room.message event into the given room with a msgtype of m.image +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-image +func (cli *Client) SendImage(roomID, body, url string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, "m.room.message", + ImageMessage{ + MsgType: "m.image", + Body: body, + URL: url, + }) +} + +// SendVideo sends an m.room.message event into the given room with a msgtype of m.video +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#m-video +func (cli *Client) SendVideo(roomID, body, url string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, "m.room.message", + VideoMessage{ + MsgType: "m.video", + Body: body, + URL: url, + }) +} + +// SendNotice sends an m.room.message event into the given room with a msgtype of m.notice +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-notice +func (cli *Client) SendNotice(roomID, text string) (*RespSendEvent, error) { + return cli.SendMessageEvent(roomID, "m.room.message", + TextMessage{"m.notice", text}) +} + +// RedactEvent redacts the given event. See http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid +func (cli *Client) RedactEvent(roomID, eventID string, req *ReqRedact) (resp *RespSendEvent, err error) { + txnID := txnID() + urlPath := cli.BuildURL("rooms", roomID, "redact", eventID, txnID) + _, err = cli.MakeRequest("PUT", urlPath, req, &resp) + return +} + +// CreateRoom creates a new Matrix room. See https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +// resp, err := cli.CreateRoom(&gomatrix.ReqCreateRoom{ +// Preset: "public_chat", +// }) +// fmt.Println("Room:", resp.RoomID) +func (cli *Client) CreateRoom(req *ReqCreateRoom) (resp *RespCreateRoom, err error) { + urlPath := cli.BuildURL("createRoom") + _, err = cli.MakeRequest("POST", urlPath, req, &resp) + return +} + +// LeaveRoom leaves the given room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave +func (cli *Client) LeaveRoom(roomID string) (resp *RespLeaveRoom, err error) { + u := cli.BuildURL("rooms", roomID, "leave") + _, err = cli.MakeRequest("POST", u, struct{}{}, &resp) + return +} + +// ForgetRoom forgets a room entirely. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget +func (cli *Client) ForgetRoom(roomID string) (resp *RespForgetRoom, err error) { + u := cli.BuildURL("rooms", roomID, "forget") + _, err = cli.MakeRequest("POST", u, struct{}{}, &resp) + return +} + +// InviteUser invites a user to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +func (cli *Client) InviteUser(roomID string, req *ReqInviteUser) (resp *RespInviteUser, err error) { + u := cli.BuildURL("rooms", roomID, "invite") + _, err = cli.MakeRequest("POST", u, struct{}{}, &resp) + return +} + +// InviteUserByThirdParty invites a third-party identifier to a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#invite-by-third-party-id-endpoint +func (cli *Client) InviteUserByThirdParty(roomID string, req *ReqInvite3PID) (resp *RespInviteUser, err error) { + u := cli.BuildURL("rooms", roomID, "invite") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// KickUser kicks a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +func (cli *Client) KickUser(roomID string, req *ReqKickUser) (resp *RespKickUser, err error) { + u := cli.BuildURL("rooms", roomID, "kick") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// BanUser bans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +func (cli *Client) BanUser(roomID string, req *ReqBanUser) (resp *RespBanUser, err error) { + u := cli.BuildURL("rooms", roomID, "ban") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// UnbanUser unbans a user from a room. See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +func (cli *Client) UnbanUser(roomID string, req *ReqUnbanUser) (resp *RespUnbanUser, err error) { + u := cli.BuildURL("rooms", roomID, "unban") + _, err = cli.MakeRequest("POST", u, req, &resp) + return +} + +// UserTyping sets the typing status of the user. See https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +func (cli *Client) UserTyping(roomID string, typing bool, timeout int64) (resp *RespTyping, err error) { + req := ReqTyping{Typing: typing, Timeout: timeout} + u := cli.BuildURL("rooms", roomID, "typing", cli.UserID) + _, err = cli.MakeRequest("PUT", u, req, &resp) + return +} + +// StateEvent gets a single state event in a room. It will attempt to JSON unmarshal into the given "outContent" struct with +// the HTTP response body, or return an error. +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-state-eventtype-statekey +func (cli *Client) StateEvent(roomID, eventType, stateKey string, outContent interface{}) (err error) { + u := cli.BuildURL("rooms", roomID, "state", eventType, stateKey) + _, err = cli.MakeRequest("GET", u, nil, outContent) + return +} + +// UploadLink uploads an HTTP URL and then returns an MXC URI. +func (cli *Client) UploadLink(link string) (*RespMediaUpload, error) { + res, err := cli.Client.Get(link) + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + return cli.UploadToContentRepo(res.Body, res.Header.Get("Content-Type"), res.ContentLength) +} + +// UploadToContentRepo uploads the given bytes to the content repository and returns an MXC URI. +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload +func (cli *Client) UploadToContentRepo(content io.Reader, contentType string, contentLength int64) (*RespMediaUpload, error) { + req, err := http.NewRequest("POST", cli.BuildBaseURL("_matrix/media/r0/upload"), content) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", contentType) + req.ContentLength = contentLength + res, err := cli.Client.Do(req) + if res != nil { + defer res.Body.Close() + } + if err != nil { + return nil, err + } + if res.StatusCode != 200 { + contents, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, HTTPError{ + Message: "Upload request failed - Failed to read response body: " + err.Error(), + Code: res.StatusCode, + } + } + return nil, HTTPError{ + Message: "Upload request failed: " + string(contents), + Code: res.StatusCode, + } + } + var m RespMediaUpload + if err := json.NewDecoder(res.Body).Decode(&m); err != nil { + return nil, err + } + return &m, nil +} + +// JoinedMembers returns a map of joined room members. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680 +// +// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. +// This API is primarily designed for application services which may want to efficiently look up joined members in a room. +func (cli *Client) JoinedMembers(roomID string) (resp *RespJoinedMembers, err error) { + u := cli.BuildURL("rooms", roomID, "joined_members") + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +// JoinedRooms returns a list of rooms which the client is joined to. See TODO-SPEC. https://github.com/matrix-org/synapse/pull/1680 +// +// In general, usage of this API is discouraged in favour of /sync, as calling this API can race with incoming membership changes. +// This API is primarily designed for application services which may want to efficiently look up joined rooms. +func (cli *Client) JoinedRooms() (resp *RespJoinedRooms, err error) { + u := cli.BuildURL("joined_rooms") + _, err = cli.MakeRequest("GET", u, nil, &resp) + return +} + +// Messages returns a list of message and state events for a room. It uses +// pagination query parameters to paginate history in the room. +// See https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages +func (cli *Client) Messages(roomID, from, to string, dir rune, limit int) (resp *RespMessages, err error) { + query := map[string]string{ + "from": from, + "dir": string(dir), + } + if to != "" { + query["to"] = to + } + if limit != 0 { + query["limit"] = strconv.Itoa(limit) + } + + urlPath := cli.BuildURLWithQuery([]string{"rooms", roomID, "messages"}, query) + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +// TurnServer returns turn server details and credentials for the client to use when initiating calls. +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-voip-turnserver +func (cli *Client) TurnServer() (resp *RespTurnServer, err error) { + urlPath := cli.BuildURL("voip", "turnServer") + _, err = cli.MakeRequest("GET", urlPath, nil, &resp) + return +} + +func txnID() string { + return "go" + strconv.FormatInt(time.Now().UnixNano(), 10) +} + +// NewClient creates a new Matrix Client ready for syncing +func NewClient(homeserverURL, userID, accessToken string) (*Client, error) { + hsURL, err := url.Parse(homeserverURL) + if err != nil { + return nil, err + } + // By default, use an in-memory store which will never save filter ids / next batch tokens to disk. + // The client will work with this storer: it just won't remember across restarts. + // In practice, a database backend should be used. + store := NewInMemoryStore() + cli := Client{ + AccessToken: accessToken, + HomeserverURL: hsURL, + UserID: userID, + Prefix: "/_matrix/client/r0", + Syncer: NewDefaultSyncer(userID, store), + Store: store, + } + // By default, use the default HTTP client. + cli.Client = http.DefaultClient + + return &cli, nil +} diff --git a/vendor/github.com/matrix-org/gomatrix/client_examples_test.go b/vendor/github.com/matrix-org/gomatrix/client_examples_test.go new file mode 100644 index 0000000..e3c0f09 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/client_examples_test.go @@ -0,0 +1,119 @@ +package gomatrix + +import ( + "fmt" + "net/http" +) + +func Example_sync() { + cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "MDAefhiuwehfuiwe") + cli.Store.SaveFilterID("@example:matrix.org", "2") // Optional: if you know it already + cli.Store.SaveNextBatch("@example:matrix.org", "111_222_333_444") // Optional: if you know it already + syncer := cli.Syncer.(*DefaultSyncer) + syncer.OnEventType("m.room.message", func(ev *Event) { + fmt.Println("Message: ", ev) + }) + + // Blocking version + if err := cli.Sync(); err != nil { + fmt.Println("Sync() returned ", err) + } + + // Non-blocking version + go func() { + for { + if err := cli.Sync(); err != nil { + fmt.Println("Sync() returned ", err) + } + // Optional: Wait a period of time before trying to sync again. + } + }() +} + +func Example_customInterfaces() { + // Custom interfaces must be set prior to calling functions on the client. + cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "MDAefhiuwehfuiwe") + + // anything which implements the Storer interface + customStore := NewInMemoryStore() + cli.Store = customStore + + // anything which implements the Syncer interface + customSyncer := NewDefaultSyncer("@example:matrix.org", customStore) + cli.Syncer = customSyncer + + // any http.Client + cli.Client = http.DefaultClient + + // Once you call a function, you can't safely change the interfaces. + cli.SendText("!foo:bar", "Down the rabbit hole") +} + +func ExampleClient_BuildURLWithQuery() { + cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "abcdef123456") + out := cli.BuildURLWithQuery([]string{"sync"}, map[string]string{ + "filter_id": "5", + }) + fmt.Println(out) + // Output: https://matrix.org/_matrix/client/r0/sync?access_token=abcdef123456&filter_id=5 +} + +func ExampleClient_BuildURL() { + userID := "@example:matrix.org" + cli, _ := NewClient("https://matrix.org", userID, "abcdef123456") + out := cli.BuildURL("user", userID, "filter") + fmt.Println(out) + // Output: https://matrix.org/_matrix/client/r0/user/@example:matrix.org/filter?access_token=abcdef123456 +} + +func ExampleClient_BuildBaseURL() { + userID := "@example:matrix.org" + cli, _ := NewClient("https://matrix.org", userID, "abcdef123456") + out := cli.BuildBaseURL("_matrix", "client", "r0", "directory", "room", "#matrix:matrix.org") + fmt.Println(out) + // Output: https://matrix.org/_matrix/client/r0/directory/room/%23matrix:matrix.org?access_token=abcdef123456 +} + +// Retrieve the content of a m.room.name state event. +func ExampleClient_StateEvent() { + content := struct { + Name string `json:"name"` + }{} + cli, _ := NewClient("https://matrix.org", "@example:matrix.org", "abcdef123456") + if err := cli.StateEvent("!foo:bar", "m.room.name", "", &content); err != nil { + panic(err) + } +} + +// Join a room by ID. +func ExampleClient_JoinRoom_id() { + cli, _ := NewClient("http://localhost:8008", "@example:localhost", "abcdef123456") + if _, err := cli.JoinRoom("!uOILRrqxnsYgQdUzar:localhost", "", nil); err != nil { + panic(err) + } +} + +// Join a room by alias. +func ExampleClient_JoinRoom_alias() { + cli, _ := NewClient("http://localhost:8008", "@example:localhost", "abcdef123456") + if resp, err := cli.JoinRoom("#test:localhost", "", nil); err != nil { + panic(err) + } else { + // Use room ID for something. + _ = resp.RoomID + } +} + +// Login to a local homeserver and set the user ID and access token on success. +func ExampleClient_Login() { + cli, _ := NewClient("http://localhost:8008", "", "") + resp, err := cli.Login(&ReqLogin{ + Type: "m.login.password", + User: "alice", + Password: "wonderland", + }) + if err != nil { + panic(err) + } + cli.SetCredentials(resp.UserID, resp.AccessToken) +} diff --git a/vendor/github.com/matrix-org/gomatrix/client_test.go b/vendor/github.com/matrix-org/gomatrix/client_test.go new file mode 100644 index 0000000..9b7d2da --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/client_test.go @@ -0,0 +1,105 @@ +package gomatrix + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "testing" +) + +func TestClient_LeaveRoom(t *testing.T) { + cli := mockClient(func(req *http.Request) (*http.Response, error) { + if req.Method == "POST" && req.URL.Path == "/_matrix/client/r0/rooms/!foo:bar/leave" { + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewBufferString(`{}`)), + }, nil + } + return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path) + }) + + if _, err := cli.LeaveRoom("!foo:bar"); err != nil { + t.Fatalf("LeaveRoom: error, got %s", err.Error()) + } +} + +func TestClient_GetAvatarUrl(t *testing.T) { + cli := mockClient(func(req *http.Request) (*http.Response, error) { + if req.Method == "GET" && req.URL.Path == "/_matrix/client/r0/profile/@user:test.gomatrix.org/avatar_url" { + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewBufferString(`{"avatar_url":"mxc://matrix.org/iJaUjkshgdfsdkjfn"}`)), + }, nil + } + return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path) + }) + + if response, err := cli.GetAvatarURL(); err != nil { + t.Fatalf("GetAvatarURL: Got error: %s", err.Error()) + } else if response == "" { + t.Fatal("GetAvatarURL: Got empty response") + } else if response != "mxc://matrix.org/iJaUjkshgdfsdkjfn" { + t.Fatalf("Unexpected response URL: %s", response) + } + +} + +func TestClient_SetAvatarUrl(t *testing.T) { + cli := mockClient(func(req *http.Request) (*http.Response, error) { + if req.Method == "PUT" && req.URL.Path == "/_matrix/client/r0/profile/@user:test.gomatrix.org/avatar_url" { + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewBufferString(`{}`)), + }, nil + } + return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path) + }) + + if err := cli.SetAvatarURL("https://foo.com/bar.png"); err != nil { + t.Fatalf("GetAvatarURL: Got error: %s", err.Error()) + } +} + +func TestClient_StateEvent(t *testing.T) { + cli := mockClient(func(req *http.Request) (*http.Response, error) { + if req.Method == "GET" && req.URL.Path == "/_matrix/client/r0/rooms/!foo:bar/state/m.room.name" { + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewBufferString(`{"name":"Room Name Goes Here"}`)), + }, nil + } + return nil, fmt.Errorf("unhandled URL: %s", req.URL.Path) + }) + + content := struct { + Name string `json:"name"` + }{} + + if err := cli.StateEvent("!foo:bar", "m.room.name", "", &content); err != nil { + t.Fatalf("StateEvent: error, got %s", err.Error()) + } + if content.Name != "Room Name Goes Here" { + t.Fatalf("StateEvent: got %s, want %s", content.Name, "Room Name Goes Here") + } +} + +func mockClient(fn func(*http.Request) (*http.Response, error)) *Client { + mrt := MockRoundTripper{ + RT: fn, + } + + cli, _ := NewClient("https://test.gomatrix.org", "@user:test.gomatrix.org", "abcdef") + cli.Client = &http.Client{ + Transport: mrt, + } + return cli +} + +type MockRoundTripper struct { + RT func(*http.Request) (*http.Response, error) +} + +func (t MockRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return t.RT(req) +} diff --git a/vendor/github.com/matrix-org/gomatrix/events.go b/vendor/github.com/matrix-org/gomatrix/events.go new file mode 100644 index 0000000..7427740 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/events.go @@ -0,0 +1,101 @@ +package gomatrix + +import ( + "html" + "regexp" +) + +// Event represents a single Matrix event. +type Event struct { + StateKey *string `json:"state_key,omitempty"` // The state key for the event. Only present on State Events. + Sender string `json:"sender"` // The user ID of the sender of the event + Type string `json:"type"` // The event type + Timestamp int64 `json:"origin_server_ts"` // The unix timestamp when this message was sent by the origin server + ID string `json:"event_id"` // The unique ID of this event + RoomID string `json:"room_id"` // The room the event was sent to. May be nil (e.g. for presence) + Content map[string]interface{} `json:"content"` // The JSON content of the event. +} + +// Body returns the value of the "body" key in the event content if it is +// present and is a string. +func (event *Event) Body() (body string, ok bool) { + value, exists := event.Content["body"] + if !exists { + return + } + body, ok = value.(string) + return +} + +// MessageType returns the value of the "msgtype" key in the event content if +// it is present and is a string. +func (event *Event) MessageType() (msgtype string, ok bool) { + value, exists := event.Content["msgtype"] + if !exists { + return + } + msgtype, ok = value.(string) + return +} + +// TextMessage is the contents of a Matrix formated message event. +type TextMessage struct { + MsgType string `json:"msgtype"` + Body string `json:"body"` +} + +// ImageInfo contains info about an image - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-image +type ImageInfo struct { + Height uint `json:"h,omitempty"` + Width uint `json:"w,omitempty"` + Mimetype string `json:"mimetype,omitempty"` + Size uint `json:"size,omitempty"` +} + +// VideoInfo contains info about a video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video +type VideoInfo struct { + Mimetype string `json:"mimetype,omitempty"` + ThumbnailInfo ImageInfo `json:"thumbnail_info"` + ThumbnailURL string `json:"thumbnail_url,omitempty"` + Height uint `json:"h,omitempty"` + Width uint `json:"w,omitempty"` + Duration uint `json:"duration,omitempty"` + Size uint `json:"size,omitempty"` +} + +// VideoMessage is an m.video - http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video +type VideoMessage struct { + MsgType string `json:"msgtype"` + Body string `json:"body"` + URL string `json:"url"` + Info VideoInfo `json:"info"` +} + +// ImageMessage is an m.image event +type ImageMessage struct { + MsgType string `json:"msgtype"` + Body string `json:"body"` + URL string `json:"url"` + Info ImageInfo `json:"info"` +} + +// An HTMLMessage is the contents of a Matrix HTML formated message event. +type HTMLMessage struct { + Body string `json:"body"` + MsgType string `json:"msgtype"` + Format string `json:"format"` + FormattedBody string `json:"formatted_body"` +} + +var htmlRegex = regexp.MustCompile("<[^<]+?>") + +// GetHTMLMessage returns an HTMLMessage with the body set to a stripped version of the provided HTML, in addition +// to the provided HTML. +func GetHTMLMessage(msgtype, htmlText string) HTMLMessage { + return HTMLMessage{ + Body: html.UnescapeString(htmlRegex.ReplaceAllLiteralString(htmlText, "")), + MsgType: msgtype, + Format: "org.matrix.custom.html", + FormattedBody: htmlText, + } +} diff --git a/vendor/github.com/matrix-org/gomatrix/filter.go b/vendor/github.com/matrix-org/gomatrix/filter.go new file mode 100644 index 0000000..e4e7628 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/filter.go @@ -0,0 +1,43 @@ +// Copyright 2017 Jan Christian Grünhage +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gomatrix + +//Filter is used by clients to specify how the server should filter responses to e.g. sync requests +//Specified by: https://matrix.org/docs/spec/client_server/r0.2.0.html#filtering +type Filter struct { + AccountData FilterPart `json:"account_data,omitempty"` + EventFields []string `json:"event_fields,omitempty"` + EventFormat string `json:"event_format,omitempty"` + Presence FilterPart `json:"presence,omitempty"` + Room struct { + AccountData FilterPart `json:"account_data,omitempty"` + Ephemeral FilterPart `json:"ephemeral,omitempty"` + IncludeLeave bool `json:"include_leave,omitempty"` + NotRooms []string `json:"not_rooms,omitempty"` + Rooms []string `json:"rooms,omitempty"` + State FilterPart `json:"state,omitempty"` + Timeline FilterPart `json:"timeline,omitempty"` + } `json:"room,omitempty"` +} + +type FilterPart struct { + NotRooms []string `json:"not_rooms,omitempty"` + Rooms []string `json:"rooms,omitempty"` + Limit *int `json:"limit,omitempty"` + NotSenders []string `json:"not_senders,omitempty"` + NotTypes []string `json:"not_types,omitempty"` + Senders []string `json:"senders,omitempty"` + Types []string `json:"types,omitempty"` +} diff --git a/vendor/github.com/matrix-org/gomatrix/hooks/install.sh b/vendor/github.com/matrix-org/gomatrix/hooks/install.sh new file mode 100755 index 0000000..f8aa331 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/hooks/install.sh @@ -0,0 +1,5 @@ +#! /bin/bash + +DOT_GIT="$(dirname $0)/../.git" + +ln -s "../../hooks/pre-commit" "$DOT_GIT/hooks/pre-commit" \ No newline at end of file diff --git a/vendor/github.com/matrix-org/gomatrix/hooks/pre-commit b/vendor/github.com/matrix-org/gomatrix/hooks/pre-commit new file mode 100755 index 0000000..bb0a27f --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/hooks/pre-commit @@ -0,0 +1,26 @@ +#! /bin/bash + +set -eu + +golint +misspell --error . + +# gofmt doesn't exit with an error code if the files don't match the expected +# format. So we have to run it and see if it outputs anything. +if gofmt -l -s . 2>&1 | read +then + echo "Error: not all code had been formatted with gofmt." + echo "Fixing the following files" + gofmt -s -w -l . + echo + echo "Please add them to the commit" + git status --short + exit 1 +fi + +ineffassign . + +go fmt +go tool vet --all --shadow . +gocyclo -over 12 . +go test -timeout 5s -test.v diff --git a/vendor/github.com/matrix-org/gomatrix/requests.go b/vendor/github.com/matrix-org/gomatrix/requests.go new file mode 100644 index 0000000..af99a22 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/requests.go @@ -0,0 +1,78 @@ +package gomatrix + +// ReqRegister is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +type ReqRegister struct { + Username string `json:"username,omitempty"` + BindEmail bool `json:"bind_email,omitempty"` + Password string `json:"password,omitempty"` + DeviceID string `json:"device_id,omitempty"` + InitialDeviceDisplayName string `json:"initial_device_display_name"` + Auth interface{} `json:"auth,omitempty"` +} + +// ReqLogin is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +type ReqLogin struct { + Type string `json:"type"` + Password string `json:"password,omitempty"` + Medium string `json:"medium,omitempty"` + User string `json:"user,omitempty"` + Address string `json:"address,omitempty"` + Token string `json:"token,omitempty"` + DeviceID string `json:"device_id,omitempty"` + InitialDeviceDisplayName string `json:"initial_device_display_name,omitempty"` +} + +// ReqCreateRoom is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +type ReqCreateRoom struct { + Visibility string `json:"visibility,omitempty"` + RoomAliasName string `json:"room_alias_name,omitempty"` + Name string `json:"name,omitempty"` + Topic string `json:"topic,omitempty"` + Invite []string `json:"invite,omitempty"` + Invite3PID []ReqInvite3PID `json:"invite_3pid,omitempty"` + CreationContent map[string]interface{} `json:"creation_content,omitempty"` + InitialState []Event `json:"initial_state,omitempty"` + Preset string `json:"preset,omitempty"` + IsDirect bool `json:"is_direct,omitempty"` +} + +// ReqRedact is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-redact-eventid-txnid +type ReqRedact struct { + Reason string `json:"reason,omitempty"` +} + +// ReqInvite3PID is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#id57 +// It is also a JSON object used in https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +type ReqInvite3PID struct { + IDServer string `json:"id_server"` + Medium string `json:"medium"` + Address string `json:"address"` +} + +// ReqInviteUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +type ReqInviteUser struct { + UserID string `json:"user_id"` +} + +// ReqKickUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +type ReqKickUser struct { + Reason string `json:"reason,omitempty"` + UserID string `json:"user_id"` +} + +// ReqBanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +type ReqBanUser struct { + Reason string `json:"reason,omitempty"` + UserID string `json:"user_id"` +} + +// ReqUnbanUser is the JSON request for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +type ReqUnbanUser struct { + UserID string `json:"user_id"` +} + +// ReqTyping is the JSON request for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +type ReqTyping struct { + Typing bool `json:"typing"` + Timeout int64 `json:"timeout"` +} diff --git a/vendor/github.com/matrix-org/gomatrix/responses.go b/vendor/github.com/matrix-org/gomatrix/responses.go new file mode 100644 index 0000000..fe0eeb3 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/responses.go @@ -0,0 +1,176 @@ +package gomatrix + +// RespError is the standard JSON error response from Homeservers. It also implements the Golang "error" interface. +// See http://matrix.org/docs/spec/client_server/r0.2.0.html#api-standards +type RespError struct { + ErrCode string `json:"errcode"` + Err string `json:"error"` +} + +// Error returns the errcode and error message. +func (e RespError) Error() string { + return e.ErrCode + ": " + e.Err +} + +// RespCreateFilter is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-user-userid-filter +type RespCreateFilter struct { + FilterID string `json:"filter_id"` +} + +// RespVersions is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-versions +type RespVersions struct { + Versions []string `json:"versions"` +} + +// RespJoinRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-join +type RespJoinRoom struct { + RoomID string `json:"room_id"` +} + +// RespLeaveRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-leave +type RespLeaveRoom struct{} + +// RespForgetRoom is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-forget +type RespForgetRoom struct{} + +// RespInviteUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-invite +type RespInviteUser struct{} + +// RespKickUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-kick +type RespKickUser struct{} + +// RespBanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-ban +type RespBanUser struct{} + +// RespUnbanUser is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-rooms-roomid-unban +type RespUnbanUser struct{} + +// RespTyping is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-typing-userid +type RespTyping struct{} + +// RespJoinedRooms is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680 +type RespJoinedRooms struct { + JoinedRooms []string `json:"joined_rooms"` +} + +// RespJoinedMembers is the JSON response for TODO-SPEC https://github.com/matrix-org/synapse/pull/1680 +type RespJoinedMembers struct { + Joined map[string]struct { + DisplayName *string `json:"display_name"` + AvatarURL *string `json:"avatar_url"` + } `json:"joined"` +} + +// RespMessages is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-rooms-roomid-messages +type RespMessages struct { + Start string `json:"start"` + Chunk []Event `json:"chunk"` + End string `json:"end"` +} + +// RespSendEvent is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid +type RespSendEvent struct { + EventID string `json:"event_id"` +} + +// RespMediaUpload is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-media-r0-upload +type RespMediaUpload struct { + ContentURI string `json:"content_uri"` +} + +// RespUserInteractive is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#user-interactive-authentication-api +type RespUserInteractive struct { + Flows []struct { + Stages []string `json:"stages"` + } `json:"flows"` + Params map[string]interface{} `json:"params"` + Session string `json:"string"` + Completed []string `json:"completed"` + ErrCode string `json:"errcode"` + Error string `json:"error"` +} + +// HasSingleStageFlow returns true if there exists at least 1 Flow with a single stage of stageName. +func (r RespUserInteractive) HasSingleStageFlow(stageName string) bool { + for _, f := range r.Flows { + if len(f.Stages) == 1 && f.Stages[0] == stageName { + return true + } + } + return false +} + +// RespUserDisplayName is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-profile-userid-displayname +type RespUserDisplayName struct { + DisplayName string `json:"displayname"` +} + +// RespRegister is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register +type RespRegister struct { + AccessToken string `json:"access_token"` + DeviceID string `json:"device_id"` + HomeServer string `json:"home_server"` + RefreshToken string `json:"refresh_token"` + UserID string `json:"user_id"` +} + +// RespLogin is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-login +type RespLogin struct { + AccessToken string `json:"access_token"` + DeviceID string `json:"device_id"` + HomeServer string `json:"home_server"` + UserID string `json:"user_id"` +} + +// RespLogout is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-logout +type RespLogout struct{} + +// RespCreateRoom is the JSON response for https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-createroom +type RespCreateRoom struct { + RoomID string `json:"room_id"` +} + +// RespSync is the JSON response for http://matrix.org/docs/spec/client_server/r0.2.0.html#get-matrix-client-r0-sync +type RespSync struct { + NextBatch string `json:"next_batch"` + AccountData struct { + Events []Event `json:"events"` + } `json:"account_data"` + Presence struct { + Events []Event `json:"events"` + } `json:"presence"` + Rooms struct { + Leave map[string]struct { + State struct { + Events []Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` + } `json:"leave"` + Join map[string]struct { + State struct { + Events []Event `json:"events"` + } `json:"state"` + Timeline struct { + Events []Event `json:"events"` + Limited bool `json:"limited"` + PrevBatch string `json:"prev_batch"` + } `json:"timeline"` + } `json:"join"` + Invite map[string]struct { + State struct { + Events []Event + } `json:"invite_state"` + } `json:"invite"` + } `json:"rooms"` +} + +type RespTurnServer struct { + Username string `json:"username"` + Password string `json:"password"` + TTL int `json:"ttl"` + URIs []string `json:"uris"` +} diff --git a/vendor/github.com/matrix-org/gomatrix/room.go b/vendor/github.com/matrix-org/gomatrix/room.go new file mode 100644 index 0000000..c9b2351 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/room.go @@ -0,0 +1,50 @@ +package gomatrix + +// Room represents a single Matrix room. +type Room struct { + ID string + State map[string]map[string]*Event +} + +// UpdateState updates the room's current state with the given Event. This will clobber events based +// on the type/state_key combination. +func (room Room) UpdateState(event *Event) { + _, exists := room.State[event.Type] + if !exists { + room.State[event.Type] = make(map[string]*Event) + } + room.State[event.Type][*event.StateKey] = event +} + +// GetStateEvent returns the state event for the given type/state_key combo, or nil. +func (room Room) GetStateEvent(eventType string, stateKey string) *Event { + stateEventMap, _ := room.State[eventType] + event, _ := stateEventMap[stateKey] + return event +} + +// GetMembershipState returns the membership state of the given user ID in this room. If there is +// no entry for this member, 'leave' is returned for consistency with left users. +func (room Room) GetMembershipState(userID string) string { + state := "leave" + event := room.GetStateEvent("m.room.member", userID) + if event != nil { + membershipState, found := event.Content["membership"] + if found { + mState, isString := membershipState.(string) + if isString { + state = mState + } + } + } + return state +} + +// NewRoom creates a new Room with the given ID +func NewRoom(roomID string) *Room { + // Init the State map and return a pointer to the Room + return &Room{ + ID: roomID, + State: make(map[string]map[string]*Event), + } +} diff --git a/vendor/github.com/matrix-org/gomatrix/store.go b/vendor/github.com/matrix-org/gomatrix/store.go new file mode 100644 index 0000000..6dc687e --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/store.go @@ -0,0 +1,65 @@ +package gomatrix + +// Storer is an interface which must be satisfied to store client data. +// +// You can either write a struct which persists this data to disk, or you can use the +// provided "InMemoryStore" which just keeps data around in-memory which is lost on +// restarts. +type Storer interface { + SaveFilterID(userID, filterID string) + LoadFilterID(userID string) string + SaveNextBatch(userID, nextBatchToken string) + LoadNextBatch(userID string) string + SaveRoom(room *Room) + LoadRoom(roomID string) *Room +} + +// InMemoryStore implements the Storer interface. +// +// Everything is persisted in-memory as maps. It is not safe to load/save filter IDs +// or next batch tokens on any goroutine other than the syncing goroutine: the one +// which called Client.Sync(). +type InMemoryStore struct { + Filters map[string]string + NextBatch map[string]string + Rooms map[string]*Room +} + +// SaveFilterID to memory. +func (s *InMemoryStore) SaveFilterID(userID, filterID string) { + s.Filters[userID] = filterID +} + +// LoadFilterID from memory. +func (s *InMemoryStore) LoadFilterID(userID string) string { + return s.Filters[userID] +} + +// SaveNextBatch to memory. +func (s *InMemoryStore) SaveNextBatch(userID, nextBatchToken string) { + s.NextBatch[userID] = nextBatchToken +} + +// LoadNextBatch from memory. +func (s *InMemoryStore) LoadNextBatch(userID string) string { + return s.NextBatch[userID] +} + +// SaveRoom to memory. +func (s *InMemoryStore) SaveRoom(room *Room) { + s.Rooms[room.ID] = room +} + +// LoadRoom from memory. +func (s *InMemoryStore) LoadRoom(roomID string) *Room { + return s.Rooms[roomID] +} + +// NewInMemoryStore constructs a new InMemoryStore. +func NewInMemoryStore() *InMemoryStore { + return &InMemoryStore{ + Filters: make(map[string]string), + NextBatch: make(map[string]string), + Rooms: make(map[string]*Room), + } +} diff --git a/vendor/github.com/matrix-org/gomatrix/sync.go b/vendor/github.com/matrix-org/gomatrix/sync.go new file mode 100644 index 0000000..c4bea48 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/sync.go @@ -0,0 +1,164 @@ +package gomatrix + +import ( + "encoding/json" + "fmt" + "runtime/debug" + "time" +) + +// Syncer represents an interface that must be satisfied in order to do /sync requests on a client. +type Syncer interface { + // Process the /sync response. The since parameter is the since= value that was used to produce the response. + // This is useful for detecting the very first sync (since=""). If an error is return, Syncing will be stopped + // permanently. + ProcessResponse(resp *RespSync, since string) error + // OnFailedSync returns either the time to wait before retrying or an error to stop syncing permanently. + OnFailedSync(res *RespSync, err error) (time.Duration, error) + // GetFilterJSON for the given user ID. NOT the filter ID. + GetFilterJSON(userID string) json.RawMessage +} + +// DefaultSyncer is the default syncing implementation. You can either write your own syncer, or selectively +// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer +// pattern to notify callers about incoming events. See DefaultSyncer.OnEventType for more information. +type DefaultSyncer struct { + UserID string + Store Storer + listeners map[string][]OnEventListener // event type to listeners array +} + +// OnEventListener can be used with DefaultSyncer.OnEventType to be informed of incoming events. +type OnEventListener func(*Event) + +// NewDefaultSyncer returns an instantiated DefaultSyncer +func NewDefaultSyncer(userID string, store Storer) *DefaultSyncer { + return &DefaultSyncer{ + UserID: userID, + Store: store, + listeners: make(map[string][]OnEventListener), + } +} + +// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of +// unrepeating events. Returns a fatal error if a listener panics. +func (s *DefaultSyncer) ProcessResponse(res *RespSync, since string) (err error) { + if !s.shouldProcessResponse(res, since) { + return + } + + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.UserID, since, r, debug.Stack()) + } + }() + + for roomID, roomData := range res.Rooms.Join { + room := s.getOrCreateRoom(roomID) + for _, event := range roomData.State.Events { + event.RoomID = roomID + room.UpdateState(&event) + s.notifyListeners(&event) + } + for _, event := range roomData.Timeline.Events { + event.RoomID = roomID + s.notifyListeners(&event) + } + } + for roomID, roomData := range res.Rooms.Invite { + room := s.getOrCreateRoom(roomID) + for _, event := range roomData.State.Events { + event.RoomID = roomID + room.UpdateState(&event) + s.notifyListeners(&event) + } + } + for roomID, roomData := range res.Rooms.Leave { + room := s.getOrCreateRoom(roomID) + for _, event := range roomData.Timeline.Events { + if event.StateKey != nil { + event.RoomID = roomID + room.UpdateState(&event) + s.notifyListeners(&event) + } + } + } + return +} + +// OnEventType allows callers to be notified when there are new events for the given event type. +// There are no duplicate checks. +func (s *DefaultSyncer) OnEventType(eventType string, callback OnEventListener) { + _, exists := s.listeners[eventType] + if !exists { + s.listeners[eventType] = []OnEventListener{} + } + s.listeners[eventType] = append(s.listeners[eventType], callback) +} + +// shouldProcessResponse returns true if the response should be processed. May modify the response to remove +// stuff that shouldn't be processed. +func (s *DefaultSyncer) shouldProcessResponse(resp *RespSync, since string) bool { + if since == "" { + return false + } + // This is a horrible hack because /sync will return the most recent messages for a room + // as soon as you /join it. We do NOT want to process those events in that particular room + // because they may have already been processed (if you toggle the bot in/out of the room). + // + // Work around this by inspecting each room's timeline and seeing if an m.room.member event for us + // exists and is "join" and then discard processing that room entirely if so. + // TODO: We probably want to process messages from after the last join event in the timeline. + for roomID, roomData := range resp.Rooms.Join { + for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- { + e := roomData.Timeline.Events[i] + if e.Type == "m.room.member" && e.StateKey != nil && *e.StateKey == s.UserID { + m := e.Content["membership"] + mship, ok := m.(string) + if !ok { + continue + } + if mship == "join" { + _, ok := resp.Rooms.Join[roomID] + if !ok { + continue + } + delete(resp.Rooms.Join, roomID) // don't re-process messages + delete(resp.Rooms.Invite, roomID) // don't re-process invites + break + } + } + } + } + return true +} + +// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse() +func (s *DefaultSyncer) getOrCreateRoom(roomID string) *Room { + room := s.Store.LoadRoom(roomID) + if room == nil { // create a new Room + room = NewRoom(roomID) + s.Store.SaveRoom(room) + } + return room +} + +func (s *DefaultSyncer) notifyListeners(event *Event) { + listeners, exists := s.listeners[event.Type] + if !exists { + return + } + for _, fn := range listeners { + fn(event) + } +} + +// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error. +func (s *DefaultSyncer) OnFailedSync(res *RespSync, err error) (time.Duration, error) { + return 10 * time.Second, nil +} + +// GetFilterJSON returns a filter with a timeline limit of 50. +func (s *DefaultSyncer) GetFilterJSON(userID string) json.RawMessage { + return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`) +} diff --git a/vendor/github.com/matrix-org/gomatrix/userids.go b/vendor/github.com/matrix-org/gomatrix/userids.go new file mode 100644 index 0000000..23e7807 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/userids.go @@ -0,0 +1,130 @@ +package gomatrix + +import ( + "bytes" + "encoding/hex" + "fmt" + "strings" +) + +const lowerhex = "0123456789abcdef" + +// encode the given byte using quoted-printable encoding (e.g "=2f") +// and writes it to the buffer +// See https://golang.org/src/mime/quotedprintable/writer.go +func encode(buf *bytes.Buffer, b byte) { + buf.WriteByte('=') + buf.WriteByte(lowerhex[b>>4]) + buf.WriteByte(lowerhex[b&0x0f]) +} + +// escape the given alpha character and writes it to the buffer +func escape(buf *bytes.Buffer, b byte) { + buf.WriteByte('_') + if b == '_' { + buf.WriteByte('_') // another _ + } else { + buf.WriteByte(b + 0x20) // ASCII shift A-Z to a-z + } +} + +func shouldEncode(b byte) bool { + return b != '-' && b != '.' && b != '_' && !(b >= '0' && b <= '9') && !(b >= 'a' && b <= 'z') && !(b >= 'A' && b <= 'Z') +} + +func shouldEscape(b byte) bool { + return (b >= 'A' && b <= 'Z') || b == '_' +} + +func isValidByte(b byte) bool { + return isValidEscapedChar(b) || (b >= '0' && b <= '9') || b == '.' || b == '=' || b == '-' +} + +func isValidEscapedChar(b byte) bool { + return b == '_' || (b >= 'a' && b <= 'z') +} + +// EncodeUserLocalpart encodes the given string into Matrix-compliant user ID localpart form. +// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets +// +// This returns a string with only the characters "a-z0-9._=-". The uppercase range A-Z +// are encoded using leading underscores ("_"). Characters outside the aforementioned ranges +// (including literal underscores ("_") and equals ("=")) are encoded as UTF8 code points (NOT NCRs) +// and converted to lower-case hex with a leading "=". For example: +// Alph@Bet_50up => _alph=40_bet=5f50up +func EncodeUserLocalpart(str string) string { + strBytes := []byte(str) + var outputBuffer bytes.Buffer + for _, b := range strBytes { + if shouldEncode(b) { + encode(&outputBuffer, b) + } else if shouldEscape(b) { + escape(&outputBuffer, b) + } else { + outputBuffer.WriteByte(b) + } + } + return outputBuffer.String() +} + +// DecodeUserLocalpart decodes the given string back into the original input string. +// Returns an error if the given string is not a valid user ID localpart encoding. +// See http://matrix.org/docs/spec/intro.html#mapping-from-other-character-sets +// +// This decodes quoted-printable bytes back into UTF8, and unescapes casing. For +// example: +// _alph=40_bet=5f50up => Alph@Bet_50up +// Returns an error if the input string contains characters outside the +// range "a-z0-9._=-", has an invalid quote-printable byte (e.g. not hex), or has +// an invalid _ escaped byte (e.g. "_5"). +func DecodeUserLocalpart(str string) (string, error) { + strBytes := []byte(str) + var outputBuffer bytes.Buffer + for i := 0; i < len(strBytes); i++ { + b := strBytes[i] + if !isValidByte(b) { + return "", fmt.Errorf("Byte pos %d: Invalid byte", i) + } + + if b == '_' { // next byte is a-z and should be upper-case or is another _ and should be a literal _ + if i+1 >= len(strBytes) { + return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding but ran out of string", i) + } + if !isValidEscapedChar(strBytes[i+1]) { // invalid escaping + return "", fmt.Errorf("Byte pos %d: expected _[a-z_] encoding", i) + } + if strBytes[i+1] == '_' { + outputBuffer.WriteByte('_') + } else { + outputBuffer.WriteByte(strBytes[i+1] - 0x20) // ASCII shift a-z to A-Z + } + i++ // skip next byte since we just handled it + } else if b == '=' { // next 2 bytes are hex and should be buffered ready to be read as utf8 + if i+2 >= len(strBytes) { + return "", fmt.Errorf("Byte pos: %d: expected quote-printable encoding but ran out of string", i) + } + dst := make([]byte, 1) + _, err := hex.Decode(dst, strBytes[i+1:i+3]) + if err != nil { + return "", err + } + outputBuffer.WriteByte(dst[0]) + i += 2 // skip next 2 bytes since we just handled it + } else { // pass through + outputBuffer.WriteByte(b) + } + } + return outputBuffer.String(), nil +} + +// ExtractUserLocalpart extracts the localpart portion of a user ID. +// See http://matrix.org/docs/spec/intro.html#user-identifiers +func ExtractUserLocalpart(userID string) (string, error) { + if len(userID) == 0 || userID[0] != '@' { + return "", fmt.Errorf("%s is not a valid user id", userID) + } + return strings.TrimPrefix( + strings.SplitN(userID, ":", 2)[0], // @foo:bar:8448 => [ "@foo", "bar:8448" ] + "@", // remove "@" prefix + ), nil +} diff --git a/vendor/github.com/matrix-org/gomatrix/userids_examples_test.go b/vendor/github.com/matrix-org/gomatrix/userids_examples_test.go new file mode 100644 index 0000000..6386b32 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/userids_examples_test.go @@ -0,0 +1,27 @@ +package gomatrix + +import "fmt" + +func ExampleEncodeUserLocalpart() { + localpart := EncodeUserLocalpart("Alph@Bet_50up") + fmt.Println(localpart) + // Output: _alph=40_bet__50up +} + +func ExampleDecodeUserLocalpart() { + localpart, err := DecodeUserLocalpart("_alph=40_bet__50up") + if err != nil { + panic(err) + } + fmt.Println(localpart) + // Output: Alph@Bet_50up +} + +func ExampleExtractUserLocalpart() { + localpart, err := ExtractUserLocalpart("@alice:matrix.org") + if err != nil { + panic(err) + } + fmt.Println(localpart) + // Output: alice +} diff --git a/vendor/github.com/matrix-org/gomatrix/userids_test.go b/vendor/github.com/matrix-org/gomatrix/userids_test.go new file mode 100644 index 0000000..b896740 --- /dev/null +++ b/vendor/github.com/matrix-org/gomatrix/userids_test.go @@ -0,0 +1,86 @@ +package gomatrix + +import ( + "testing" +) + +var useridtests = []struct { + Input string + Output string +}{ + {"Alph@Bet_50up", "_alph=40_bet__50up"}, // The doc example + {"abcdef", "abcdef"}, // no-op + {"i_like_pie_", "i__like__pie__"}, // double underscore escaping + {"ABCDEF", "_a_b_c_d_e_f"}, // all-caps + {"!£", "=21=c2=a3"}, // punctuation and outside ascii range (U+00A3 => c2 a3) + {"___", "______"}, // literal underscores + {"hello-world.", "hello-world."}, // allowed punctuation + {"5+5=10", "5=2b5=3d10"}, // equals sign + {"東方Project", "=e6=9d=b1=e6=96=b9_project"}, // CJK mixed + {" foo bar", "=09foo=20bar"}, // whitespace (tab and space) +} + +func TestEncodeUserLocalpart(t *testing.T) { + for _, u := range useridtests { + out := EncodeUserLocalpart(u.Input) + if out != u.Output { + t.Fatalf("TestEncodeUserLocalpart(%s) => Got: %s Expected: %s", u.Input, out, u.Output) + } + } +} + +func TestDecodeUserLocalpart(t *testing.T) { + for _, u := range useridtests { + in, _ := DecodeUserLocalpart(u.Output) + if in != u.Input { + t.Fatalf("TestDecodeUserLocalpart(%s) => Got: %s Expected: %s", u.Output, in, u.Input) + } + } +} + +var errtests = []struct { + Input string +}{ + {"foo@bar"}, // invalid character @ + {"foo_5bar"}, // invalid character after _ + {"foo_._-bar"}, // multiple invalid characters after _ + {"foo=2Hbar"}, // invalid hex after = + {"foo=2hbar"}, // invalid hex after = (lower-case) + {"foo=======2fbar"}, // multiple invalid hex after = + {"foo=2"}, // end of string after = + {"foo_"}, // end of string after _ +} + +func TestDecodeUserLocalpartErrors(t *testing.T) { + for _, u := range errtests { + out, err := DecodeUserLocalpart(u.Input) + if out != "" { + t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: %s Expected: empty string", u.Input, out) + } + if err == nil { + t.Fatalf("TestDecodeUserLocalpartErrors(%s) => Got: nil error Expected: error", u.Input) + } + } +} + +var localparttests = []struct { + Input string + ExpectOutput string +}{ + {"@foo:bar", "foo"}, + {"@foo:bar:8448", "foo"}, + {"@foo.bar:baz.quuz", "foo.bar"}, +} + +func TestExtractUserLocalpart(t *testing.T) { + for _, u := range localparttests { + out, err := ExtractUserLocalpart(u.Input) + if err != nil { + t.Errorf("TestExtractUserLocalpart(%s) => Error: %s", u.Input, err) + continue + } + if out != u.ExpectOutput { + t.Errorf("TestExtractUserLocalpart(%s) => Got: %s, Want %s", u.Input, out, u.ExpectOutput) + } + } +} diff --git a/vendor/github.com/urfave/cli/.flake8 b/vendor/github.com/urfave/cli/.flake8 new file mode 100644 index 0000000..6deafc2 --- /dev/null +++ b/vendor/github.com/urfave/cli/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 120 diff --git a/vendor/github.com/urfave/cli/.gitignore b/vendor/github.com/urfave/cli/.gitignore new file mode 100644 index 0000000..faf70c4 --- /dev/null +++ b/vendor/github.com/urfave/cli/.gitignore @@ -0,0 +1,2 @@ +*.coverprofile +node_modules/ diff --git a/vendor/github.com/urfave/cli/.travis.yml b/vendor/github.com/urfave/cli/.travis.yml new file mode 100644 index 0000000..cf8d098 --- /dev/null +++ b/vendor/github.com/urfave/cli/.travis.yml @@ -0,0 +1,27 @@ +language: go +sudo: false +dist: trusty +osx_image: xcode8.3 +go: 1.8.x + +os: +- linux +- osx + +cache: + directories: + - node_modules + +before_script: +- go get github.com/urfave/gfmrun/... || true +- go get golang.org/x/tools/cmd/goimports +- if [ ! -f node_modules/.bin/markdown-toc ] ; then + npm install markdown-toc ; + fi + +script: +- ./runtests gen +- ./runtests vet +- ./runtests test +- ./runtests gfmrun +- ./runtests toc diff --git a/vendor/github.com/urfave/cli/CHANGELOG.md b/vendor/github.com/urfave/cli/CHANGELOG.md new file mode 100644 index 0000000..401eae5 --- /dev/null +++ b/vendor/github.com/urfave/cli/CHANGELOG.md @@ -0,0 +1,435 @@ +# Change Log + +**ATTN**: This project uses [semantic versioning](http://semver.org/). + +## [Unreleased] + +## 1.20.0 - 2017-08-10 + +### Fixed + +* `HandleExitCoder` is now correctly iterates over all errors in + a `MultiError`. The exit code is the exit code of the last error or `1` if + there are no `ExitCoder`s in the `MultiError`. +* Fixed YAML file loading on Windows (previously would fail validate the file path) +* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly + propogated +* `ErrWriter` is now passed downwards through command structure to avoid the + need to redefine it +* Pass `Command` context into `OnUsageError` rather than parent context so that + all fields are avaiable +* Errors occuring in `Before` funcs are no longer double printed +* Use `UsageText` in the help templates for commands and subcommands if + defined; otherwise build the usage as before (was previously ignoring this + field) +* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if + a program calls `Set` or `GlobalSet` directly after flag parsing (would + previously only return `true` if the flag was set during parsing) + +### Changed + +* No longer exit the program on command/subcommand error if the error raised is + not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was + determined to be a regression in functionality. See [the + PR](https://github.com/urfave/cli/pull/595) for discussion. + +### Added + +* `CommandsByName` type was added to make it easy to sort `Command`s by name, + alphabetically +* `altsrc` now handles loading of string and int arrays from TOML +* Support for definition of custom help templates for `App` via + `CustomAppHelpTemplate` +* Support for arbitrary key/value fields on `App` to be used with + `CustomAppHelpTemplate` via `ExtraInfo` +* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be + `cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag` + interface to be used. + + +## [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/altsrc/altsrc.go b/vendor/github.com/urfave/cli/altsrc/altsrc.go new file mode 100644 index 0000000..ac34bf6 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/altsrc.go @@ -0,0 +1,3 @@ +package altsrc + +//go:generate python ../generate-flag-types altsrc -i ../flag-types.json -o flag_generated.go diff --git a/vendor/github.com/urfave/cli/altsrc/flag.go b/vendor/github.com/urfave/cli/altsrc/flag.go new file mode 100644 index 0000000..84ef009 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/flag.go @@ -0,0 +1,261 @@ +package altsrc + +import ( + "fmt" + "strconv" + "strings" + "syscall" + + "gopkg.in/urfave/cli.v1" +) + +// FlagInputSourceExtension is an extension interface of cli.Flag that +// allows a value to be set on the existing parsed flags. +type FlagInputSourceExtension interface { + cli.Flag + ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error +} + +// ApplyInputSourceValues iterates over all provided flags and +// executes ApplyInputSourceValue on flags implementing the +// FlagInputSourceExtension interface to initialize these flags +// to an alternate input source. +func ApplyInputSourceValues(context *cli.Context, inputSourceContext InputSourceContext, flags []cli.Flag) error { + for _, f := range flags { + inputSourceExtendedFlag, isType := f.(FlagInputSourceExtension) + if isType { + err := inputSourceExtendedFlag.ApplyInputSourceValue(context, inputSourceContext) + if err != nil { + return err + } + } + } + + return nil +} + +// InitInputSource is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new +// input source based on the func provided. If there is no error it will then apply the new input source to any flags +// that are supported by the input source +func InitInputSource(flags []cli.Flag, createInputSource func() (InputSourceContext, error)) cli.BeforeFunc { + return func(context *cli.Context) error { + inputSource, err := createInputSource() + if err != nil { + return fmt.Errorf("Unable to create input source: inner error: \n'%v'", err.Error()) + } + + return ApplyInputSourceValues(context, inputSource, flags) + } +} + +// InitInputSourceWithContext is used to to setup an InputSourceContext on a cli.Command Before method. It will create a new +// input source based on the func provided with potentially using existing cli.Context values to initialize itself. If there is +// no error it will then apply the new input source to any flags that are supported by the input source +func InitInputSourceWithContext(flags []cli.Flag, createInputSource func(context *cli.Context) (InputSourceContext, error)) cli.BeforeFunc { + return func(context *cli.Context) error { + inputSource, err := createInputSource(context) + if err != nil { + return fmt.Errorf("Unable to create input source with context: inner error: \n'%v'", err.Error()) + } + + return ApplyInputSourceValues(context, inputSource, flags) + } +} + +// ApplyInputSourceValue applies a generic value to the flagSet if required +func (f *GenericFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value, err := isc.Generic(f.GenericFlag.Name) + if err != nil { + return err + } + if value != nil { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, value.String()) + }) + } + } + } + + return nil +} + +// ApplyInputSourceValue applies a StringSlice value to the flagSet if required +func (f *StringSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value, err := isc.StringSlice(f.StringSliceFlag.Name) + if err != nil { + return err + } + if value != nil { + var sliceValue cli.StringSlice = value + eachName(f.Name, func(name string) { + underlyingFlag := f.set.Lookup(f.Name) + if underlyingFlag != nil { + underlyingFlag.Value = &sliceValue + } + }) + } + } + } + return nil +} + +// ApplyInputSourceValue applies a IntSlice value if required +func (f *IntSliceFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value, err := isc.IntSlice(f.IntSliceFlag.Name) + if err != nil { + return err + } + if value != nil { + var sliceValue cli.IntSlice = value + eachName(f.Name, func(name string) { + underlyingFlag := f.set.Lookup(f.Name) + if underlyingFlag != nil { + underlyingFlag.Value = &sliceValue + } + }) + } + } + } + return nil +} + +// ApplyInputSourceValue applies a Bool value to the flagSet if required +func (f *BoolFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value, err := isc.Bool(f.BoolFlag.Name) + if err != nil { + return err + } + if value { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, strconv.FormatBool(value)) + }) + } + } + } + return nil +} + +// ApplyInputSourceValue applies a BoolT value to the flagSet if required +func (f *BoolTFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVar) { + value, err := isc.BoolT(f.BoolTFlag.Name) + if err != nil { + return err + } + if !value { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, strconv.FormatBool(value)) + }) + } + } + } + return nil +} + +// ApplyInputSourceValue applies a String value to the flagSet if required +func (f *StringFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value, err := isc.String(f.StringFlag.Name) + if err != nil { + return err + } + if value != "" { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, value) + }) + } + } + } + return nil +} + +// ApplyInputSourceValue applies a int value to the flagSet if required +func (f *IntFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value, err := isc.Int(f.IntFlag.Name) + if err != nil { + return err + } + if value > 0 { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, strconv.FormatInt(int64(value), 10)) + }) + } + } + } + return nil +} + +// ApplyInputSourceValue applies a Duration value to the flagSet if required +func (f *DurationFlag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value, err := isc.Duration(f.DurationFlag.Name) + if err != nil { + return err + } + if value > 0 { + eachName(f.Name, func(name string) { + f.set.Set(f.Name, value.String()) + }) + } + } + } + return nil +} + +// ApplyInputSourceValue applies a Float64 value to the flagSet if required +func (f *Float64Flag) ApplyInputSourceValue(context *cli.Context, isc InputSourceContext) error { + if f.set != nil { + if !(context.IsSet(f.Name) || isEnvVarSet(f.EnvVar)) { + value, err := isc.Float64(f.Float64Flag.Name) + if err != nil { + return err + } + if value > 0 { + floatStr := float64ToString(value) + eachName(f.Name, func(name string) { + f.set.Set(f.Name, floatStr) + }) + } + } + } + return nil +} + +func isEnvVarSet(envVars string) bool { + for _, envVar := range strings.Split(envVars, ",") { + envVar = strings.TrimSpace(envVar) + if _, ok := syscall.Getenv(envVar); ok { + // TODO: Can't use this for bools as + // set means that it was true or false based on + // Bool flag type, should work for other types + return true + } + } + + return false +} + +func float64ToString(f float64) string { + return fmt.Sprintf("%v", f) +} + +func eachName(longName string, fn func(string)) { + parts := strings.Split(longName, ",") + for _, name := range parts { + name = strings.Trim(name, " ") + fn(name) + } +} diff --git a/vendor/github.com/urfave/cli/altsrc/flag_generated.go b/vendor/github.com/urfave/cli/altsrc/flag_generated.go new file mode 100644 index 0000000..0aeb0b0 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/flag_generated.go @@ -0,0 +1,347 @@ +package altsrc + +import ( + "flag" + + "gopkg.in/urfave/cli.v1" +) + +// WARNING: This file is generated! + +// BoolFlag is the flag type that wraps cli.BoolFlag to allow +// for other values to be specified +type BoolFlag struct { + cli.BoolFlag + set *flag.FlagSet +} + +// NewBoolFlag creates a new BoolFlag +func NewBoolFlag(fl cli.BoolFlag) *BoolFlag { + return &BoolFlag{BoolFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped BoolFlag.Apply +func (f *BoolFlag) Apply(set *flag.FlagSet) { + f.set = set + f.BoolFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped BoolFlag.ApplyWithError +func (f *BoolFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.BoolFlag.ApplyWithError(set) +} + +// BoolTFlag is the flag type that wraps cli.BoolTFlag to allow +// for other values to be specified +type BoolTFlag struct { + cli.BoolTFlag + set *flag.FlagSet +} + +// NewBoolTFlag creates a new BoolTFlag +func NewBoolTFlag(fl cli.BoolTFlag) *BoolTFlag { + return &BoolTFlag{BoolTFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped BoolTFlag.Apply +func (f *BoolTFlag) Apply(set *flag.FlagSet) { + f.set = set + f.BoolTFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped BoolTFlag.ApplyWithError +func (f *BoolTFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.BoolTFlag.ApplyWithError(set) +} + +// DurationFlag is the flag type that wraps cli.DurationFlag to allow +// for other values to be specified +type DurationFlag struct { + cli.DurationFlag + set *flag.FlagSet +} + +// NewDurationFlag creates a new DurationFlag +func NewDurationFlag(fl cli.DurationFlag) *DurationFlag { + return &DurationFlag{DurationFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped DurationFlag.Apply +func (f *DurationFlag) Apply(set *flag.FlagSet) { + f.set = set + f.DurationFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped DurationFlag.ApplyWithError +func (f *DurationFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.DurationFlag.ApplyWithError(set) +} + +// Float64Flag is the flag type that wraps cli.Float64Flag to allow +// for other values to be specified +type Float64Flag struct { + cli.Float64Flag + set *flag.FlagSet +} + +// NewFloat64Flag creates a new Float64Flag +func NewFloat64Flag(fl cli.Float64Flag) *Float64Flag { + return &Float64Flag{Float64Flag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped Float64Flag.Apply +func (f *Float64Flag) Apply(set *flag.FlagSet) { + f.set = set + f.Float64Flag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Float64Flag.ApplyWithError +func (f *Float64Flag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Float64Flag.ApplyWithError(set) +} + +// GenericFlag is the flag type that wraps cli.GenericFlag to allow +// for other values to be specified +type GenericFlag struct { + cli.GenericFlag + set *flag.FlagSet +} + +// NewGenericFlag creates a new GenericFlag +func NewGenericFlag(fl cli.GenericFlag) *GenericFlag { + return &GenericFlag{GenericFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped GenericFlag.Apply +func (f *GenericFlag) Apply(set *flag.FlagSet) { + f.set = set + f.GenericFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped GenericFlag.ApplyWithError +func (f *GenericFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.GenericFlag.ApplyWithError(set) +} + +// Int64Flag is the flag type that wraps cli.Int64Flag to allow +// for other values to be specified +type Int64Flag struct { + cli.Int64Flag + set *flag.FlagSet +} + +// NewInt64Flag creates a new Int64Flag +func NewInt64Flag(fl cli.Int64Flag) *Int64Flag { + return &Int64Flag{Int64Flag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped Int64Flag.Apply +func (f *Int64Flag) Apply(set *flag.FlagSet) { + f.set = set + f.Int64Flag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Int64Flag.ApplyWithError +func (f *Int64Flag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Int64Flag.ApplyWithError(set) +} + +// IntFlag is the flag type that wraps cli.IntFlag to allow +// for other values to be specified +type IntFlag struct { + cli.IntFlag + set *flag.FlagSet +} + +// NewIntFlag creates a new IntFlag +func NewIntFlag(fl cli.IntFlag) *IntFlag { + return &IntFlag{IntFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped IntFlag.Apply +func (f *IntFlag) Apply(set *flag.FlagSet) { + f.set = set + f.IntFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped IntFlag.ApplyWithError +func (f *IntFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.IntFlag.ApplyWithError(set) +} + +// IntSliceFlag is the flag type that wraps cli.IntSliceFlag to allow +// for other values to be specified +type IntSliceFlag struct { + cli.IntSliceFlag + set *flag.FlagSet +} + +// NewIntSliceFlag creates a new IntSliceFlag +func NewIntSliceFlag(fl cli.IntSliceFlag) *IntSliceFlag { + return &IntSliceFlag{IntSliceFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped IntSliceFlag.Apply +func (f *IntSliceFlag) Apply(set *flag.FlagSet) { + f.set = set + f.IntSliceFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped IntSliceFlag.ApplyWithError +func (f *IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.IntSliceFlag.ApplyWithError(set) +} + +// Int64SliceFlag is the flag type that wraps cli.Int64SliceFlag to allow +// for other values to be specified +type Int64SliceFlag struct { + cli.Int64SliceFlag + set *flag.FlagSet +} + +// NewInt64SliceFlag creates a new Int64SliceFlag +func NewInt64SliceFlag(fl cli.Int64SliceFlag) *Int64SliceFlag { + return &Int64SliceFlag{Int64SliceFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped Int64SliceFlag.Apply +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) { + f.set = set + f.Int64SliceFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Int64SliceFlag.ApplyWithError +func (f *Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Int64SliceFlag.ApplyWithError(set) +} + +// StringFlag is the flag type that wraps cli.StringFlag to allow +// for other values to be specified +type StringFlag struct { + cli.StringFlag + set *flag.FlagSet +} + +// NewStringFlag creates a new StringFlag +func NewStringFlag(fl cli.StringFlag) *StringFlag { + return &StringFlag{StringFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped StringFlag.Apply +func (f *StringFlag) Apply(set *flag.FlagSet) { + f.set = set + f.StringFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped StringFlag.ApplyWithError +func (f *StringFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.StringFlag.ApplyWithError(set) +} + +// StringSliceFlag is the flag type that wraps cli.StringSliceFlag to allow +// for other values to be specified +type StringSliceFlag struct { + cli.StringSliceFlag + set *flag.FlagSet +} + +// NewStringSliceFlag creates a new StringSliceFlag +func NewStringSliceFlag(fl cli.StringSliceFlag) *StringSliceFlag { + return &StringSliceFlag{StringSliceFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped StringSliceFlag.Apply +func (f *StringSliceFlag) Apply(set *flag.FlagSet) { + f.set = set + f.StringSliceFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped StringSliceFlag.ApplyWithError +func (f *StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.StringSliceFlag.ApplyWithError(set) +} + +// Uint64Flag is the flag type that wraps cli.Uint64Flag to allow +// for other values to be specified +type Uint64Flag struct { + cli.Uint64Flag + set *flag.FlagSet +} + +// NewUint64Flag creates a new Uint64Flag +func NewUint64Flag(fl cli.Uint64Flag) *Uint64Flag { + return &Uint64Flag{Uint64Flag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped Uint64Flag.Apply +func (f *Uint64Flag) Apply(set *flag.FlagSet) { + f.set = set + f.Uint64Flag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped Uint64Flag.ApplyWithError +func (f *Uint64Flag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.Uint64Flag.ApplyWithError(set) +} + +// UintFlag is the flag type that wraps cli.UintFlag to allow +// for other values to be specified +type UintFlag struct { + cli.UintFlag + set *flag.FlagSet +} + +// NewUintFlag creates a new UintFlag +func NewUintFlag(fl cli.UintFlag) *UintFlag { + return &UintFlag{UintFlag: fl, set: nil} +} + +// Apply saves the flagSet for later usage calls, then calls the +// wrapped UintFlag.Apply +func (f *UintFlag) Apply(set *flag.FlagSet) { + f.set = set + f.UintFlag.Apply(set) +} + +// ApplyWithError saves the flagSet for later usage calls, then calls the +// wrapped UintFlag.ApplyWithError +func (f *UintFlag) ApplyWithError(set *flag.FlagSet) error { + f.set = set + return f.UintFlag.ApplyWithError(set) +} diff --git a/vendor/github.com/urfave/cli/altsrc/flag_test.go b/vendor/github.com/urfave/cli/altsrc/flag_test.go new file mode 100644 index 0000000..cd18294 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/flag_test.go @@ -0,0 +1,336 @@ +package altsrc + +import ( + "flag" + "fmt" + "os" + "strings" + "testing" + "time" + + "gopkg.in/urfave/cli.v1" +) + +type testApplyInputSource struct { + Flag FlagInputSourceExtension + FlagName string + FlagSetName string + Expected string + ContextValueString string + ContextValue flag.Value + EnvVarValue string + EnvVarName string + MapValue interface{} +} + +func TestGenericApplyInputSourceValue(t *testing.T) { + v := &Parser{"abc", "def"} + c := runTest(t, testApplyInputSource{ + Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + FlagName: "test", + MapValue: v, + }) + expect(t, v, c.Generic("test")) +} + +func TestGenericApplyInputSourceMethodContextSet(t *testing.T) { + p := &Parser{"abc", "def"} + c := runTest(t, testApplyInputSource{ + Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}}), + FlagName: "test", + MapValue: &Parser{"efg", "hig"}, + ContextValueString: p.String(), + }) + expect(t, p, c.Generic("test")) +} + +func TestGenericApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewGenericFlag(cli.GenericFlag{Name: "test", Value: &Parser{}, EnvVar: "TEST"}), + FlagName: "test", + MapValue: &Parser{"efg", "hij"}, + EnvVarName: "TEST", + EnvVarValue: "abc,def", + }) + expect(t, &Parser{"abc", "def"}, c.Generic("test")) +} + +func TestStringSliceApplyInputSourceValue(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []interface{}{"hello", "world"}, + }) + expect(t, c.StringSlice("test"), []string{"hello", "world"}) +} + +func TestStringSliceApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []interface{}{"hello", "world"}, + ContextValueString: "ohno", + }) + expect(t, c.StringSlice("test"), []string{"ohno"}) +} + +func TestStringSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringSliceFlag(cli.StringSliceFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: []interface{}{"hello", "world"}, + EnvVarName: "TEST", + EnvVarValue: "oh,no", + }) + expect(t, c.StringSlice("test"), []string{"oh", "no"}) +} + +func TestIntSliceApplyInputSourceValue(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []interface{}{1, 2}, + }) + expect(t, c.IntSlice("test"), []int{1, 2}) +} + +func TestIntSliceApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test"}), + FlagName: "test", + MapValue: []interface{}{1, 2}, + ContextValueString: "3", + }) + expect(t, c.IntSlice("test"), []int{3}) +} + +func TestIntSliceApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntSliceFlag(cli.IntSliceFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: []interface{}{1, 2}, + EnvVarName: "TEST", + EnvVarValue: "3,4", + }) + expect(t, c.IntSlice("test"), []int{3, 4}) +} + +func TestBoolApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + FlagName: "test", + MapValue: true, + }) + expect(t, true, c.Bool("test")) +} + +func TestBoolApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolFlag(cli.BoolFlag{Name: "test"}), + FlagName: "test", + MapValue: false, + ContextValueString: "true", + }) + expect(t, true, c.Bool("test")) +} + +func TestBoolApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolFlag(cli.BoolFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: false, + EnvVarName: "TEST", + EnvVarValue: "true", + }) + expect(t, true, c.Bool("test")) +} + +func TestBoolTApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), + FlagName: "test", + MapValue: false, + }) + expect(t, false, c.BoolT("test")) +} + +func TestBoolTApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test"}), + FlagName: "test", + MapValue: true, + ContextValueString: "false", + }) + expect(t, false, c.BoolT("test")) +} + +func TestBoolTApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewBoolTFlag(cli.BoolTFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: true, + EnvVarName: "TEST", + EnvVarValue: "false", + }) + expect(t, false, c.BoolT("test")) +} + +func TestStringApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + FlagName: "test", + MapValue: "hello", + }) + expect(t, "hello", c.String("test")) +} + +func TestStringApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringFlag(cli.StringFlag{Name: "test"}), + FlagName: "test", + MapValue: "hello", + ContextValueString: "goodbye", + }) + expect(t, "goodbye", c.String("test")) +} + +func TestStringApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewStringFlag(cli.StringFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: "hello", + EnvVarName: "TEST", + EnvVarValue: "goodbye", + }) + expect(t, "goodbye", c.String("test")) +} + +func TestIntApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + FlagName: "test", + MapValue: 15, + }) + expect(t, 15, c.Int("test")) +} + +func TestIntApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntFlag(cli.IntFlag{Name: "test"}), + FlagName: "test", + MapValue: 15, + ContextValueString: "7", + }) + expect(t, 7, c.Int("test")) +} + +func TestIntApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: 15, + EnvVarName: "TEST", + EnvVarValue: "12", + }) + expect(t, 12, c.Int("test")) +} + +func TestDurationApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + FlagName: "test", + MapValue: time.Duration(30 * time.Second), + }) + expect(t, time.Duration(30*time.Second), c.Duration("test")) +} + +func TestDurationApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewDurationFlag(cli.DurationFlag{Name: "test"}), + FlagName: "test", + MapValue: time.Duration(30 * time.Second), + ContextValueString: time.Duration(15 * time.Second).String(), + }) + expect(t, time.Duration(15*time.Second), c.Duration("test")) +} + +func TestDurationApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewDurationFlag(cli.DurationFlag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: time.Duration(30 * time.Second), + EnvVarName: "TEST", + EnvVarValue: time.Duration(15 * time.Second).String(), + }) + expect(t, time.Duration(15*time.Second), c.Duration("test")) +} + +func TestFloat64ApplyInputSourceMethodSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + FlagName: "test", + MapValue: 1.3, + }) + expect(t, 1.3, c.Float64("test")) +} + +func TestFloat64ApplyInputSourceMethodContextSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(cli.Float64Flag{Name: "test"}), + FlagName: "test", + MapValue: 1.3, + ContextValueString: fmt.Sprintf("%v", 1.4), + }) + expect(t, 1.4, c.Float64("test")) +} + +func TestFloat64ApplyInputSourceMethodEnvVarSet(t *testing.T) { + c := runTest(t, testApplyInputSource{ + Flag: NewFloat64Flag(cli.Float64Flag{Name: "test", EnvVar: "TEST"}), + FlagName: "test", + MapValue: 1.3, + EnvVarName: "TEST", + EnvVarValue: fmt.Sprintf("%v", 1.4), + }) + expect(t, 1.4, c.Float64("test")) +} + +func runTest(t *testing.T, test testApplyInputSource) *cli.Context { + inputSource := &MapInputSource{valueMap: map[interface{}]interface{}{test.FlagName: test.MapValue}} + set := flag.NewFlagSet(test.FlagSetName, flag.ContinueOnError) + c := cli.NewContext(nil, set, nil) + if test.EnvVarName != "" && test.EnvVarValue != "" { + os.Setenv(test.EnvVarName, test.EnvVarValue) + defer os.Setenv(test.EnvVarName, "") + } + + test.Flag.Apply(set) + if test.ContextValue != nil { + flag := set.Lookup(test.FlagName) + flag.Value = test.ContextValue + } + if test.ContextValueString != "" { + set.Set(test.FlagName, test.ContextValueString) + } + test.Flag.ApplyInputSourceValue(c, inputSource) + + return c +} + +type Parser [2]string + +func (p *Parser) Set(value string) error { + parts := strings.Split(value, ",") + if len(parts) != 2 { + return fmt.Errorf("invalid format") + } + + (*p)[0] = parts[0] + (*p)[1] = parts[1] + + return nil +} + +func (p *Parser) String() string { + return fmt.Sprintf("%s,%s", p[0], p[1]) +} diff --git a/vendor/github.com/urfave/cli/altsrc/helpers_test.go b/vendor/github.com/urfave/cli/altsrc/helpers_test.go new file mode 100644 index 0000000..3b7f7e9 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/helpers_test.go @@ -0,0 +1,18 @@ +package altsrc + +import ( + "reflect" + "testing" +) + +func expect(t *testing.T, a interface{}, b interface{}) { + if !reflect.DeepEqual(b, a) { + t.Errorf("Expected %#v (type %v) - Got %#v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func refute(t *testing.T, a interface{}, b interface{}) { + if a == b { + t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} diff --git a/vendor/github.com/urfave/cli/altsrc/input_source_context.go b/vendor/github.com/urfave/cli/altsrc/input_source_context.go new file mode 100644 index 0000000..276dcda --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/input_source_context.go @@ -0,0 +1,21 @@ +package altsrc + +import ( + "time" + + "gopkg.in/urfave/cli.v1" +) + +// InputSourceContext is an interface used to allow +// other input sources to be implemented as needed. +type InputSourceContext interface { + Int(name string) (int, error) + Duration(name string) (time.Duration, error) + Float64(name string) (float64, error) + String(name string) (string, error) + StringSlice(name string) ([]string, error) + IntSlice(name string) ([]int, error) + Generic(name string) (cli.Generic, error) + Bool(name string) (bool, error) + BoolT(name string) (bool, error) +} diff --git a/vendor/github.com/urfave/cli/altsrc/map_input_source.go b/vendor/github.com/urfave/cli/altsrc/map_input_source.go new file mode 100644 index 0000000..b3169e0 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/map_input_source.go @@ -0,0 +1,262 @@ +package altsrc + +import ( + "fmt" + "reflect" + "strings" + "time" + + "gopkg.in/urfave/cli.v1" +) + +// MapInputSource implements InputSourceContext to return +// data from the map that is loaded. +type MapInputSource struct { + valueMap map[interface{}]interface{} +} + +// nestedVal checks if the name has '.' delimiters. +// If so, it tries to traverse the tree by the '.' delimited sections to find +// a nested value for the key. +func nestedVal(name string, tree map[interface{}]interface{}) (interface{}, bool) { + if sections := strings.Split(name, "."); len(sections) > 1 { + node := tree + for _, section := range sections[:len(sections)-1] { + if child, ok := node[section]; !ok { + return nil, false + } else { + if ctype, ok := child.(map[interface{}]interface{}); !ok { + return nil, false + } else { + node = ctype + } + } + } + if val, ok := node[sections[len(sections)-1]]; ok { + return val, true + } + } + return nil, false +} + +// Int returns an int from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Int(name string) (int, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(int) + if !isType { + return 0, incorrectTypeForFlagError(name, "int", otherGenericValue) + } + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(int) + if !isType { + return 0, incorrectTypeForFlagError(name, "int", nestedGenericValue) + } + return otherValue, nil + } + + return 0, nil +} + +// Duration returns a duration from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Duration(name string) (time.Duration, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(time.Duration) + if !isType { + return 0, incorrectTypeForFlagError(name, "duration", otherGenericValue) + } + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(time.Duration) + if !isType { + return 0, incorrectTypeForFlagError(name, "duration", nestedGenericValue) + } + return otherValue, nil + } + + return 0, nil +} + +// Float64 returns an float64 from the map if it exists otherwise returns 0 +func (fsm *MapInputSource) Float64(name string) (float64, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(float64) + if !isType { + return 0, incorrectTypeForFlagError(name, "float64", otherGenericValue) + } + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(float64) + if !isType { + return 0, incorrectTypeForFlagError(name, "float64", nestedGenericValue) + } + return otherValue, nil + } + + return 0, nil +} + +// String returns a string from the map if it exists otherwise returns an empty string +func (fsm *MapInputSource) String(name string) (string, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(string) + if !isType { + return "", incorrectTypeForFlagError(name, "string", otherGenericValue) + } + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(string) + if !isType { + return "", incorrectTypeForFlagError(name, "string", nestedGenericValue) + } + return otherValue, nil + } + + return "", nil +} + +// StringSlice returns an []string from the map if it exists otherwise returns nil +func (fsm *MapInputSource) StringSlice(name string) ([]string, error) { + otherGenericValue, exists := fsm.valueMap[name] + if !exists { + otherGenericValue, exists = nestedVal(name, fsm.valueMap) + if !exists { + return nil, nil + } + } + + otherValue, isType := otherGenericValue.([]interface{}) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue) + } + + var stringSlice = make([]string, 0, len(otherValue)) + for i, v := range otherValue { + stringValue, isType := v.(string) + + if !isType { + return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "string", v) + } + + stringSlice = append(stringSlice, stringValue) + } + + return stringSlice, nil +} + +// IntSlice returns an []int from the map if it exists otherwise returns nil +func (fsm *MapInputSource) IntSlice(name string) ([]int, error) { + otherGenericValue, exists := fsm.valueMap[name] + if !exists { + otherGenericValue, exists = nestedVal(name, fsm.valueMap) + if !exists { + return nil, nil + } + } + + otherValue, isType := otherGenericValue.([]interface{}) + if !isType { + return nil, incorrectTypeForFlagError(name, "[]interface{}", otherGenericValue) + } + + var intSlice = make([]int, 0, len(otherValue)) + for i, v := range otherValue { + intValue, isType := v.(int) + + if !isType { + return nil, incorrectTypeForFlagError(fmt.Sprintf("%s[%d]", name, i), "int", v) + } + + intSlice = append(intSlice, intValue) + } + + return intSlice, nil +} + +// Generic returns an cli.Generic from the map if it exists otherwise returns nil +func (fsm *MapInputSource) Generic(name string) (cli.Generic, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(cli.Generic) + if !isType { + return nil, incorrectTypeForFlagError(name, "cli.Generic", otherGenericValue) + } + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(cli.Generic) + if !isType { + return nil, incorrectTypeForFlagError(name, "cli.Generic", nestedGenericValue) + } + return otherValue, nil + } + + return nil, nil +} + +// Bool returns an bool from the map otherwise returns false +func (fsm *MapInputSource) Bool(name string) (bool, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(bool) + if !isType { + return false, incorrectTypeForFlagError(name, "bool", otherGenericValue) + } + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(bool) + if !isType { + return false, incorrectTypeForFlagError(name, "bool", nestedGenericValue) + } + return otherValue, nil + } + + return false, nil +} + +// BoolT returns an bool from the map otherwise returns true +func (fsm *MapInputSource) BoolT(name string) (bool, error) { + otherGenericValue, exists := fsm.valueMap[name] + if exists { + otherValue, isType := otherGenericValue.(bool) + if !isType { + return true, incorrectTypeForFlagError(name, "bool", otherGenericValue) + } + return otherValue, nil + } + nestedGenericValue, exists := nestedVal(name, fsm.valueMap) + if exists { + otherValue, isType := nestedGenericValue.(bool) + if !isType { + return true, incorrectTypeForFlagError(name, "bool", nestedGenericValue) + } + return otherValue, nil + } + + return true, nil +} + +func incorrectTypeForFlagError(name, expectedTypeName string, value interface{}) error { + valueType := reflect.TypeOf(value) + valueTypeName := "" + if valueType != nil { + valueTypeName = valueType.Name() + } + + return fmt.Errorf("Mismatched type for flag '%s'. Expected '%s' but actual is '%s'", name, expectedTypeName, valueTypeName) +} diff --git a/vendor/github.com/urfave/cli/altsrc/toml_command_test.go b/vendor/github.com/urfave/cli/altsrc/toml_command_test.go new file mode 100644 index 0000000..a5053d4 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/toml_command_test.go @@ -0,0 +1,310 @@ +// Disabling building of toml support in cases where golang is 1.0 or 1.1 +// as the encoding library is not implemented or supported. + +// +build go1.2 + +package altsrc + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "gopkg.in/urfave/cli.v1" +) + +func TestCommandTomFileTest(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + defer os.Remove("current.toml") + test := []string{"test-cmd", "--load", "current.toml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileTestGlobalEnvVarWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + defer os.Remove("current.toml") + + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + test := []string{"test-cmd", "--load", "current.toml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileTestGlobalEnvVarWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + defer os.Remove("current.toml") + + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + test := []string{"test-cmd", "--load", "current.toml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileTestSpecifiedFlagWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + defer os.Remove("current.toml") + + test := []string{"test-cmd", "--load", "current.toml", "--test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileTestSpecifiedFlagWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte(`[top] + test = 15`), 0666) + defer os.Remove("current.toml") + + test := []string{"test-cmd", "--load", "current.toml", "--top.test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileTestDefaultValueFileWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + defer os.Remove("current.toml") + + test := []string{"test-cmd", "--load", "current.toml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileTestDefaultValueFileWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + defer os.Remove("current.toml") + + test := []string{"test-cmd", "--load", "current.toml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("test = 15"), 0666) + defer os.Remove("current.toml") + + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", "current.toml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandTomlFileFlagHasDefaultGlobalEnvTomlSetGlobalEnvWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.toml", []byte("[top]\ntest = 15"), 0666) + defer os.Remove("current.toml") + + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", "current.toml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewTomlSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} diff --git a/vendor/github.com/urfave/cli/altsrc/toml_file_loader.go b/vendor/github.com/urfave/cli/altsrc/toml_file_loader.go new file mode 100644 index 0000000..37870fc --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/toml_file_loader.go @@ -0,0 +1,113 @@ +// Disabling building of toml support in cases where golang is 1.0 or 1.1 +// as the encoding library is not implemented or supported. + +// +build go1.2 + +package altsrc + +import ( + "fmt" + "reflect" + + "github.com/BurntSushi/toml" + "gopkg.in/urfave/cli.v1" +) + +type tomlMap struct { + Map map[interface{}]interface{} +} + +func unmarshalMap(i interface{}) (ret map[interface{}]interface{}, err error) { + ret = make(map[interface{}]interface{}) + m := i.(map[string]interface{}) + for key, val := range m { + v := reflect.ValueOf(val) + switch v.Kind() { + case reflect.Bool: + ret[key] = val.(bool) + case reflect.String: + ret[key] = val.(string) + case reflect.Int: + ret[key] = int(val.(int)) + case reflect.Int8: + ret[key] = int(val.(int8)) + case reflect.Int16: + ret[key] = int(val.(int16)) + case reflect.Int32: + ret[key] = int(val.(int32)) + case reflect.Int64: + ret[key] = int(val.(int64)) + case reflect.Uint: + ret[key] = int(val.(uint)) + case reflect.Uint8: + ret[key] = int(val.(uint8)) + case reflect.Uint16: + ret[key] = int(val.(uint16)) + case reflect.Uint32: + ret[key] = int(val.(uint32)) + case reflect.Uint64: + ret[key] = int(val.(uint64)) + case reflect.Float32: + ret[key] = float64(val.(float32)) + case reflect.Float64: + ret[key] = float64(val.(float64)) + case reflect.Map: + if tmp, err := unmarshalMap(val); err == nil { + ret[key] = tmp + } else { + return nil, err + } + case reflect.Array, reflect.Slice: + ret[key] = val.([]interface{}) + default: + return nil, fmt.Errorf("Unsupported: type = %#v", v.Kind()) + } + } + return ret, nil +} + +func (self *tomlMap) UnmarshalTOML(i interface{}) error { + if tmp, err := unmarshalMap(i); err == nil { + self.Map = tmp + } else { + return err + } + return nil +} + +type tomlSourceContext struct { + FilePath string +} + +// NewTomlSourceFromFile creates a new TOML InputSourceContext from a filepath. +func NewTomlSourceFromFile(file string) (InputSourceContext, error) { + tsc := &tomlSourceContext{FilePath: file} + var results tomlMap = tomlMap{} + if err := readCommandToml(tsc.FilePath, &results); err != nil { + return nil, fmt.Errorf("Unable to load TOML file '%s': inner error: \n'%v'", tsc.FilePath, err.Error()) + } + return &MapInputSource{valueMap: results.Map}, nil +} + +// NewTomlSourceFromFlagFunc creates a new TOML InputSourceContext from a provided flag name and source context. +func NewTomlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { + return func(context *cli.Context) (InputSourceContext, error) { + filePath := context.String(flagFileName) + return NewTomlSourceFromFile(filePath) + } +} + +func readCommandToml(filePath string, container interface{}) (err error) { + b, err := loadDataFrom(filePath) + if err != nil { + return err + } + + err = toml.Unmarshal(b, container) + if err != nil { + return err + } + + err = nil + return +} diff --git a/vendor/github.com/urfave/cli/altsrc/yaml_command_test.go b/vendor/github.com/urfave/cli/altsrc/yaml_command_test.go new file mode 100644 index 0000000..9d3f431 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/yaml_command_test.go @@ -0,0 +1,313 @@ +// Disabling building of yaml support in cases where golang is 1.0 or 1.1 +// as the encoding library is not implemented or supported. + +// +build go1.2 + +package altsrc + +import ( + "flag" + "io/ioutil" + "os" + "testing" + + "gopkg.in/urfave/cli.v1" +) + +func TestCommandYamlFileTest(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestGlobalEnvVarWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestGlobalEnvVarWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "10") + defer os.Setenv("THE_TEST", "") + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 10) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestSpecifiedFlagWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml", "--test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestSpecifiedFlagWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml", "--top.test", "7"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 7) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestDefaultValueFileWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileTestDefaultValueFileWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 15) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWins(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte("test: 15"), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "test", Value: 7, EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} + +func TestCommandYamlFileFlagHasDefaultGlobalEnvYamlSetGlobalEnvWinsNested(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", 0) + ioutil.WriteFile("current.yaml", []byte(`top: + test: 15`), 0666) + defer os.Remove("current.yaml") + + os.Setenv("THE_TEST", "11") + defer os.Setenv("THE_TEST", "") + + test := []string{"test-cmd", "--load", "current.yaml"} + set.Parse(test) + + c := cli.NewContext(app, set, nil) + + command := &cli.Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(c *cli.Context) error { + val := c.Int("top.test") + expect(t, val, 11) + return nil + }, + Flags: []cli.Flag{ + NewIntFlag(cli.IntFlag{Name: "top.test", Value: 7, EnvVar: "THE_TEST"}), + cli.StringFlag{Name: "load"}}, + } + command.Before = InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) + err := command.Run(c) + + expect(t, err, nil) +} diff --git a/vendor/github.com/urfave/cli/altsrc/yaml_file_loader.go b/vendor/github.com/urfave/cli/altsrc/yaml_file_loader.go new file mode 100644 index 0000000..dd808d5 --- /dev/null +++ b/vendor/github.com/urfave/cli/altsrc/yaml_file_loader.go @@ -0,0 +1,92 @@ +// Disabling building of yaml support in cases where golang is 1.0 or 1.1 +// as the encoding library is not implemented or supported. + +// +build go1.2 + +package altsrc + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "os" + "runtime" + "strings" + + "gopkg.in/urfave/cli.v1" + + "gopkg.in/yaml.v2" +) + +type yamlSourceContext struct { + FilePath string +} + +// NewYamlSourceFromFile creates a new Yaml InputSourceContext from a filepath. +func NewYamlSourceFromFile(file string) (InputSourceContext, error) { + ysc := &yamlSourceContext{FilePath: file} + var results map[interface{}]interface{} + err := readCommandYaml(ysc.FilePath, &results) + if err != nil { + return nil, fmt.Errorf("Unable to load Yaml file '%s': inner error: \n'%v'", ysc.FilePath, err.Error()) + } + + return &MapInputSource{valueMap: results}, nil +} + +// NewYamlSourceFromFlagFunc creates a new Yaml InputSourceContext from a provided flag name and source context. +func NewYamlSourceFromFlagFunc(flagFileName string) func(context *cli.Context) (InputSourceContext, error) { + return func(context *cli.Context) (InputSourceContext, error) { + filePath := context.String(flagFileName) + return NewYamlSourceFromFile(filePath) + } +} + +func readCommandYaml(filePath string, container interface{}) (err error) { + b, err := loadDataFrom(filePath) + if err != nil { + return err + } + + err = yaml.Unmarshal(b, container) + if err != nil { + return err + } + + err = nil + return +} + +func loadDataFrom(filePath string) ([]byte, error) { + u, err := url.Parse(filePath) + if err != nil { + return nil, err + } + + if u.Host != "" { // i have a host, now do i support the scheme? + switch u.Scheme { + case "http", "https": + res, err := http.Get(filePath) + if err != nil { + return nil, err + } + return ioutil.ReadAll(res.Body) + default: + return nil, fmt.Errorf("scheme of %s is unsupported", filePath) + } + } else if u.Path != "" { // i dont have a host, but I have a path. I am a local file. + if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { + return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) + } + return ioutil.ReadFile(filePath) + } else if runtime.GOOS == "windows" && strings.Contains(u.String(), "\\") { + // on Windows systems u.Path is always empty, so we need to check the string directly. + if _, notFoundFileErr := os.Stat(filePath); notFoundFileErr != nil { + return nil, fmt.Errorf("Cannot read from file: '%s' because it does not exist.", filePath) + } + return ioutil.ReadFile(filePath) + } else { + return nil, fmt.Errorf("unable to determine how to load from path %s", filePath) + } +} 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/app_test.go b/vendor/github.com/urfave/cli/app_test.go new file mode 100644 index 0000000..e14ddaf --- /dev/null +++ b/vendor/github.com/urfave/cli/app_test.go @@ -0,0 +1,1742 @@ +package cli + +import ( + "bytes" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "reflect" + "strings" + "testing" +) + +var ( + lastExitCode = 0 + fakeOsExiter = func(rc int) { + lastExitCode = rc + } + fakeErrWriter = &bytes.Buffer{} +) + +func init() { + OsExiter = fakeOsExiter + ErrWriter = fakeErrWriter +} + +type opCounts struct { + Total, BashComplete, OnUsageError, Before, CommandNotFound, Action, After, SubCommand int +} + +func ExampleApp_Run() { + // set args for examples sake + os.Args = []string{"greet", "--name", "Jeremy"} + + app := NewApp() + app.Name = "greet" + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + } + app.Action = func(c *Context) error { + fmt.Printf("Hello %v\n", c.String("name")) + return nil + } + app.UsageText = "app [first_arg] [second_arg]" + app.Author = "Harrison" + app.Email = "harrison@lolwut.com" + app.Authors = []Author{{Name: "Oliver Allen", Email: "oliver@toyshop.com"}} + app.Run(os.Args) + // Output: + // Hello Jeremy +} + +func ExampleApp_Run_subcommand() { + // set args for examples sake + os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} + app := NewApp() + app.Name = "say" + app.Commands = []Command{ + { + Name: "hello", + Aliases: []string{"hi"}, + Usage: "use it to see a description", + Description: "This is how we describe hello the function", + Subcommands: []Command{ + { + Name: "english", + Aliases: []string{"en"}, + Usage: "sends a greeting in english", + Description: "greets someone in english", + Flags: []Flag{ + StringFlag{ + Name: "name", + Value: "Bob", + Usage: "Name of the person to greet", + }, + }, + Action: func(c *Context) error { + fmt.Println("Hello,", c.String("name")) + return nil + }, + }, + }, + }, + } + + app.Run(os.Args) + // Output: + // Hello, Jeremy +} + +func ExampleApp_Run_appHelp() { + // set args for examples sake + os.Args = []string{"greet", "help"} + + app := NewApp() + app.Name = "greet" + app.Version = "0.1.0" + app.Description = "This is how we describe greet the app" + app.Authors = []Author{ + {Name: "Harrison", Email: "harrison@lolwut.com"}, + {Name: "Oliver Allen", Email: "oliver@toyshop.com"}, + } + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + } + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, + }, + } + app.Run(os.Args) + // Output: + // NAME: + // greet - A new cli application + // + // USAGE: + // greet [global options] command [command options] [arguments...] + // + // VERSION: + // 0.1.0 + // + // DESCRIPTION: + // This is how we describe greet the app + // + // AUTHORS: + // Harrison + // Oliver Allen + // + // COMMANDS: + // describeit, d use it to see a description + // help, h Shows a list of commands or help for one command + // + // GLOBAL OPTIONS: + // --name value a name to say (default: "bob") + // --help, -h show help + // --version, -v print the version +} + +func ExampleApp_Run_commandHelp() { + // set args for examples sake + os.Args = []string{"greet", "h", "describeit"} + + app := NewApp() + app.Name = "greet" + app.Flags = []Flag{ + StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, + } + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, + }, + } + app.Run(os.Args) + // Output: + // NAME: + // greet describeit - use it to see a description + // + // USAGE: + // greet describeit [arguments...] + // + // DESCRIPTION: + // This is how we describe describeit the function +} + +func ExampleApp_Run_noAction() { + app := App{} + app.Name = "greet" + app.Run([]string{"greet"}) + // Output: + // NAME: + // greet + // + // USAGE: + // [global options] command [command options] [arguments...] + // + // COMMANDS: + // help, h Shows a list of commands or help for one command + // + // GLOBAL OPTIONS: + // --help, -h show help + // --version, -v print the version +} + +func ExampleApp_Run_subcommandNoAction() { + app := App{} + app.Name = "greet" + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + }, + } + app.Run([]string{"greet", "describeit"}) + // Output: + // NAME: + // describeit - use it to see a description + // + // USAGE: + // describeit [arguments...] + // + // DESCRIPTION: + // This is how we describe describeit the function + +} + +func ExampleApp_Run_bashComplete() { + // set args for examples sake + os.Args = []string{"greet", "--generate-bash-completion"} + + app := NewApp() + app.Name = "greet" + app.EnableBashCompletion = true + app.Commands = []Command{ + { + Name: "describeit", + Aliases: []string{"d"}, + Usage: "use it to see a description", + Description: "This is how we describe describeit the function", + Action: func(c *Context) error { + fmt.Printf("i like to describe things") + return nil + }, + }, { + Name: "next", + Usage: "next example", + Description: "more stuff to see when generating bash completion", + Action: func(c *Context) error { + fmt.Printf("the next example") + return nil + }, + }, + } + + app.Run(os.Args) + // Output: + // describeit + // d + // next + // help + // h +} + +func TestApp_Run(t *testing.T) { + s := "" + + app := NewApp() + app.Action = func(c *Context) error { + s = s + c.Args().First() + return nil + } + + err := app.Run([]string{"command", "foo"}) + expect(t, err, nil) + err = app.Run([]string{"command", "bar"}) + expect(t, err, nil) + expect(t, s, "foobar") +} + +var commandAppTests = []struct { + name string + expected bool +}{ + {"foobar", true}, + {"batbaz", true}, + {"b", true}, + {"f", true}, + {"bat", false}, + {"nothing", false}, +} + +func TestApp_Command(t *testing.T) { + app := NewApp() + fooCommand := Command{Name: "foobar", Aliases: []string{"f"}} + batCommand := Command{Name: "batbaz", Aliases: []string{"b"}} + app.Commands = []Command{ + fooCommand, + batCommand, + } + + for _, test := range commandAppTests { + expect(t, app.Command(test.name) != nil, test.expected) + } +} + +func TestApp_Setup_defaultsWriter(t *testing.T) { + app := &App{} + app.Setup() + expect(t, app.Writer, os.Stdout) +} + +func TestApp_CommandWithArgBeforeFlags(t *testing.T) { + var parsedOption, firstArg string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + firstArg = c.Args().First() + return nil + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) + + expect(t, parsedOption, "my-option") + expect(t, firstArg, "my-arg") +} + +func TestApp_RunAsSubcommandParseFlags(t *testing.T) { + var context *Context + + a := NewApp() + a.Commands = []Command{ + { + Name: "foo", + Action: func(c *Context) error { + context = c + return nil + }, + Flags: []Flag{ + StringFlag{ + Name: "lang", + Value: "english", + Usage: "language for the greeting", + }, + }, + Before: func(_ *Context) error { return nil }, + }, + } + a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) + + expect(t, context.Args().Get(0), "abcd") + expect(t, context.String("lang"), "spanish") +} + +func TestApp_RunAsSubCommandIncorrectUsage(t *testing.T) { + a := App{ + Flags: []Flag{ + StringFlag{Name: "--foo"}, + }, + Writer: bytes.NewBufferString(""), + } + + set := flag.NewFlagSet("", flag.ContinueOnError) + set.Parse([]string{"", "---foo"}) + c := &Context{flagSet: set} + + err := a.RunAsSubcommand(c) + + expect(t, err, errors.New("bad flag syntax: ---foo")) +} + +func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { + var parsedOption string + var args []string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + StringFlag{Name: "option", Value: "", Usage: "some option"}, + }, + Action: func(c *Context) error { + parsedOption = c.String("option") + args = c.Args() + return nil + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) + + expect(t, parsedOption, "my-option") + expect(t, args[0], "my-arg") + expect(t, args[1], "--") + expect(t, args[2], "--notARealFlag") +} + +func TestApp_CommandWithDash(t *testing.T) { + var args []string + + app := NewApp() + command := Command{ + Name: "cmd", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-"}) + + expect(t, args[0], "my-arg") + expect(t, args[1], "-") +} + +func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { + var args []string + + app := NewApp() + command := Command{ + Name: "cmd", + Action: func(c *Context) error { + args = c.Args() + return nil + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) + + expect(t, args[0], "my-arg") + expect(t, args[1], "--") + expect(t, args[2], "notAFlagAtAll") +} + +func TestApp_VisibleCommands(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Name: "frob", + HelpName: "foo frob", + Action: func(_ *Context) error { return nil }, + }, + { + Name: "frib", + HelpName: "foo frib", + Hidden: true, + Action: func(_ *Context) error { return nil }, + }, + } + + app.Setup() + expected := []Command{ + app.Commands[0], + app.Commands[2], // help + } + actual := app.VisibleCommands() + expect(t, len(expected), len(actual)) + for i, actualCommand := range actual { + expectedCommand := expected[i] + + if expectedCommand.Action != nil { + // comparing func addresses is OK! + expect(t, fmt.Sprintf("%p", expectedCommand.Action), fmt.Sprintf("%p", actualCommand.Action)) + } + + // nil out funcs, as they cannot be compared + // (https://github.com/golang/go/issues/8554) + expectedCommand.Action = nil + actualCommand.Action = nil + + if !reflect.DeepEqual(expectedCommand, actualCommand) { + t.Errorf("expected\n%#v\n!=\n%#v", expectedCommand, actualCommand) + } + } +} + +func TestApp_Float64Flag(t *testing.T) { + var meters float64 + + app := NewApp() + app.Flags = []Flag{ + Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, + } + app.Action = func(c *Context) error { + meters = c.Float64("height") + return nil + } + + app.Run([]string{"", "--height", "1.93"}) + expect(t, meters, 1.93) +} + +func TestApp_ParseSliceFlags(t *testing.T) { + var parsedOption, firstArg string + var parsedIntSlice []int + var parsedStringSlice []string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + IntSliceFlag{Name: "p", Value: &IntSlice{}, Usage: "set one or more ip addr"}, + StringSliceFlag{Name: "ip", Value: &StringSlice{}, Usage: "set one or more ports to open"}, + }, + Action: func(c *Context) error { + parsedIntSlice = c.IntSlice("p") + parsedStringSlice = c.StringSlice("ip") + parsedOption = c.String("option") + firstArg = c.Args().First() + return nil + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) + + IntsEquals := func(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true + } + + StrsEquals := func(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true + } + var expectedIntSlice = []int{22, 80} + var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} + + if !IntsEquals(parsedIntSlice, expectedIntSlice) { + t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice) + } + + if !StrsEquals(parsedStringSlice, expectedStringSlice) { + t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice) + } +} + +func TestApp_ParseSliceFlagsWithMissingValue(t *testing.T) { + var parsedIntSlice []int + var parsedStringSlice []string + + app := NewApp() + command := Command{ + Name: "cmd", + Flags: []Flag{ + IntSliceFlag{Name: "a", Usage: "set numbers"}, + StringSliceFlag{Name: "str", Usage: "set strings"}, + }, + Action: func(c *Context) error { + parsedIntSlice = c.IntSlice("a") + parsedStringSlice = c.StringSlice("str") + return nil + }, + } + app.Commands = []Command{command} + + app.Run([]string{"", "cmd", "my-arg", "-a", "2", "-str", "A"}) + + var expectedIntSlice = []int{2} + var expectedStringSlice = []string{"A"} + + if parsedIntSlice[0] != expectedIntSlice[0] { + t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) + } + + if parsedStringSlice[0] != expectedStringSlice[0] { + t.Errorf("%v does not match %v", parsedIntSlice[0], expectedIntSlice[0]) + } +} + +func TestApp_DefaultStdout(t *testing.T) { + app := NewApp() + + if app.Writer != os.Stdout { + t.Error("Default output writer not set.") + } +} + +type mockWriter struct { + written []byte +} + +func (fw *mockWriter) Write(p []byte) (n int, err error) { + if fw.written == nil { + fw.written = p + } else { + fw.written = append(fw.written, p...) + } + + return len(p), nil +} + +func (fw *mockWriter) GetWritten() (b []byte) { + return fw.written +} + +func TestApp_SetStdout(t *testing.T) { + w := &mockWriter{} + + app := NewApp() + app.Name = "test" + app.Writer = w + + err := app.Run([]string{"help"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if len(w.written) == 0 { + t.Error("App did not write output to desired writer.") + } +} + +func TestApp_BeforeFunc(t *testing.T) { + counts := &opCounts{} + beforeError := fmt.Errorf("fail") + var err error + + app := NewApp() + + app.Before = func(c *Context) error { + counts.Total++ + counts.Before = counts.Total + s := c.String("opt") + if s == "fail" { + return beforeError + } + + return nil + } + + app.Commands = []Command{ + { + Name: "sub", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, + }, + } + + app.Flags = []Flag{ + StringFlag{Name: "opt"}, + } + + // run with the Before() func succeeding + err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if counts.Before != 1 { + t.Errorf("Before() not executed when expected") + } + + if counts.SubCommand != 2 { + t.Errorf("Subcommand not executed when expected") + } + + // reset + counts = &opCounts{} + + // run with the Before() func failing + err = app.Run([]string{"command", "--opt", "fail", "sub"}) + + // should be the same error produced by the Before func + if err != beforeError { + t.Errorf("Run error expected, but not received") + } + + if counts.Before != 1 { + t.Errorf("Before() not executed when expected") + } + + if counts.SubCommand != 0 { + t.Errorf("Subcommand executed when NOT expected") + } + + // reset + counts = &opCounts{} + + afterError := errors.New("fail again") + app.After = func(_ *Context) error { + return afterError + } + + // run with the Before() func failing, wrapped by After() + err = app.Run([]string{"command", "--opt", "fail", "sub"}) + + // should be the same error produced by the Before func + if _, ok := err.(MultiError); !ok { + t.Errorf("MultiError expected, but not received") + } + + if counts.Before != 1 { + t.Errorf("Before() not executed when expected") + } + + if counts.SubCommand != 0 { + t.Errorf("Subcommand executed when NOT expected") + } +} + +func TestApp_AfterFunc(t *testing.T) { + counts := &opCounts{} + afterError := fmt.Errorf("fail") + var err error + + app := NewApp() + + app.After = func(c *Context) error { + counts.Total++ + counts.After = counts.Total + s := c.String("opt") + if s == "fail" { + return afterError + } + + return nil + } + + app.Commands = []Command{ + { + Name: "sub", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, + }, + } + + app.Flags = []Flag{ + StringFlag{Name: "opt"}, + } + + // run with the After() func succeeding + err = app.Run([]string{"command", "--opt", "succeed", "sub"}) + + if err != nil { + t.Fatalf("Run error: %s", err) + } + + if counts.After != 2 { + t.Errorf("After() not executed when expected") + } + + if counts.SubCommand != 1 { + t.Errorf("Subcommand not executed when expected") + } + + // reset + counts = &opCounts{} + + // run with the Before() func failing + err = app.Run([]string{"command", "--opt", "fail", "sub"}) + + // should be the same error produced by the Before func + if err != afterError { + t.Errorf("Run error expected, but not received") + } + + if counts.After != 2 { + t.Errorf("After() not executed when expected") + } + + if counts.SubCommand != 1 { + t.Errorf("Subcommand not executed when expected") + } +} + +func TestAppNoHelpFlag(t *testing.T) { + oldFlag := HelpFlag + defer func() { + HelpFlag = oldFlag + }() + + HelpFlag = BoolFlag{} + + app := NewApp() + app.Writer = ioutil.Discard + err := app.Run([]string{"test", "-h"}) + + if err != flag.ErrHelp { + t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) + } +} + +func TestAppHelpPrinter(t *testing.T) { + oldPrinter := HelpPrinter + defer func() { + HelpPrinter = oldPrinter + }() + + var wasCalled = false + HelpPrinter = func(w io.Writer, template string, data interface{}) { + wasCalled = true + } + + app := NewApp() + app.Run([]string{"-h"}) + + if wasCalled == false { + t.Errorf("Help printer expected to be called, but was not") + } +} + +func TestApp_VersionPrinter(t *testing.T) { + oldPrinter := VersionPrinter + defer func() { + VersionPrinter = oldPrinter + }() + + var wasCalled = false + VersionPrinter = func(c *Context) { + wasCalled = true + } + + app := NewApp() + ctx := NewContext(app, nil, nil) + ShowVersion(ctx) + + if wasCalled == false { + t.Errorf("Version printer expected to be called, but was not") + } +} + +func TestApp_CommandNotFound(t *testing.T) { + counts := &opCounts{} + app := NewApp() + + app.CommandNotFound = func(c *Context, command string) { + counts.Total++ + counts.CommandNotFound = counts.Total + } + + app.Commands = []Command{ + { + Name: "bar", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, + }, + } + + app.Run([]string{"command", "foo"}) + + expect(t, counts.CommandNotFound, 1) + expect(t, counts.SubCommand, 0) + expect(t, counts.Total, 1) +} + +func TestApp_OrderOfOperations(t *testing.T) { + counts := &opCounts{} + + resetCounts := func() { counts = &opCounts{} } + + app := NewApp() + app.EnableBashCompletion = true + app.BashComplete = func(c *Context) { + counts.Total++ + counts.BashComplete = counts.Total + } + + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + counts.Total++ + counts.OnUsageError = counts.Total + return errors.New("hay OnUsageError") + } + + beforeNoError := func(c *Context) error { + counts.Total++ + counts.Before = counts.Total + return nil + } + + beforeError := func(c *Context) error { + counts.Total++ + counts.Before = counts.Total + return errors.New("hay Before") + } + + app.Before = beforeNoError + app.CommandNotFound = func(c *Context, command string) { + counts.Total++ + counts.CommandNotFound = counts.Total + } + + afterNoError := func(c *Context) error { + counts.Total++ + counts.After = counts.Total + return nil + } + + afterError := func(c *Context) error { + counts.Total++ + counts.After = counts.Total + return errors.New("hay After") + } + + app.After = afterNoError + app.Commands = []Command{ + { + Name: "bar", + Action: func(c *Context) error { + counts.Total++ + counts.SubCommand = counts.Total + return nil + }, + }, + } + + app.Action = func(c *Context) error { + counts.Total++ + counts.Action = counts.Total + return nil + } + + _ = app.Run([]string{"command", "--nope"}) + expect(t, counts.OnUsageError, 1) + expect(t, counts.Total, 1) + + resetCounts() + + _ = app.Run([]string{"command", "--generate-bash-completion"}) + expect(t, counts.BashComplete, 1) + expect(t, counts.Total, 1) + + resetCounts() + + oldOnUsageError := app.OnUsageError + app.OnUsageError = nil + _ = app.Run([]string{"command", "--nope"}) + expect(t, counts.Total, 0) + app.OnUsageError = oldOnUsageError + + resetCounts() + + _ = app.Run([]string{"command", "foo"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.CommandNotFound, 0) + expect(t, counts.Action, 2) + expect(t, counts.After, 3) + expect(t, counts.Total, 3) + + resetCounts() + + app.Before = beforeError + _ = app.Run([]string{"command", "bar"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.After, 2) + expect(t, counts.Total, 2) + app.Before = beforeNoError + + resetCounts() + + app.After = nil + _ = app.Run([]string{"command", "bar"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.SubCommand, 2) + expect(t, counts.Total, 2) + app.After = afterNoError + + resetCounts() + + app.After = afterError + err := app.Run([]string{"command", "bar"}) + if err == nil { + t.Fatalf("expected a non-nil error") + } + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.SubCommand, 2) + expect(t, counts.After, 3) + expect(t, counts.Total, 3) + app.After = afterNoError + + resetCounts() + + oldCommands := app.Commands + app.Commands = nil + _ = app.Run([]string{"command"}) + expect(t, counts.OnUsageError, 0) + expect(t, counts.Before, 1) + expect(t, counts.Action, 2) + expect(t, counts.After, 3) + expect(t, counts.Total, 3) + app.Commands = oldCommands +} + +func TestApp_Run_CommandWithSubcommandHasHelpTopic(t *testing.T) { + var subcommandHelpTopics = [][]string{ + {"command", "foo", "--help"}, + {"command", "foo", "-h"}, + {"command", "foo", "help"}, + } + + for _, flagSet := range subcommandHelpTopics { + t.Logf("==> checking with flags %v", flagSet) + + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + + subCmdBar := Command{ + Name: "bar", + Usage: "does bar things", + } + subCmdBaz := Command{ + Name: "baz", + Usage: "does baz things", + } + cmd := Command{ + Name: "foo", + Description: "descriptive wall of text about how it does foo things", + Subcommands: []Command{subCmdBar, subCmdBaz}, + Action: func(c *Context) error { return nil }, + } + + app.Commands = []Command{cmd} + err := app.Run(flagSet) + + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if strings.Contains(output, "No help topic for") { + t.Errorf("expect a help topic, got none: \n%q", output) + } + + for _, shouldContain := range []string{ + cmd.Name, cmd.Description, + subCmdBar.Name, subCmdBar.Usage, + subCmdBaz.Name, subCmdBaz.Usage, + } { + if !strings.Contains(output, shouldContain) { + t.Errorf("want help to contain %q, did not: \n%q", shouldContain, output) + } + } + } +} + +func TestApp_Run_SubcommandFullPath(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "command foo bar - does bar things") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "command foo bar [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + +func TestApp_Run_SubcommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + HelpName: "custom", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "custom - does bar things") { + t.Errorf("expected HelpName for subcommand: %s", output) + } + if !strings.Contains(output, "custom [arguments...]") { + t.Errorf("expected HelpName to subcommand: %s", output) + } +} + +func TestApp_Run_CommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "command" + subCmd := Command{ + Name: "bar", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + HelpName: "custom", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "bar", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "command foo bar - does bar things") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "command foo bar [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + +func TestApp_Run_CommandSubcommandHelpName(t *testing.T) { + app := NewApp() + buf := new(bytes.Buffer) + app.Writer = buf + app.Name = "base" + subCmd := Command{ + Name: "bar", + HelpName: "custom", + Usage: "does bar things", + } + cmd := Command{ + Name: "foo", + Description: "foo commands", + Subcommands: []Command{subCmd}, + } + app.Commands = []Command{cmd} + + err := app.Run([]string{"command", "foo", "--help"}) + if err != nil { + t.Error(err) + } + + output := buf.String() + if !strings.Contains(output, "base foo - foo commands") { + t.Errorf("expected full path to subcommand: %s", output) + } + if !strings.Contains(output, "base foo command [command options] [arguments...]") { + t.Errorf("expected full path to subcommand: %s", output) + } +} + +func TestApp_Run_Help(t *testing.T) { + var helpArguments = [][]string{{"boom", "--help"}, {"boom", "-h"}, {"boom", "help"}} + + for _, args := range helpArguments { + buf := new(bytes.Buffer) + + t.Logf("==> checking with arguments %v", args) + + app := NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Writer = buf + app.Action = func(c *Context) error { + buf.WriteString("boom I say!") + return nil + } + + err := app.Run(args) + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "boom - make an explosive entrance") { + t.Errorf("want help to contain %q, did not: \n%q", "boom - make an explosive entrance", output) + } + } +} + +func TestApp_Run_Version(t *testing.T) { + var versionArguments = [][]string{{"boom", "--version"}, {"boom", "-v"}} + + for _, args := range versionArguments { + buf := new(bytes.Buffer) + + t.Logf("==> checking with arguments %v", args) + + app := NewApp() + app.Name = "boom" + app.Usage = "make an explosive entrance" + app.Version = "0.1.0" + app.Writer = buf + app.Action = func(c *Context) error { + buf.WriteString("boom I say!") + return nil + } + + err := app.Run(args) + if err != nil { + t.Error(err) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "0.1.0") { + t.Errorf("want version to contain %q, did not: \n%q", "0.1.0", output) + } + } +} + +func TestApp_Run_Categories(t *testing.T) { + app := NewApp() + app.Name = "categories" + app.HideHelp = true + app.Commands = []Command{ + { + Name: "command1", + Category: "1", + }, + { + Name: "command2", + Category: "1", + }, + { + Name: "command3", + Category: "2", + }, + } + buf := new(bytes.Buffer) + app.Writer = buf + + app.Run([]string{"categories"}) + + expect := CommandCategories{ + &CommandCategory{ + Name: "1", + Commands: []Command{ + app.Commands[0], + app.Commands[1], + }, + }, + &CommandCategory{ + Name: "2", + Commands: []Command{ + app.Commands[2], + }, + }, + } + if !reflect.DeepEqual(app.Categories(), expect) { + t.Fatalf("expected categories %#v, to equal %#v", app.Categories(), expect) + } + + output := buf.String() + t.Logf("output: %q\n", buf.Bytes()) + + if !strings.Contains(output, "1:\n command1") { + t.Errorf("want buffer to include category %q, did not: \n%q", "1:\n command1", output) + } +} + +func TestApp_VisibleCategories(t *testing.T) { + app := NewApp() + app.Name = "visible-categories" + app.HideHelp = true + app.Commands = []Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, + } + + expected := []*CommandCategory{ + { + Name: "2", + Commands: []Command{ + app.Commands[1], + }, + }, + { + Name: "3", + Commands: []Command{ + app.Commands[2], + }, + }, + } + + app.Setup() + expect(t, expected, app.VisibleCategories()) + + app = NewApp() + app.Name = "visible-categories" + app.HideHelp = true + app.Commands = []Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + }, + } + + expected = []*CommandCategory{ + { + Name: "3", + Commands: []Command{ + app.Commands[2], + }, + }, + } + + app.Setup() + expect(t, expected, app.VisibleCategories()) + + app = NewApp() + app.Name = "visible-categories" + app.HideHelp = true + app.Commands = []Command{ + { + Name: "command1", + Category: "1", + HelpName: "foo command1", + Hidden: true, + }, + { + Name: "command2", + Category: "2", + HelpName: "foo command2", + Hidden: true, + }, + { + Name: "command3", + Category: "3", + HelpName: "foo command3", + Hidden: true, + }, + } + + expected = []*CommandCategory{} + + app.Setup() + expect(t, expected, app.VisibleCategories()) +} + +func TestApp_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { + app := NewApp() + app.Action = func(c *Context) error { return nil } + app.Before = func(c *Context) error { return fmt.Errorf("before error") } + app.After = func(c *Context) error { return fmt.Errorf("after error") } + + err := app.Run([]string{"foo"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} + +func TestApp_Run_SubcommandDoesNotOverwriteErrorFromBefore(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Subcommands: []Command{ + { + Name: "sub", + }, + }, + Name: "bar", + Before: func(c *Context) error { return fmt.Errorf("before error") }, + After: func(c *Context) error { return fmt.Errorf("after error") }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} + +func TestApp_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect no subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + { + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestApp_OnUsageError_WithWrongFlagValue_ForSubcommand(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{Name: "flag"}, + } + app.OnUsageError = func(c *Context, err error, isSubcommand bool) error { + if isSubcommand { + t.Errorf("Expect subcommand") + } + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + } + app.Commands = []Command{ + { + Name: "bar", + }, + } + + err := app.Run([]string{"foo", "--flag=wrong", "bar"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +// A custom flag that conforms to the relevant interfaces, but has none of the +// fields that the other flag types do. +type customBoolFlag struct { + Nombre string +} + +// Don't use the normal FlagStringer +func (c *customBoolFlag) String() string { + return "***" + c.Nombre + "***" +} + +func (c *customBoolFlag) GetName() string { + return c.Nombre +} + +func (c *customBoolFlag) Apply(set *flag.FlagSet) { + set.String(c.Nombre, c.Nombre, "") +} + +func TestCustomFlagsUnused(t *testing.T) { + app := NewApp() + app.Flags = []Flag{&customBoolFlag{"custom"}} + + err := app.Run([]string{"foo"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestCustomFlagsUsed(t *testing.T) { + app := NewApp() + app.Flags = []Flag{&customBoolFlag{"custom"}} + + err := app.Run([]string{"foo", "--custom=bar"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestCustomHelpVersionFlags(t *testing.T) { + app := NewApp() + + // Be sure to reset the global flags + defer func(helpFlag Flag, versionFlag Flag) { + HelpFlag = helpFlag + VersionFlag = versionFlag + }(HelpFlag, VersionFlag) + + HelpFlag = &customBoolFlag{"help-custom"} + VersionFlag = &customBoolFlag{"version-custom"} + + err := app.Run([]string{"foo", "--help-custom=bar"}) + if err != nil { + t.Errorf("Run returned unexpected error: %v", err) + } +} + +func TestHandleAction_WithNonFuncAction(t *testing.T) { + app := NewApp() + app.Action = 42 + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + err = HandleAction(app.Action, NewContext(app, fs, nil)) + + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + exitErr, ok := err.(*ExitError) + + if !ok { + t.Fatalf("expected to receive a *ExitError") + } + + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type.") { + t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) + } + + if exitErr.ExitCode() != 2 { + t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) + } +} + +func TestHandleAction_WithInvalidFuncSignature(t *testing.T) { + app := NewApp() + app.Action = func() string { return "" } + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + err = HandleAction(app.Action, NewContext(app, fs, nil)) + + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + exitErr, ok := err.(*ExitError) + + if !ok { + t.Fatalf("expected to receive a *ExitError") + } + + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { + t.Fatalf("expected an unknown Action error, but got: %v", exitErr.Error()) + } + + if exitErr.ExitCode() != 2 { + t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) + } +} + +func TestHandleAction_WithInvalidFuncReturnSignature(t *testing.T) { + app := NewApp() + app.Action = func(_ *Context) (int, error) { return 0, nil } + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + err = HandleAction(app.Action, NewContext(app, fs, nil)) + + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + exitErr, ok := err.(*ExitError) + + if !ok { + t.Fatalf("expected to receive a *ExitError") + } + + if !strings.HasPrefix(exitErr.Error(), "ERROR invalid Action type") { + t.Fatalf("expected an invalid Action signature error, but got: %v", exitErr.Error()) + } + + if exitErr.ExitCode() != 2 { + t.Fatalf("expected error exit code to be 2, but got: %v", exitErr.ExitCode()) + } +} + +func TestHandleAction_WithUnknownPanic(t *testing.T) { + defer func() { refute(t, recover(), nil) }() + + var fn ActionFunc + + app := NewApp() + app.Action = func(ctx *Context) error { + fn(ctx) + return nil + } + fs, err := flagSet(app.Name, app.Flags) + if err != nil { + t.Errorf("error creating FlagSet: %s", err) + } + HandleAction(app.Action, NewContext(app, fs, nil)) +} + +func TestShellCompletionForIncompleteFlags(t *testing.T) { + app := NewApp() + app.Flags = []Flag{ + IntFlag{ + Name: "test-completion", + }, + } + app.EnableBashCompletion = true + app.BashComplete = func(ctx *Context) { + for _, command := range ctx.App.Commands { + if command.Hidden { + continue + } + + for _, name := range command.Names() { + fmt.Fprintln(ctx.App.Writer, name) + } + } + + for _, flag := range ctx.App.Flags { + for _, name := range strings.Split(flag.GetName(), ",") { + if name == BashCompletionFlag.GetName() { + continue + } + + switch name = strings.TrimSpace(name); len(name) { + case 0: + case 1: + fmt.Fprintln(ctx.App.Writer, "-"+name) + default: + fmt.Fprintln(ctx.App.Writer, "--"+name) + } + } + } + } + app.Action = func(ctx *Context) error { + return fmt.Errorf("should not get here") + } + err := app.Run([]string{"", "--test-completion", "--" + BashCompletionFlag.GetName()}) + if err != nil { + t.Errorf("app should not return an error: %s", err) + } +} + +func TestHandleActionActuallyWorksWithActions(t *testing.T) { + var f ActionFunc + called := false + f = func(c *Context) error { + called = true + return nil + } + + err := HandleAction(f, nil) + + if err != nil { + t.Errorf("Should not have errored: %v", err) + } + + if !called { + t.Errorf("Function was not called") + } +} diff --git a/vendor/github.com/urfave/cli/appveyor.yml b/vendor/github.com/urfave/cli/appveyor.yml new file mode 100644 index 0000000..1e1489c --- /dev/null +++ b/vendor/github.com/urfave/cli/appveyor.yml @@ -0,0 +1,26 @@ +version: "{build}" + +os: Windows Server 2016 + +image: Visual Studio 2017 + +clone_folder: c:\gopath\src\github.com\urfave\cli + +environment: + GOPATH: C:\gopath + GOVERSION: 1.8.x + PYTHON: C:\Python36-x64 + PYTHON_VERSION: 3.6.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/autocomplete/bash_autocomplete b/vendor/github.com/urfave/cli/autocomplete/bash_autocomplete new file mode 100755 index 0000000..37d9c14 --- /dev/null +++ b/vendor/github.com/urfave/cli/autocomplete/bash_autocomplete @@ -0,0 +1,16 @@ +#! /bin/bash + +: ${PROG:=$(basename ${BASH_SOURCE})} + +_cli_bash_autocomplete() { + local cur opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} + +complete -F _cli_bash_autocomplete $PROG + +unset PROG diff --git a/vendor/github.com/urfave/cli/autocomplete/zsh_autocomplete b/vendor/github.com/urfave/cli/autocomplete/zsh_autocomplete new file mode 100644 index 0000000..5430a18 --- /dev/null +++ b/vendor/github.com/urfave/cli/autocomplete/zsh_autocomplete @@ -0,0 +1,5 @@ +autoload -U compinit && compinit +autoload -U bashcompinit && bashcompinit + +script_dir=$(dirname $0) +source ${script_dir}/bash_autocomplete 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/command_test.go b/vendor/github.com/urfave/cli/command_test.go new file mode 100644 index 0000000..4ad994c --- /dev/null +++ b/vendor/github.com/urfave/cli/command_test.go @@ -0,0 +1,240 @@ +package cli + +import ( + "errors" + "flag" + "fmt" + "io/ioutil" + "strings" + "testing" +) + +func TestCommandFlagParsing(t *testing.T) { + cases := []struct { + testArgs []string + skipFlagParsing bool + skipArgReorder bool + expectedErr error + }{ + // Test normal "not ignoring flags" flow + {[]string{"test-cmd", "blah", "blah", "-break"}, false, false, errors.New("flag provided but not defined: -break")}, + + // Test no arg reorder + {[]string{"test-cmd", "blah", "blah", "-break"}, false, true, nil}, + + {[]string{"test-cmd", "blah", "blah"}, true, false, nil}, // Test SkipFlagParsing without any args that look like flags + {[]string{"test-cmd", "blah", "-break"}, true, false, nil}, // Test SkipFlagParsing with random flag arg + {[]string{"test-cmd", "blah", "-help"}, true, false, nil}, // Test SkipFlagParsing with "special" help flag arg + } + + for _, c := range cases { + app := NewApp() + app.Writer = ioutil.Discard + set := flag.NewFlagSet("test", 0) + set.Parse(c.testArgs) + + context := NewContext(app, set, nil) + + command := Command{ + Name: "test-cmd", + Aliases: []string{"tc"}, + Usage: "this is for testing", + Description: "testing", + Action: func(_ *Context) error { return nil }, + SkipFlagParsing: c.skipFlagParsing, + SkipArgReorder: c.skipArgReorder, + } + + err := command.Run(context) + + expect(t, err, c.expectedErr) + expect(t, []string(context.Args()), c.testArgs) + } +} + +func TestCommand_Run_DoesNotOverwriteErrorFromBefore(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Name: "bar", + Before: func(c *Context) error { + return fmt.Errorf("before error") + }, + After: func(c *Context) error { + return fmt.Errorf("after error") + }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.Contains(err.Error(), "before error") { + t.Errorf("expected text of error from Before method, but got none in \"%v\"", err) + } + if !strings.Contains(err.Error(), "after error") { + t.Errorf("expected text of error from After method, but got none in \"%v\"", err) + } +} + +func TestCommand_Run_BeforeSavesMetadata(t *testing.T) { + var receivedMsgFromAction string + var receivedMsgFromAfter string + + app := NewApp() + app.Commands = []Command{ + { + Name: "bar", + Before: func(c *Context) error { + c.App.Metadata["msg"] = "hello world" + return nil + }, + Action: func(c *Context) error { + msg, ok := c.App.Metadata["msg"] + if !ok { + return errors.New("msg not found") + } + receivedMsgFromAction = msg.(string) + return nil + }, + After: func(c *Context) error { + msg, ok := c.App.Metadata["msg"] + if !ok { + return errors.New("msg not found") + } + receivedMsgFromAfter = msg.(string) + return nil + }, + }, + } + + err := app.Run([]string{"foo", "bar"}) + if err != nil { + t.Fatalf("expected no error from Run, got %s", err) + } + + expectedMsg := "hello world" + + if receivedMsgFromAction != expectedMsg { + t.Fatalf("expected msg from Action to match. Given: %q\nExpected: %q", + receivedMsgFromAction, expectedMsg) + } + if receivedMsgFromAfter != expectedMsg { + t.Fatalf("expected msg from After to match. Given: %q\nExpected: %q", + receivedMsgFromAction, expectedMsg) + } +} + +func TestCommand_OnUsageError_hasCommandContext(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Name: "bar", + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + return fmt.Errorf("intercepted in %s: %s", c.Command.Name, err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted in bar") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestCommand_OnUsageError_WithWrongFlagValue(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Name: "bar", + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestCommand_OnUsageError_WithSubcommand(t *testing.T) { + app := NewApp() + app.Commands = []Command{ + { + Name: "bar", + Subcommands: []Command{ + { + Name: "baz", + }, + }, + Flags: []Flag{ + IntFlag{Name: "flag"}, + }, + OnUsageError: func(c *Context, err error, _ bool) error { + if !strings.HasPrefix(err.Error(), "invalid value \"wrong\"") { + t.Errorf("Expect an invalid value error, but got \"%v\"", err) + } + return errors.New("intercepted: " + err.Error()) + }, + }, + } + + err := app.Run([]string{"foo", "bar", "--flag=wrong"}) + if err == nil { + t.Fatalf("expected to receive error from Run, got none") + } + + if !strings.HasPrefix(err.Error(), "intercepted: invalid value") { + t.Errorf("Expect an intercepted error, but got \"%v\"", err) + } +} + +func TestCommand_Run_SubcommandsCanUseErrWriter(t *testing.T) { + app := NewApp() + app.ErrWriter = ioutil.Discard + app.Commands = []Command{ + { + Name: "bar", + Usage: "this is for testing", + Subcommands: []Command{ + { + Name: "baz", + Usage: "this is for testing", + Action: func(c *Context) error { + if c.App.ErrWriter != ioutil.Discard { + return fmt.Errorf("ErrWriter not passed") + } + + return nil + }, + }, + }, + }, + } + + err := app.Run([]string{"foo", "bar", "baz"}) + if err != nil { + t.Fatal(err) + } +} 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/context_test.go b/vendor/github.com/urfave/cli/context_test.go new file mode 100644 index 0000000..7acca10 --- /dev/null +++ b/vendor/github.com/urfave/cli/context_test.go @@ -0,0 +1,403 @@ +package cli + +import ( + "flag" + "os" + "testing" + "time" +) + +func TestNewContext(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + set.Int64("myflagInt64", int64(12), "doc") + set.Uint("myflagUint", uint(93), "doc") + set.Uint64("myflagUint64", uint64(93), "doc") + set.Float64("myflag64", float64(17), "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Int("myflag", 42, "doc") + globalSet.Int64("myflagInt64", int64(42), "doc") + globalSet.Uint("myflagUint", uint(33), "doc") + globalSet.Uint64("myflagUint64", uint64(33), "doc") + globalSet.Float64("myflag64", float64(47), "doc") + globalCtx := NewContext(nil, globalSet, nil) + command := Command{Name: "mycommand"} + c := NewContext(nil, set, globalCtx) + c.Command = command + expect(t, c.Int("myflag"), 12) + expect(t, c.Int64("myflagInt64"), int64(12)) + expect(t, c.Uint("myflagUint"), uint(93)) + expect(t, c.Uint64("myflagUint64"), uint64(93)) + expect(t, c.Float64("myflag64"), float64(17)) + expect(t, c.GlobalInt("myflag"), 42) + expect(t, c.GlobalInt64("myflagInt64"), int64(42)) + expect(t, c.GlobalUint("myflagUint"), uint(33)) + expect(t, c.GlobalUint64("myflagUint64"), uint64(33)) + expect(t, c.GlobalFloat64("myflag64"), float64(47)) + expect(t, c.Command.Name, "mycommand") +} + +func TestContext_Int(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.Int("myflag"), 12) +} + +func TestContext_Int64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int64("myflagInt64", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.Int64("myflagInt64"), int64(12)) +} + +func TestContext_Uint(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint("myflagUint", uint(13), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Uint("myflagUint"), uint(13)) +} + +func TestContext_Uint64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Uint64("myflagUint64", uint64(9), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Uint64("myflagUint64"), uint64(9)) +} + +func TestContext_GlobalInt(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("myflag", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.GlobalInt("myflag"), 12) + expect(t, c.GlobalInt("nope"), 0) +} + +func TestContext_GlobalInt64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int64("myflagInt64", 12, "doc") + c := NewContext(nil, set, nil) + expect(t, c.GlobalInt64("myflagInt64"), int64(12)) + expect(t, c.GlobalInt64("nope"), int64(0)) +} + +func TestContext_Float64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Float64("myflag", float64(17), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Float64("myflag"), float64(17)) +} + +func TestContext_GlobalFloat64(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Float64("myflag", float64(17), "doc") + c := NewContext(nil, set, nil) + expect(t, c.GlobalFloat64("myflag"), float64(17)) + expect(t, c.GlobalFloat64("nope"), float64(0)) +} + +func TestContext_Duration(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Duration("myflag", time.Duration(12*time.Second), "doc") + c := NewContext(nil, set, nil) + expect(t, c.Duration("myflag"), time.Duration(12*time.Second)) +} + +func TestContext_String(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.String("myflag", "hello world", "doc") + c := NewContext(nil, set, nil) + expect(t, c.String("myflag"), "hello world") +} + +func TestContext_Bool(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(nil, set, nil) + expect(t, c.Bool("myflag"), false) +} + +func TestContext_BoolT(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", true, "doc") + c := NewContext(nil, set, nil) + expect(t, c.BoolT("myflag"), true) +} + +func TestContext_GlobalBool(t *testing.T) { + set := flag.NewFlagSet("test", 0) + + globalSet := flag.NewFlagSet("test-global", 0) + globalSet.Bool("myflag", false, "doc") + globalCtx := NewContext(nil, globalSet, nil) + + c := NewContext(nil, set, globalCtx) + expect(t, c.GlobalBool("myflag"), false) + expect(t, c.GlobalBool("nope"), false) +} + +func TestContext_GlobalBoolT(t *testing.T) { + set := flag.NewFlagSet("test", 0) + + globalSet := flag.NewFlagSet("test-global", 0) + globalSet.Bool("myflag", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + + c := NewContext(nil, set, globalCtx) + expect(t, c.GlobalBoolT("myflag"), true) + expect(t, c.GlobalBoolT("nope"), false) +} + +func TestContext_Args(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(nil, set, nil) + set.Parse([]string{"--myflag", "bat", "baz"}) + expect(t, len(c.Args()), 2) + expect(t, c.Bool("myflag"), true) +} + +func TestContext_NArg(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + c := NewContext(nil, set, nil) + set.Parse([]string{"--myflag", "bat", "baz"}) + expect(t, c.NArg(), 2) +} + +func TestContext_IsSet(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + set.String("otherflag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--myflag", "bat", "baz"}) + globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) + expect(t, c.IsSet("myflag"), true) + expect(t, c.IsSet("otherflag"), false) + expect(t, c.IsSet("bogusflag"), false) + expect(t, c.IsSet("myflagGlobal"), false) +} + +// XXX Corresponds to hack in context.IsSet for flags with EnvVar field +// Should be moved to `flag_test` in v2 +func TestContext_IsSet_fromEnv(t *testing.T) { + var ( + timeoutIsSet, tIsSet bool + noEnvVarIsSet, nIsSet bool + passwordIsSet, pIsSet bool + unparsableIsSet, uIsSet bool + ) + + clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + os.Setenv("APP_PASSWORD", "") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"}, + Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"}, + Float64Flag{Name: "no-env-var, n"}, + }, + Action: func(ctx *Context) error { + timeoutIsSet = ctx.IsSet("timeout") + tIsSet = ctx.IsSet("t") + passwordIsSet = ctx.IsSet("password") + pIsSet = ctx.IsSet("p") + unparsableIsSet = ctx.IsSet("unparsable") + uIsSet = ctx.IsSet("u") + noEnvVarIsSet = ctx.IsSet("no-env-var") + nIsSet = ctx.IsSet("n") + return nil + }, + } + a.Run([]string{"run"}) + expect(t, timeoutIsSet, true) + expect(t, tIsSet, true) + expect(t, passwordIsSet, true) + expect(t, pIsSet, true) + expect(t, noEnvVarIsSet, false) + expect(t, nIsSet, false) + + os.Setenv("APP_UNPARSABLE", "foobar") + a.Run([]string{"run"}) + expect(t, unparsableIsSet, false) + expect(t, uIsSet, false) +} + +func TestContext_GlobalIsSet(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + set.String("otherflag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + globalSet.Bool("myflagGlobalUnset", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--myflag", "bat", "baz"}) + globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) + expect(t, c.GlobalIsSet("myflag"), false) + expect(t, c.GlobalIsSet("otherflag"), false) + expect(t, c.GlobalIsSet("bogusflag"), false) + expect(t, c.GlobalIsSet("myflagGlobal"), true) + expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) + expect(t, c.GlobalIsSet("bogusGlobal"), false) +} + +// XXX Corresponds to hack in context.IsSet for flags with EnvVar field +// Should be moved to `flag_test` in v2 +func TestContext_GlobalIsSet_fromEnv(t *testing.T) { + var ( + timeoutIsSet, tIsSet bool + noEnvVarIsSet, nIsSet bool + passwordIsSet, pIsSet bool + unparsableIsSet, uIsSet bool + ) + + clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + os.Setenv("APP_PASSWORD", "") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + StringFlag{Name: "password, p", EnvVar: "APP_PASSWORD"}, + Float64Flag{Name: "no-env-var, n"}, + Float64Flag{Name: "unparsable, u", EnvVar: "APP_UNPARSABLE"}, + }, + Commands: []Command{ + { + Name: "hello", + Action: func(ctx *Context) error { + timeoutIsSet = ctx.GlobalIsSet("timeout") + tIsSet = ctx.GlobalIsSet("t") + passwordIsSet = ctx.GlobalIsSet("password") + pIsSet = ctx.GlobalIsSet("p") + unparsableIsSet = ctx.GlobalIsSet("unparsable") + uIsSet = ctx.GlobalIsSet("u") + noEnvVarIsSet = ctx.GlobalIsSet("no-env-var") + nIsSet = ctx.GlobalIsSet("n") + return nil + }, + }, + }, + } + if err := a.Run([]string{"run", "hello"}); err != nil { + t.Logf("error running Run(): %+v", err) + } + expect(t, timeoutIsSet, true) + expect(t, tIsSet, true) + expect(t, passwordIsSet, true) + expect(t, pIsSet, true) + expect(t, noEnvVarIsSet, false) + expect(t, nIsSet, false) + + os.Setenv("APP_UNPARSABLE", "foobar") + if err := a.Run([]string{"run"}); err != nil { + t.Logf("error running Run(): %+v", err) + } + expect(t, unparsableIsSet, false) + expect(t, uIsSet, false) +} + +func TestContext_NumFlags(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Bool("myflag", false, "doc") + set.String("otherflag", "hello world", "doc") + globalSet := flag.NewFlagSet("test", 0) + globalSet.Bool("myflagGlobal", true, "doc") + globalCtx := NewContext(nil, globalSet, nil) + c := NewContext(nil, set, globalCtx) + set.Parse([]string{"--myflag", "--otherflag=foo"}) + globalSet.Parse([]string{"--myflagGlobal"}) + expect(t, c.NumFlags(), 2) +} + +func TestContext_GlobalFlag(t *testing.T) { + var globalFlag string + var globalFlagSet bool + app := NewApp() + app.Flags = []Flag{ + StringFlag{Name: "global, g", Usage: "global"}, + } + app.Action = func(c *Context) error { + globalFlag = c.GlobalString("global") + globalFlagSet = c.GlobalIsSet("global") + return nil + } + app.Run([]string{"command", "-g", "foo"}) + expect(t, globalFlag, "foo") + expect(t, globalFlagSet, true) + +} + +func TestContext_GlobalFlagsInSubcommands(t *testing.T) { + subcommandRun := false + parentFlag := false + app := NewApp() + + app.Flags = []Flag{ + BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, + } + + app.Commands = []Command{ + { + Name: "foo", + Flags: []Flag{ + BoolFlag{Name: "parent, p", Usage: "Parent flag"}, + }, + Subcommands: []Command{ + { + Name: "bar", + Action: func(c *Context) error { + if c.GlobalBool("debug") { + subcommandRun = true + } + if c.GlobalBool("parent") { + parentFlag = true + } + return nil + }, + }, + }, + }, + } + + app.Run([]string{"command", "-d", "foo", "-p", "bar"}) + + expect(t, subcommandRun, true) + expect(t, parentFlag, true) +} + +func TestContext_Set(t *testing.T) { + set := flag.NewFlagSet("test", 0) + set.Int("int", 5, "an int") + c := NewContext(nil, set, nil) + + expect(t, c.IsSet("int"), false) + c.Set("int", "1") + expect(t, c.Int("int"), 1) + expect(t, c.IsSet("int"), true) +} + +func TestContext_GlobalSet(t *testing.T) { + gSet := flag.NewFlagSet("test", 0) + gSet.Int("int", 5, "an int") + + set := flag.NewFlagSet("sub", 0) + set.Int("int", 3, "an int") + + pc := NewContext(nil, gSet, nil) + c := NewContext(nil, set, pc) + + c.Set("int", "1") + expect(t, c.Int("int"), 1) + expect(t, c.GlobalInt("int"), 5) + + expect(t, c.GlobalIsSet("int"), false) + c.GlobalSet("int", "1") + expect(t, c.Int("int"), 1) + expect(t, c.GlobalInt("int"), 1) + expect(t, c.GlobalIsSet("int"), true) +} 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/errors_test.go b/vendor/github.com/urfave/cli/errors_test.go new file mode 100644 index 0000000..9b609c5 --- /dev/null +++ b/vendor/github.com/urfave/cli/errors_test.go @@ -0,0 +1,122 @@ +package cli + +import ( + "bytes" + "errors" + "fmt" + "testing" +) + +func TestHandleExitCoder_nil(t *testing.T) { + exitCode := 0 + called := false + + OsExiter = func(rc int) { + if !called { + exitCode = rc + called = true + } + } + + defer func() { OsExiter = fakeOsExiter }() + + HandleExitCoder(nil) + + expect(t, exitCode, 0) + expect(t, called, false) +} + +func TestHandleExitCoder_ExitCoder(t *testing.T) { + exitCode := 0 + called := false + + OsExiter = func(rc int) { + if !called { + exitCode = rc + called = true + } + } + + defer func() { OsExiter = fakeOsExiter }() + + HandleExitCoder(NewExitError("galactic perimeter breach", 9)) + + expect(t, exitCode, 9) + expect(t, called, true) +} + +func TestHandleExitCoder_MultiErrorWithExitCoder(t *testing.T) { + exitCode := 0 + called := false + + OsExiter = func(rc int) { + if !called { + exitCode = rc + called = true + } + } + + defer func() { OsExiter = fakeOsExiter }() + + exitErr := NewExitError("galactic perimeter breach", 9) + exitErr2 := NewExitError("last ExitCoder", 11) + err := NewMultiError(errors.New("wowsa"), errors.New("egad"), exitErr, exitErr2) + HandleExitCoder(err) + + expect(t, exitCode, 11) + expect(t, called, true) +} + +// make a stub to not import pkg/errors +type ErrorWithFormat struct { + error +} + +func NewErrorWithFormat(m string) *ErrorWithFormat { + return &ErrorWithFormat{error: errors.New(m)} +} + +func (f *ErrorWithFormat) Format(s fmt.State, verb rune) { + fmt.Fprintf(s, "This the format: %v", f.error) +} + +func TestHandleExitCoder_ErrorWithFormat(t *testing.T) { + called := false + + OsExiter = func(rc int) { + if !called { + called = true + } + } + ErrWriter = &bytes.Buffer{} + + defer func() { + OsExiter = fakeOsExiter + ErrWriter = fakeErrWriter + }() + + err := NewExitError(NewErrorWithFormat("I am formatted"), 1) + HandleExitCoder(err) + + expect(t, called, true) + expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: I am formatted\n") +} + +func TestHandleExitCoder_MultiErrorWithFormat(t *testing.T) { + called := false + + OsExiter = func(rc int) { + if !called { + called = true + } + } + ErrWriter = &bytes.Buffer{} + + defer func() { OsExiter = fakeOsExiter }() + + err := NewMultiError(NewErrorWithFormat("err1"), NewErrorWithFormat("err2")) + HandleExitCoder(err) + + expect(t, called, true) + expect(t, ErrWriter.(*bytes.Buffer).String(), "This the format: err1\nThis the format: err2\n") +} 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/flag_test.go b/vendor/github.com/urfave/cli/flag_test.go new file mode 100644 index 0000000..1ccb639 --- /dev/null +++ b/vendor/github.com/urfave/cli/flag_test.go @@ -0,0 +1,1215 @@ +package cli + +import ( + "fmt" + "os" + "reflect" + "regexp" + "runtime" + "strings" + "testing" + "time" +) + +var boolFlagTests = []struct { + name string + expected string +}{ + {"help", "--help\t"}, + {"h", "-h\t"}, +} + +func TestBoolFlagHelpOutput(t *testing.T) { + for _, test := range boolFlagTests { + flag := BoolFlag{Name: test.name} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestFlagsFromEnv(t *testing.T) { + var flagTests = []struct { + input string + output interface{} + flag Flag + errRegexp string + }{ + {"", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"1", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"false", false, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"foobar", true, BoolFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, + + {"", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"1", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"false", false, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, ""}, + {"foobar", true, BoolTFlag{Name: "debug", EnvVar: "DEBUG"}, fmt.Sprintf(`could not parse foobar as bool value for flag debug: .*`)}, + + {"1s", 1 * time.Second, DurationFlag{Name: "time", EnvVar: "TIME"}, ""}, + {"foobar", false, DurationFlag{Name: "time", EnvVar: "TIME"}, fmt.Sprintf(`could not parse foobar as duration for flag time: .*`)}, + + {"1.2", 1.2, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1", 1.0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"foobar", 0, Float64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as float64 value for flag seconds: .*`)}, + + {"1", int64(1), Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, Int64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + + {"1", 1, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as int value for flag seconds: .*`)}, + {"foobar", 0, IntFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int value for flag seconds: .*`)}, + + {"1,2", IntSlice{1, 2}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2,2", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int slice value for flag seconds: .*`)}, + {"foobar", IntSlice{}, IntSliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int slice value for flag seconds: .*`)}, + + {"1,2", Int64Slice{1, 2}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2,2", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2,2 as int64 slice value for flag seconds: .*`)}, + {"foobar", Int64Slice{}, Int64SliceFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as int64 slice value for flag seconds: .*`)}, + + {"foo", "foo", StringFlag{Name: "name", EnvVar: "NAME"}, ""}, + + {"foo,bar", StringSlice{"foo", "bar"}, StringSliceFlag{Name: "names", EnvVar: "NAMES"}, ""}, + + {"1", uint(1), UintFlag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint value for flag seconds: .*`)}, + {"foobar", 0, UintFlag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint value for flag seconds: .*`)}, + + {"1", uint64(1), Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, ""}, + {"1.2", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse 1.2 as uint64 value for flag seconds: .*`)}, + {"foobar", 0, Uint64Flag{Name: "seconds", EnvVar: "SECONDS"}, fmt.Sprintf(`could not parse foobar as uint64 value for flag seconds: .*`)}, + + {"foo,bar", &Parser{"foo", "bar"}, GenericFlag{Name: "names", Value: &Parser{}, EnvVar: "NAMES"}, ""}, + } + + for _, test := range flagTests { + os.Clearenv() + os.Setenv(reflect.ValueOf(test.flag).FieldByName("EnvVar").String(), test.input) + a := App{ + Flags: []Flag{test.flag}, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.value(test.flag.GetName()), test.output) { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.value(test.flag.GetName())) + } + return nil + }, + } + + err := a.Run([]string{"run"}) + + if test.errRegexp != "" { + if err == nil { + t.Errorf("expected error to match %s, got none", test.errRegexp) + } else { + if matched, _ := regexp.MatchString(test.errRegexp, err.Error()); !matched { + t.Errorf("expected error to match %s, got error %s", test.errRegexp, err) + } + } + } else { + if err != nil && test.errRegexp == "" { + t.Errorf("expected no error got %s", err) + } + } + } +} + +var stringFlagTests = []struct { + name string + usage string + value string + expected string +}{ + {"foo", "", "", "--foo value\t"}, + {"f", "", "", "-f value\t"}, + {"f", "The total `foo` desired", "all", "-f foo\tThe total foo desired (default: \"all\")"}, + {"test", "", "Something", "--test value\t(default: \"Something\")"}, + {"config,c", "Load configuration from `FILE`", "", "--config FILE, -c FILE\tLoad configuration from FILE"}, + {"config,c", "Load configuration from `CONFIG`", "config.json", "--config CONFIG, -c CONFIG\tLoad configuration from CONFIG (default: \"config.json\")"}, +} + +func TestStringFlagHelpOutput(t *testing.T) { + for _, test := range stringFlagTests { + flag := StringFlag{Name: test.name, Usage: test.usage, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_FOO", "derp") + for _, test := range stringFlagTests { + flag := StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} + output := flag.String() + + expectedSuffix := " [$APP_FOO]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_FOO%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var stringSliceFlagTests = []struct { + name string + value *StringSlice + expected string +}{ + {"foo", func() *StringSlice { + s := &StringSlice{} + s.Set("") + return s + }(), "--foo value\t"}, + {"f", func() *StringSlice { + s := &StringSlice{} + s.Set("") + return s + }(), "-f value\t"}, + {"f", func() *StringSlice { + s := &StringSlice{} + s.Set("Lipstick") + return s + }(), "-f value\t(default: \"Lipstick\")"}, + {"test", func() *StringSlice { + s := &StringSlice{} + s.Set("Something") + return s + }(), "--test value\t(default: \"Something\")"}, +} + +func TestStringSliceFlagHelpOutput(t *testing.T) { + for _, test := range stringSliceFlagTests { + flag := StringSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_QWWX", "11,4") + for _, test := range stringSliceFlagTests { + flag := StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} + output := flag.String() + + expectedSuffix := " [$APP_QWWX]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_QWWX%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + +var intFlagTests = []struct { + name string + expected string +}{ + {"hats", "--hats value\t(default: 9)"}, + {"H", "-H value\t(default: 9)"}, +} + +func TestIntFlagHelpOutput(t *testing.T) { + for _, test := range intFlagTests { + flag := IntFlag{Name: test.name, Value: 9} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range intFlagTests { + flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var int64FlagTests = []struct { + name string + expected string +}{ + {"hats", "--hats value\t(default: 8589934592)"}, + {"H", "-H value\t(default: 8589934592)"}, +} + +func TestInt64FlagHelpOutput(t *testing.T) { + for _, test := range int64FlagTests { + flag := Int64Flag{Name: test.name, Value: 8589934592} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestInt64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range int64FlagTests { + flag := IntFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var uintFlagTests = []struct { + name string + expected string +}{ + {"nerfs", "--nerfs value\t(default: 41)"}, + {"N", "-N value\t(default: 41)"}, +} + +func TestUintFlagHelpOutput(t *testing.T) { + for _, test := range uintFlagTests { + flag := UintFlag{Name: test.name, Value: 41} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestUintFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range uintFlagTests { + flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var uint64FlagTests = []struct { + name string + expected string +}{ + {"gerfs", "--gerfs value\t(default: 8589934582)"}, + {"G", "-G value\t(default: 8589934582)"}, +} + +func TestUint64FlagHelpOutput(t *testing.T) { + for _, test := range uint64FlagTests { + flag := Uint64Flag{Name: test.name, Value: 8589934582} + output := flag.String() + + if output != test.expected { + t.Errorf("%s does not match %s", output, test.expected) + } + } +} + +func TestUint64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2") + for _, test := range uint64FlagTests { + flag := UintFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var durationFlagTests = []struct { + name string + expected string +}{ + {"hooting", "--hooting value\t(default: 1s)"}, + {"H", "-H value\t(default: 1s)"}, +} + +func TestDurationFlagHelpOutput(t *testing.T) { + for _, test := range durationFlagTests { + flag := DurationFlag{Name: test.name, Value: 1 * time.Second} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAR", "2h3m6s") + for _, test := range durationFlagTests { + flag := DurationFlag{Name: test.name, EnvVar: "APP_BAR"} + output := flag.String() + + expectedSuffix := " [$APP_BAR]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAR%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var intSliceFlagTests = []struct { + name string + value *IntSlice + expected string +}{ + {"heads", &IntSlice{}, "--heads value\t"}, + {"H", &IntSlice{}, "-H value\t"}, + {"H, heads", func() *IntSlice { + i := &IntSlice{} + i.Set("9") + i.Set("3") + return i + }(), "-H value, --heads value\t(default: 9, 3)"}, +} + +func TestIntSliceFlagHelpOutput(t *testing.T) { + for _, test := range intSliceFlagTests { + flag := IntSliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SMURF", "42,3") + for _, test := range intSliceFlagTests { + flag := IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + output := flag.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + +var int64SliceFlagTests = []struct { + name string + value *Int64Slice + expected string +}{ + {"heads", &Int64Slice{}, "--heads value\t"}, + {"H", &Int64Slice{}, "-H value\t"}, + {"H, heads", func() *Int64Slice { + i := &Int64Slice{} + i.Set("2") + i.Set("17179869184") + return i + }(), "-H value, --heads value\t(default: 2, 17179869184)"}, +} + +func TestInt64SliceFlagHelpOutput(t *testing.T) { + for _, test := range int64SliceFlagTests { + flag := Int64SliceFlag{Name: test.name, Value: test.value} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestInt64SliceFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SMURF", "42,17179869184") + for _, test := range int64SliceFlagTests { + flag := Int64SliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} + output := flag.String() + + expectedSuffix := " [$APP_SMURF]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_SMURF%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%q does not end with"+expectedSuffix, output) + } + } +} + +var float64FlagTests = []struct { + name string + expected string +}{ + {"hooting", "--hooting value\t(default: 0.1)"}, + {"H", "-H value\t(default: 0.1)"}, +} + +func TestFloat64FlagHelpOutput(t *testing.T) { + for _, test := range float64FlagTests { + flag := Float64Flag{Name: test.name, Value: float64(0.1)} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_BAZ", "99.4") + for _, test := range float64FlagTests { + flag := Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} + output := flag.String() + + expectedSuffix := " [$APP_BAZ]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_BAZ%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +var genericFlagTests = []struct { + name string + value Generic + expected string +}{ + {"toads", &Parser{"abc", "def"}, "--toads value\ttest flag (default: abc,def)"}, + {"t", &Parser{"abc", "def"}, "-t value\ttest flag (default: abc,def)"}, +} + +func TestGenericFlagHelpOutput(t *testing.T) { + for _, test := range genericFlagTests { + flag := GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} + output := flag.String() + + if output != test.expected { + t.Errorf("%q does not match %q", output, test.expected) + } + } +} + +func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { + os.Clearenv() + os.Setenv("APP_ZAP", "3") + for _, test := range genericFlagTests { + flag := GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} + output := flag.String() + + expectedSuffix := " [$APP_ZAP]" + if runtime.GOOS == "windows" { + expectedSuffix = " [%APP_ZAP%]" + } + if !strings.HasSuffix(output, expectedSuffix) { + t.Errorf("%s does not end with"+expectedSuffix, output) + } + } +} + +func TestParseMultiString(t *testing.T) { + (&App{ + Flags: []Flag{ + StringFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) error { + if ctx.String("serve") != "10" { + t.Errorf("main name not set") + } + if ctx.String("s") != "10" { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "-s", "10"}) +} + +func TestParseDestinationString(t *testing.T) { + var dest string + a := App{ + Flags: []Flag{ + StringFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) error { + if dest != "10" { + t.Errorf("expected destination String 10") + } + return nil + }, + } + a.Run([]string{"run", "--dest", "10"}) +} + +func TestParseMultiStringFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_COUNT", "20") + (&App{ + Flags: []Flag{ + StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, + }, + Action: func(ctx *Context) error { + if ctx.String("count") != "20" { + t.Errorf("main name not set") + } + if ctx.String("c") != "20" { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_COUNT", "20") + (&App{ + Flags: []Flag{ + StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, + }, + Action: func(ctx *Context) error { + if ctx.String("count") != "20" { + t.Errorf("main name not set") + } + if ctx.String("c") != "20" { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringSlice(t *testing.T) { + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "serve, s", Value: &StringSlice{}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiStringSliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + StringSliceFlag{Name: "intervals, i", Value: &StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiInt(t *testing.T) { + a := App{ + Flags: []Flag{ + IntFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) error { + if ctx.Int("serve") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("s") != 10 { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run", "-s", "10"}) +} + +func TestParseDestinationInt(t *testing.T) { + var dest int + a := App{ + Flags: []Flag{ + IntFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) error { + if dest != 10 { + t.Errorf("expected destination Int 10") + } + return nil + }, + } + a.Run([]string{"run", "--dest", "10"}) +} + +func TestParseMultiIntFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "10") + a := App{ + Flags: []Flag{ + IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) error { + if ctx.Int("timeout") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("t") != 10 { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiIntFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "10") + a := App{ + Flags: []Flag{ + IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) error { + if ctx.Int("timeout") != 10 { + t.Errorf("main name not set") + } + if ctx.Int("t") != 10 { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiIntSlice(t *testing.T) { + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "serve, s", Value: &IntSlice{}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "20"}) +} + +func TestParseMultiIntSliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,40") + + (&App{ + Flags: []Flag{ + IntSliceFlag{Name: "intervals, i", Value: &IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiInt64Slice(t *testing.T) { + (&App{ + Flags: []Flag{ + Int64SliceFlag{Name: "serve, s", Value: &Int64Slice{}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("serve"), []int64{10, 17179869184}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.Int64Slice("s"), []int64{10, 17179869184}) { + t.Errorf("short name not set") + } + return nil + }, + }).Run([]string{"run", "-s", "10", "-s", "17179869184"}) +} + +func TestParseMultiInt64SliceFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,17179869184") + + (&App{ + Flags: []Flag{ + Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Int64Slice("i"), []int64{20, 30, 17179869184}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiInt64SliceFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_INTERVALS", "20,30,17179869184") + + (&App{ + Flags: []Flag{ + Int64SliceFlag{Name: "intervals, i", Value: &Int64Slice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Int64Slice("intervals"), []int64{20, 30, 17179869184}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Int64Slice("i"), []int64{20, 30, 17179869184}) { + t.Errorf("short name not set from env") + } + return nil + }, + }).Run([]string{"run"}) +} + +func TestParseMultiFloat64(t *testing.T) { + a := App{ + Flags: []Flag{ + Float64Flag{Name: "serve, s"}, + }, + Action: func(ctx *Context) error { + if ctx.Float64("serve") != 10.2 { + t.Errorf("main name not set") + } + if ctx.Float64("s") != 10.2 { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run", "-s", "10.2"}) +} + +func TestParseDestinationFloat64(t *testing.T) { + var dest float64 + a := App{ + Flags: []Flag{ + Float64Flag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) error { + if dest != 10.2 { + t.Errorf("expected destination Float64 10.2") + } + return nil + }, + } + a.Run([]string{"run", "--dest", "10.2"}) +} + +func TestParseMultiFloat64FromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) error { + if ctx.Float64("timeout") != 15.5 { + t.Errorf("main name not set") + } + if ctx.Float64("t") != 15.5 { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiFloat64FromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_TIMEOUT_SECONDS", "15.5") + a := App{ + Flags: []Flag{ + Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, + }, + Action: func(ctx *Context) error { + if ctx.Float64("timeout") != 15.5 { + t.Errorf("main name not set") + } + if ctx.Float64("t") != 15.5 { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBool(t *testing.T) { + a := App{ + Flags: []Flag{ + BoolFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("serve") != true { + t.Errorf("main name not set") + } + if ctx.Bool("s") != true { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run", "--serve"}) +} + +func TestParseDestinationBool(t *testing.T) { + var dest bool + a := App{ + Flags: []Flag{ + BoolFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) error { + if dest != true { + t.Errorf("expected destination Bool true") + } + return nil + }, + } + a.Run([]string{"run", "--dest"}) +} + +func TestParseMultiBoolFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "1") + a := App{ + Flags: []Flag{ + BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") != true { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") != true { + t.Errorf("short name not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBoolFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "1") + a := App{ + Flags: []Flag{ + BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") != true { + t.Errorf("main name not set from env") + } + if ctx.Bool("d") != true { + t.Errorf("short name not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseBoolTFromEnv(t *testing.T) { + var boolTFlagTests = []struct { + input string + output bool + }{ + {"", false}, + {"1", true}, + {"false", false}, + {"true", true}, + } + + for _, test := range boolTFlagTests { + os.Clearenv() + os.Setenv("DEBUG", test.input) + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "DEBUG"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("debug") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("debug")) + } + if ctx.Bool("d") != test.output { + t.Errorf("expected %+v to be parsed as %+v, instead was %+v", test.input, test.output, ctx.Bool("d")) + } + return nil + }, + } + a.Run([]string{"run"}) + } +} + +func TestParseMultiBoolT(t *testing.T) { + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "serve, s"}, + }, + Action: func(ctx *Context) error { + if ctx.BoolT("serve") != true { + t.Errorf("main name not set") + } + if ctx.BoolT("s") != true { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run", "--serve"}) +} + +func TestParseDestinationBoolT(t *testing.T) { + var dest bool + a := App{ + Flags: []Flag{ + BoolTFlag{ + Name: "dest", + Destination: &dest, + }, + }, + Action: func(ctx *Context) error { + if dest != true { + t.Errorf("expected destination BoolT true") + } + return nil + }, + } + a.Run([]string{"run", "--dest"}) +} + +func TestParseMultiBoolTFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "0") + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, + }, + Action: func(ctx *Context) error { + if ctx.BoolT("debug") != false { + t.Errorf("main name not set from env") + } + if ctx.BoolT("d") != false { + t.Errorf("short name not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseMultiBoolTFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_DEBUG", "0") + a := App{ + Flags: []Flag{ + BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, + }, + Action: func(ctx *Context) error { + if ctx.BoolT("debug") != false { + t.Errorf("main name not set from env") + } + if ctx.BoolT("d") != false { + t.Errorf("short name not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +type Parser [2]string + +func (p *Parser) Set(value string) error { + parts := strings.Split(value, ",") + if len(parts) != 2 { + return fmt.Errorf("invalid format") + } + + (*p)[0] = parts[0] + (*p)[1] = parts[1] + + return nil +} + +func (p *Parser) String() string { + return fmt.Sprintf("%s,%s", p[0], p[1]) +} + +func (p *Parser) Get() interface{} { + return p +} + +func TestParseGeneric(t *testing.T) { + a := App{ + Flags: []Flag{ + GenericFlag{Name: "serve, s", Value: &Parser{}}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { + t.Errorf("main name not set") + } + if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { + t.Errorf("short name not set") + } + return nil + }, + } + a.Run([]string{"run", "-s", "10,20"}) +} + +func TestParseGenericFromEnv(t *testing.T) { + os.Clearenv() + os.Setenv("APP_SERVE", "20,30") + a := App{ + Flags: []Flag{ + GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { + t.Errorf("main name not set from env") + } + if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) { + t.Errorf("short name not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} + +func TestParseGenericFromEnvCascade(t *testing.T) { + os.Clearenv() + os.Setenv("APP_FOO", "99,2000") + a := App{ + Flags: []Flag{ + GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, + }, + Action: func(ctx *Context) error { + if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { + t.Errorf("value not set from env") + } + return nil + }, + } + a.Run([]string{"run"}) +} 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/help_test.go b/vendor/github.com/urfave/cli/help_test.go new file mode 100644 index 0000000..70b6300 --- /dev/null +++ b/vendor/github.com/urfave/cli/help_test.go @@ -0,0 +1,452 @@ +package cli + +import ( + "bytes" + "flag" + "fmt" + "runtime" + "strings" + "testing" +) + +func Test_ShowAppHelp_NoAuthor(t *testing.T) { + output := new(bytes.Buffer) + app := NewApp() + app.Writer = output + + c := NewContext(app, nil, nil) + + ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("AUTHOR(S):")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "AUTHOR(S):") + } +} + +func Test_ShowAppHelp_NoVersion(t *testing.T) { + output := new(bytes.Buffer) + app := NewApp() + app.Writer = output + + app.Version = "" + + c := NewContext(app, nil, nil) + + ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") + } +} + +func Test_ShowAppHelp_HideVersion(t *testing.T) { + output := new(bytes.Buffer) + app := NewApp() + app.Writer = output + + app.HideVersion = true + + c := NewContext(app, nil, nil) + + ShowAppHelp(c) + + if bytes.Index(output.Bytes(), []byte("VERSION:")) != -1 { + t.Errorf("expected\n%snot to include %s", output.String(), "VERSION:") + } +} + +func Test_Help_Custom_Flags(t *testing.T) { + oldFlag := HelpFlag + defer func() { + HelpFlag = oldFlag + }() + + HelpFlag = BoolFlag{ + Name: "help, x", + Usage: "show help", + } + + app := App{ + Flags: []Flag{ + BoolFlag{Name: "foo, h"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("h") != true { + t.Errorf("custom help flag not set") + } + return nil + }, + } + output := new(bytes.Buffer) + app.Writer = output + app.Run([]string{"test", "-h"}) + if output.Len() > 0 { + t.Errorf("unexpected output: %s", output.String()) + } +} + +func Test_Version_Custom_Flags(t *testing.T) { + oldFlag := VersionFlag + defer func() { + VersionFlag = oldFlag + }() + + VersionFlag = BoolFlag{ + Name: "version, V", + Usage: "show version", + } + + app := App{ + Flags: []Flag{ + BoolFlag{Name: "foo, v"}, + }, + Action: func(ctx *Context) error { + if ctx.Bool("v") != true { + t.Errorf("custom version flag not set") + } + return nil + }, + } + output := new(bytes.Buffer) + app.Writer = output + app.Run([]string{"test", "-v"}) + if output.Len() > 0 { + t.Errorf("unexpected output: %s", output.String()) + } +} + +func Test_helpCommand_Action_ErrorIfNoTopic(t *testing.T) { + app := NewApp() + + set := flag.NewFlagSet("test", 0) + set.Parse([]string{"foo"}) + + c := NewContext(app, set, nil) + + err := helpCommand.Action.(func(*Context) error)(c) + + if err == nil { + t.Fatalf("expected error from helpCommand.Action(), but got nil") + } + + exitErr, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + } + + if !strings.HasPrefix(exitErr.Error(), "No help topic for") { + t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error()) + } + + if exitErr.exitCode != 3 { + t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode) + } +} + +func Test_helpCommand_InHelpOutput(t *testing.T) { + app := NewApp() + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"test", "--help"}) + + s := output.String() + + if strings.Contains(s, "\nCOMMANDS:\nGLOBAL OPTIONS:\n") { + t.Fatalf("empty COMMANDS section detected: %q", s) + } + + if !strings.Contains(s, "help, h") { + t.Fatalf("missing \"help, h\": %q", s) + } +} + +func Test_helpSubcommand_Action_ErrorIfNoTopic(t *testing.T) { + app := NewApp() + + set := flag.NewFlagSet("test", 0) + set.Parse([]string{"foo"}) + + c := NewContext(app, set, nil) + + err := helpSubcommand.Action.(func(*Context) error)(c) + + if err == nil { + t.Fatalf("expected error from helpCommand.Action(), but got nil") + } + + exitErr, ok := err.(*ExitError) + if !ok { + t.Fatalf("expected ExitError from helpCommand.Action(), but instead got: %v", err.Error()) + } + + if !strings.HasPrefix(exitErr.Error(), "No help topic for") { + t.Fatalf("expected an unknown help topic error, but got: %v", exitErr.Error()) + } + + if exitErr.exitCode != 3 { + t.Fatalf("expected exit value = 3, got %d instead", exitErr.exitCode) + } +} + +func TestShowAppHelp_CommandAliases(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Aliases: []string{"fr", "frob"}, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "--help"}) + + if !strings.Contains(output.String(), "frobbly, fr, frob") { + t.Errorf("expected output to include all command aliases; got: %q", output.String()) + } +} + +func TestShowCommandHelp_CommandAliases(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Aliases: []string{"fr", "frob", "bork"}, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "help", "fr"}) + + if !strings.Contains(output.String(), "frobbly") { + t.Errorf("expected output to include command name; got: %q", output.String()) + } + + if strings.Contains(output.String(), "bork") { + t.Errorf("expected output to exclude command aliases; got: %q", output.String()) + } +} + +func TestShowSubcommandHelp_CommandAliases(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Aliases: []string{"fr", "frob", "bork"}, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "help"}) + + if !strings.Contains(output.String(), "frobbly, fr, frob, bork") { + t.Errorf("expected output to include all command aliases; got: %q", output.String()) + } +} + +func TestShowCommandHelp_Customtemplate(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Action: func(ctx *Context) error { + return nil + }, + HelpName: "foo frobbly", + CustomHelpTemplate: `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{.HelpName}} [FLAGS] TARGET [TARGET ...] + +FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}} +EXAMPLES: + 1. Frobbly runs with this param locally. + $ {{.HelpName}} wobbly +`, + }, + }, + } + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "help", "frobbly"}) + + if strings.Contains(output.String(), "2. Frobbly runs without this param locally.") { + t.Errorf("expected output to exclude \"2. Frobbly runs without this param locally.\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "1. Frobbly runs with this param locally.") { + t.Errorf("expected output to include \"1. Frobbly runs with this param locally.\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "$ foo frobbly wobbly") { + t.Errorf("expected output to include \"$ foo frobbly wobbly\"; got: %q", output.String()) + } +} + +func TestShowSubcommandHelp_CommandUsageText(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + UsageText: "this is usage text", + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + + app.Run([]string{"foo", "frobbly", "--help"}) + + if !strings.Contains(output.String(), "this is usage text") { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + +func TestShowSubcommandHelp_SubcommandUsageText(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Subcommands: []Command{ + { + Name: "bobbly", + UsageText: "this is usage text", + }, + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"foo", "frobbly", "bobbly", "--help"}) + + if !strings.Contains(output.String(), "this is usage text") { + t.Errorf("expected output to include usage text; got: %q", output.String()) + } +} + +func TestShowAppHelp_HiddenCommand(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Action: func(ctx *Context) error { + return nil + }, + }, + { + Name: "secretfrob", + Hidden: true, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"app", "--help"}) + + if strings.Contains(output.String(), "secretfrob") { + t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "frobbly") { + t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) + } +} + +func TestShowAppHelp_CustomAppTemplate(t *testing.T) { + app := &App{ + Commands: []Command{ + { + Name: "frobbly", + Action: func(ctx *Context) error { + return nil + }, + }, + { + Name: "secretfrob", + Hidden: true, + Action: func(ctx *Context) error { + return nil + }, + }, + }, + ExtraInfo: func() map[string]string { + platform := fmt.Sprintf("OS: %s | Arch: %s", runtime.GOOS, runtime.GOARCH) + goruntime := fmt.Sprintf("Version: %s | CPUs: %d", runtime.Version(), runtime.NumCPU()) + return map[string]string{ + "PLATFORM": platform, + "RUNTIME": goruntime, + } + }, + CustomAppHelpTemplate: `NAME: + {{.Name}} - {{.Usage}} + +USAGE: + {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...] + +COMMANDS: + {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}} + {{end}}{{if .VisibleFlags}} +GLOBAL FLAGS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +VERSION: + 2.0.0 +{{"\n"}}{{range $key, $value := ExtraInfo}} +{{$key}}: + {{$value}} +{{end}}`, + } + + output := &bytes.Buffer{} + app.Writer = output + app.Run([]string{"app", "--help"}) + + if strings.Contains(output.String(), "secretfrob") { + t.Errorf("expected output to exclude \"secretfrob\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "frobbly") { + t.Errorf("expected output to include \"frobbly\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "PLATFORM:") || + !strings.Contains(output.String(), "OS:") || + !strings.Contains(output.String(), "Arch:") { + t.Errorf("expected output to include \"PLATFORM:, OS: and Arch:\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "RUNTIME:") || + !strings.Contains(output.String(), "Version:") || + !strings.Contains(output.String(), "CPUs:") { + t.Errorf("expected output to include \"RUNTIME:, Version: and CPUs:\"; got: %q", output.String()) + } + + if !strings.Contains(output.String(), "VERSION:") || + !strings.Contains(output.String(), "2.0.0") { + t.Errorf("expected output to include \"VERSION:, 2.0.0\"; got: %q", output.String()) + } +} diff --git a/vendor/github.com/urfave/cli/helpers_test.go b/vendor/github.com/urfave/cli/helpers_test.go new file mode 100644 index 0000000..109ea7a --- /dev/null +++ b/vendor/github.com/urfave/cli/helpers_test.go @@ -0,0 +1,28 @@ +package cli + +import ( + "os" + "reflect" + "runtime" + "strings" + "testing" +) + +var ( + wd, _ = os.Getwd() +) + +func expect(t *testing.T, a interface{}, b interface{}) { + _, fn, line, _ := runtime.Caller(1) + fn = strings.Replace(fn, wd+"/", "", -1) + + if !reflect.DeepEqual(a, b) { + t.Errorf("(%s:%d) Expected %v (type %v) - Got %v (type %v)", fn, line, b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} + +func refute(t *testing.T, a interface{}, b interface{}) { + if reflect.DeepEqual(a, b) { + t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) + } +} diff --git a/vendor/github.com/urfave/cli/helpers_unix_test.go b/vendor/github.com/urfave/cli/helpers_unix_test.go new file mode 100644 index 0000000..ae27fc5 --- /dev/null +++ b/vendor/github.com/urfave/cli/helpers_unix_test.go @@ -0,0 +1,9 @@ +// +build darwin dragonfly freebsd linux netbsd openbsd solaris + +package cli + +import "os" + +func clearenv() { + os.Clearenv() +} diff --git a/vendor/github.com/urfave/cli/helpers_windows_test.go b/vendor/github.com/urfave/cli/helpers_windows_test.go new file mode 100644 index 0000000..4eb84f9 --- /dev/null +++ b/vendor/github.com/urfave/cli/helpers_windows_test.go @@ -0,0 +1,20 @@ +package cli + +import ( + "os" + "syscall" +) + +// os.Clearenv() doesn't actually unset variables on Windows +// See: https://github.com/golang/go/issues/17902 +func clearenv() { + for _, s := range os.Environ() { + for j := 1; j < len(s); j++ { + if s[j] == '=' { + keyp, _ := syscall.UTF16PtrFromString(s[0:j]) + syscall.SetEnvironmentVariable(keyp, nil) + break + } + } + } +} 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()) From 1bfd1cedbd36951685173650c7373fcc8f17a438 Mon Sep 17 00:00:00 2001 From: Thomas Boerger Date: Mon, 8 Jan 2018 09:54:30 +0100 Subject: [PATCH 2/3] Applied default structure and added some sugar --- .appveyor.yml | 62 ++++++++ .dockerignore | 2 + .drone.yml | 133 +++++++++++++++++ .github/issue_template.md | 0 .github/pull_request_template.md | 0 .gitignore | 32 +++- Dockerfile | 15 +- Dockerfile.arm | 9 ++ Dockerfile.arm64 | 9 ++ Dockerfile.i386 | 9 ++ Dockerfile.windows | 10 ++ Gopkg.lock | 14 +- Gopkg.toml | 8 + Makefile | 13 -- README.md | 46 ++++-- main.go | 242 ++++++++++++++++++++++--------- plugin.go | 124 ++++++++++++++++ template.go | 137 +++++++++++++++++ 18 files changed, 755 insertions(+), 110 deletions(-) create mode 100644 .appveyor.yml create mode 100644 .dockerignore create mode 100644 .drone.yml create mode 100644 .github/issue_template.md create mode 100644 .github/pull_request_template.md create mode 100644 Dockerfile.arm create mode 100644 Dockerfile.arm64 create mode 100644 Dockerfile.i386 create mode 100644 Dockerfile.windows delete mode 100644 Makefile create mode 100644 plugin.go create mode 100644 template.go diff --git a/.appveyor.yml b/.appveyor.yml new file mode 100644 index 0000000..f4a85ee --- /dev/null +++ b/.appveyor.yml @@ -0,0 +1,62 @@ +version: '{build}' +image: 'Visual Studio 2017' +platform: x64 + +clone_folder: 'c:\go\src\github.com\drone-plugins\drone-matrix' +max_jobs: 1 + +environment: + DOCKER_USERNAME: + secure: '4YzzahbEiMZQJpOCOd1LAw==' + DOCKER_PASSWORD: + secure: 'VqO/G3Zfslu6zSLdwHKO+Q==' + +install: + - ps: | + docker version + go version + +build_script: + - ps: | + if ( $env:APPVEYOR_REPO_TAG -eq 'false' ) { + go build -ldflags "-X main.build=$env:APPVEYOR_BUILD_VERSION" -a -o drone-matrix.exe + } else { + $version = $env:APPVEYOR_REPO_TAG_NAME.substring(1) + go build -ldflags "-X main.version=$version -X main.build=$env:APPVEYOR_BUILD_VERSION" -a -o drone-matrix.exe + } + + docker pull microsoft/nanoserver:10.0.14393.1593 + docker build -f Dockerfile.windows -t plugins/matrix:windows . + +test_script: + - ps: | + docker run --rm plugins/matrix:windows --version + +deploy_script: + - ps: | + $ErrorActionPreference = 'Stop'; + + if ( $env:APPVEYOR_PULL_REQUEST_NUMBER ) { + Write-Host Nothing to deploy. + } else { + docker login --username $env:DOCKER_USERNAME --password $env:DOCKER_PASSWORD + + if ( $env:APPVEYOR_REPO_TAG -eq 'true' ) { + $major,$minor,$patch = $env:APPVEYOR_REPO_TAG_NAME.substring(1).split('.') + + docker push plugins/matrix:windows + + docker tag plugins/matrix:windows plugins/matrix:$major.$minor.$patch-windows + docker push plugins/matrix:$major.$minor.$patch-windows + + docker tag plugins/matrix:windows plugins/matrix:$major.$minor-windows + docker push plugins/matrix:$major.$minor-windows + + docker tag plugins/matrix:windows plugins/matrix:$major-windows + docker push plugins/matrix:$major-windows + } else { + if ( $env:APPVEYOR_REPO_BRANCH -eq 'master' ) { + docker push plugins/matrix:windows + } + } + } diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..c13ca3f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +* +!release/ diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..e6f0f11 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,133 @@ +workspace: + base: /go + path: src/github.com/drone-plugins/drone-matrix + +pipeline: + test: + image: golang:1.9 + pull: true + commands: + - go vet + - | + for PKG in $(go list ./... | grep -v /vendor/); do + go test -cover -coverprofile $PKG/coverage.out $PKG + done + + build_linux_amd64: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=amd64 + - CGO_ENABLED=0 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/amd64/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/amd64/drone-matrix + fi + + build_linux_i386: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=386 + - CGO_ENABLED=0 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/i386/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/i386/drone-matrix + fi + + build_linux_arm64: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=arm64 + - CGO_ENABLED=0 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm64/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm64/drone-matrix + fi + + build_linux_arm: + image: golang:1.9 + pull: true + group: build + environment: + - GOOS=linux + - GOARCH=arm + - CGO_ENABLED=0 + - GOARM=7 + commands: + - | + if test "${DRONE_TAG}" = ""; then + go build -v -ldflags "-X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm/drone-matrix + else + go build -v -ldflags "-X main.version=${DRONE_TAG##v} -X main.build=${DRONE_BUILD_NUMBER}" -a -o release/linux/arm/drone-matrix + fi + + publish_linux_amd64: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + dockerfile: Dockerfile + when: + event: [ push, tag ] + + publish_linux_i386: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + auto_tag_suffix: i386 + dockerfile: Dockerfile.i386 + when: + event: [ push, tag ] + + publish_linux_arm64: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + auto_tag_suffix: arm64 + dockerfile: Dockerfile.arm64 + when: + event: [ push, tag ] + + publish_linux_arm: + image: plugins/docker:17.05 + pull: true + secrets: [ docker_username, docker_password ] + group: docker + repo: plugins/matrix + auto_tag: true + auto_tag_suffix: arm + dockerfile: Dockerfile.arm + when: + event: [ push, tag ] + + microbadger: + image: plugins/webhook:1 + pull: true + secrets: [ webhook_url ] + when: + status: [ success ] diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 0000000..e69de29 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 5b8f9f0..57472fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,28 @@ -*.swp -*~ -/drone-plugin-matrix -/vendor +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +release/ +coverage.out +drone-matrix diff --git a/Dockerfile b/Dockerfile index 927d18e..1429bc1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ -# vim: set ft=dockerfile: -FROM alpine:3.6 -# Author with no obligation to maintain -MAINTAINER Paul Tötterman +FROM plugins/base:multiarch -RUN apk --no-cache add ca-certificates -ADD drone-plugin-matrix / -ENTRYPOINT /drone-plugin-matrix +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/amd64/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.arm b/Dockerfile.arm new file mode 100644 index 0000000..4f10bd9 --- /dev/null +++ b/Dockerfile.arm @@ -0,0 +1,9 @@ +FROM plugins/base:multiarch + +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/arm/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 new file mode 100644 index 0000000..dba8bf6 --- /dev/null +++ b/Dockerfile.arm64 @@ -0,0 +1,9 @@ +FROM plugins/base:multiarch + +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/arm64/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.i386 b/Dockerfile.i386 new file mode 100644 index 0000000..5c515de --- /dev/null +++ b/Dockerfile.i386 @@ -0,0 +1,9 @@ +FROM plugins/base:multiarch + +LABEL maintainer="Drone.IO Community " \ + org.label-schema.name="Drone Matrix" \ + org.label-schema.vendor="Drone.IO Community" \ + org.label-schema.schema-version="1.0" + +ADD release/linux/i386/drone-matrix /bin/ +ENTRYPOINT ["/bin/drone-matrix"] diff --git a/Dockerfile.windows b/Dockerfile.windows new file mode 100644 index 0000000..9de0dbe --- /dev/null +++ b/Dockerfile.windows @@ -0,0 +1,10 @@ +# escape=` +FROM microsoft/nanoserver:10.0.14393.1593 + +LABEL maintainer="Drone.IO Community " ` + org.label-schema.name="Drone Matrix" ` + org.label-schema.vendor="Drone.IO Community" ` + org.label-schema.schema-version="1.0" + +ADD drone-matrix.exe /drone-matrix.exe +ENTRYPOINT [ "\\drone-matrix.exe" ] diff --git a/Gopkg.lock b/Gopkg.lock index 77d6019..e6dcba5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,15 +1,27 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. +[[projects]] + name = "github.com/aymerick/raymond" + packages = [".","ast","lexer","parser"] + revision = "a2232af10b53ef1ae5a767f5178db3a6c1dab655" + version = "v2.0.1" + [[projects]] branch = "master" name = "github.com/matrix-org/gomatrix" packages = ["."] revision = "a7fc80c8060c2544fe5d4dae465b584f8e9b4e27" +[[projects]] + name = "github.com/urfave/cli" + packages = ["."] + revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" + version = "v1.20.0" + [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "d631b7f46070377e77e160dda36075f4421695f6149e974427eafc8458012b3c" + inputs-digest = "02adaa1a61f60449825943c7e92d6d66d4b88b225834f75dc6c181a96836f25b" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 36e6b1d..3edad83 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -21,6 +21,14 @@ # version = "2.4.0" +[[constraint]] + name = "github.com/aymerick/raymond" + version = "2.0.1" + [[constraint]] branch = "master" name = "github.com/matrix-org/gomatrix" + +[[constraint]] + name = "github.com/urfave/cli" + version = "1.20.0" diff --git a/Makefile b/Makefile deleted file mode 100644 index d82484f..0000000 --- a/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -.PHONY: build -build: drone-plugin-matrix - -drone-plugin-matrix: main.go - CGO_ENABLED=0 go build -ldflags '-s -w' - -.PHONY: docker -docker: drone-plugin-matrix - docker build -t drone-plugin-matrix . - -.PHONY: clean -clean: - rm -f drone-plugin-matrix diff --git a/README.md b/README.md index c1098e4..161fb7c 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,37 @@ -# drone-plugin-matrix +# drone-matrix -[Drone](https://drone.io/) notifications to [Matrix](https://matrix.org/) +[![Build Status](http://beta.drone.io/api/badges/drone-plugins/drone-matrix/status.svg)](http://beta.drone.io/drone-plugins/drone-matrix) +[![Join the discussion at https://discourse.drone.io](https://img.shields.io/badge/discourse-forum-orange.svg)](https://discourse.drone.io) +[![Drone questions at https://stackoverflow.com](https://img.shields.io/badge/drone-stackoverflow-orange.svg)](https://stackoverflow.com/questions/tagged/drone.io) +[![Go Doc](https://godoc.org/github.com/drone-plugins/drone-matrix?status.svg)](http://godoc.org/github.com/drone-plugins/drone-matrix) +[![Go Report](https://goreportcard.com/badge/github.com/drone-plugins/drone-matrix)](https://goreportcard.com/report/github.com/drone-plugins/drone-matrix) +[![](https://images.microbadger.com/badges/image/plugins/matrix.svg)](https://microbadger.com/images/plugins/matrix "Get your own image badge on microbadger.com") -Usage: +Drone plugin for sending build notifications to [Matrix](https://matrix.org/). For the usage information and a listing of the available options please take a look at [the docs](http://plugins.drone.io/drone-plugins/drone-matrix/). -```yaml - matrix: - image: ptman/drone-plugin-matrix - homeserver: https://matrix.org # defaults to https://matrix.org - roomid: '!0123456789abcdef:matrix.org' # room has to already be joined - secrets: - - matrix_username # either username ('ourbot') - - matrix_password # and password ('*ourbot-password*') - # - matrix_userid # or userid ('@ourbot:matrix.org') - # - matrix_accesstoken # and access token ('long string of characters') +## Build + +Build the binary with the following commands: + +``` +go build ``` -## License +## Docker -Apache-2.0 +Build the Docker image with the following commands: + +``` +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -tags netgo -o release/linux/amd64/drone-matrix +docker build --rm -t plugins/matrix . +``` + +### Usage + +``` +docker run --rm \ + -e PLUGIN_ROOMID=0123456789abcdef:matrix.org \ + -e PLUGIN_USERNAME=yourbot \ + -e PLUGIN_PASSWORD=p455w0rd \ + plugins/matrix +``` diff --git a/main.go b/main.go index 4fbce1c..6281170 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,3 @@ -// Copyright (c) 2017 Paul Tötterman . All rights reserved. - package main import ( @@ -7,79 +5,183 @@ import ( "log" "os" - "github.com/matrix-org/gomatrix" + "github.com/urfave/cli" +) + +var ( + version = "0.0.0" + build = "0" ) func main() { - // Secrets - password := os.Getenv("MATRIX_PASSWORD") - accessToken := os.Getenv("MATRIX_ACCESSTOKEN") - // Not sure if these are secrets or nice to have close to them - userName := os.Getenv("MATRIX_USERNAME") - userID := os.Getenv("MATRIX_USERID") - - // Override secrets if present - if pw := os.Getenv("PLUGIN_PASSWORD"); pw != "" { - password = pw - } - if at := os.Getenv("PLUGIN_ACCESSTOKEN"); at != "" { - accessToken = at - } - if un := os.Getenv("PLUGIN_USERNAME"); un != "" { - userName = un - } - if ui := os.Getenv("PLUGIN_USERID"); ui != "" { - userID = ui + app := cli.NewApp() + app.Name = "codecov plugin" + app.Usage = "codecov plugin" + app.Version = fmt.Sprintf("%s+%s", version, build) + app.Action = run + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "username", + Usage: "username for authentication", + EnvVar: "PLUGIN_USERNAME,MATRIX_USERNAME", + }, + cli.StringFlag{ + Name: "password", + Usage: "password for authentication", + EnvVar: "PLUGIN_PASSWORD,MATRIX_PASSWORD", + }, + cli.StringFlag{ + Name: "userid", + Usage: "userid for authentication", + EnvVar: "PLUGIN_USERID,PLUGIN_USER_ID,MATRIX_USERID,MATRIX_USER_ID", + }, + cli.StringFlag{ + Name: "accesstoken", + Usage: "accesstoken for authentication", + EnvVar: "PLUGIN_ACCESSTOKEN,PLUGIN_ACCESS_TOKEN,MATRIX_ACCESSTOKEN,MATRIX_ACCESS_TOKEN", + }, + cli.StringFlag{ + Name: "homeserver", + Value: "https://matrix.org", + Usage: "matrix home server", + EnvVar: "PLUGIN_HOMESERVER,MATRIX_HOMESERVER", + }, + cli.StringFlag{ + Name: "roomid", + Usage: "roomid to send messages", + EnvVar: "PLUGIN_ROOMID,MATRIX_ROOMID", + }, + cli.StringFlag{ + Name: "template", + Usage: "template for the message", + EnvVar: "PLUGIN_TEMPLATE,MATRIX_TEMPLATE", + }, + cli.StringFlag{ + Name: "repo.owner", + Usage: "repository owner", + EnvVar: "DRONE_REPO_OWNER", + }, + cli.StringFlag{ + Name: "repo.name", + Usage: "repository name", + EnvVar: "DRONE_REPO_NAME", + }, + cli.StringFlag{ + Name: "commit.sha", + Usage: "git commit sha", + EnvVar: "DRONE_COMMIT_SHA", + Value: "00000000", + }, + cli.StringFlag{ + Name: "commit.ref", + Value: "refs/heads/master", + Usage: "git commit ref", + EnvVar: "DRONE_COMMIT_REF", + }, + cli.StringFlag{ + Name: "commit.branch", + Value: "master", + Usage: "git commit branch", + EnvVar: "DRONE_COMMIT_BRANCH", + }, + cli.StringFlag{ + Name: "commit.author", + Usage: "git author name", + EnvVar: "DRONE_COMMIT_AUTHOR", + }, + cli.StringFlag{ + Name: "commit.message", + Usage: "commit message", + EnvVar: "DRONE_COMMIT_MESSAGE", + }, + cli.StringFlag{ + Name: "build.event", + Value: "push", + Usage: "build event", + EnvVar: "DRONE_BUILD_EVENT", + }, + cli.IntFlag{ + Name: "build.number", + Usage: "build number", + EnvVar: "DRONE_BUILD_NUMBER", + }, + cli.StringFlag{ + Name: "build.status", + Usage: "build status", + Value: "success", + EnvVar: "DRONE_BUILD_STATUS", + }, + cli.StringFlag{ + Name: "build.link", + Usage: "build link", + EnvVar: "DRONE_BUILD_LINK", + }, + cli.Int64Flag{ + Name: "build.started", + Usage: "build started", + EnvVar: "DRONE_BUILD_STARTED", + }, + cli.Int64Flag{ + Name: "build.created", + Usage: "build created", + EnvVar: "DRONE_BUILD_CREATED", + }, + cli.StringFlag{ + Name: "build.tag", + Usage: "build tag", + EnvVar: "DRONE_TAG", + }, + cli.StringFlag{ + Name: "build.deployTo", + Usage: "environment deployed to", + EnvVar: "DRONE_DEPLOY_TO", + }, + cli.Int64Flag{ + Name: "job.started", + Usage: "job started", + EnvVar: "DRONE_JOB_STARTED", + }, } - homeServer := os.Getenv("PLUGIN_HOMESERVER") - if homeServer == "" { - homeServer = "https://matrix.org" - } - - // TODO: resolve room aliases - roomID := os.Getenv("PLUGIN_ROOMID") - message := os.Getenv("PLUGIN_MESSAGE") - - repoOwner := os.Getenv("DRONE_REPO_OWNER") - repoName := os.Getenv("DRONE_REPO_NAME") - - buildStatus := os.Getenv("DRONE_BUILD_STATUS") - buildLink := os.Getenv("DRONE_BUILD_LINK") - buildBranch := os.Getenv("DRONE_BRANCH") - buildAuthor := os.Getenv("DRONE_COMMIT_AUTHOR") - buildCommit := os.Getenv("DRONE_COMMIT") - - m, err := gomatrix.NewClient(homeServer, userID, accessToken) - if err != nil { - log.Fatal(err) - } - - if userID == "" || accessToken == "" { - r, err := m.Login(&gomatrix.ReqLogin{ - Type: "m.login.password", - User: userName, - Password: password, - InitialDeviceDisplayName: "Drone", - }) - if err != nil { - log.Fatal(err) - } - m.SetCredentials(r.UserID, r.AccessToken) - } - - if message == "" { - message = fmt.Sprintf("Build %s <%s> %s/%s#%s (%s) by %s", - buildStatus, - buildLink, - repoOwner, - repoName, - buildCommit[:8], - buildBranch, - buildAuthor) - } - - if _, err := m.SendNotice(roomID, message); err != nil { + if err := app.Run(os.Args); err != nil { log.Fatal(err) } } + +func run(c *cli.Context) error { + 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"), + DeployTo: c.String("build.deployTo"), + Link: c.String("build.link"), + Started: c.Int64("build.started"), + Created: c.Int64("build.created"), + }, + Job: Job{ + Started: c.Int64("job.started"), + }, + Config: Config{ + Username: c.String("username"), + Password: c.String("password"), + UserID: c.String("userid"), + AccessToken: c.String("accesstoken"), + Homeserver: c.String("homeserver"), + RoomID: c.String("roomid"), + Template: c.String("template"), + }, + } + + return plugin.Exec() +} diff --git a/plugin.go b/plugin.go new file mode 100644 index 0000000..d1e1ccf --- /dev/null +++ b/plugin.go @@ -0,0 +1,124 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/matrix-org/gomatrix" +) + +type ( + Repo struct { + Owner string + Name string + } + + Build struct { + Tag string + Event string + Number int + Commit string + Ref string + Branch string + Author string + Message string + DeployTo string + Status string + Link string + Started int64 + Created int64 + } + + Job struct { + Started int64 + } + + Config struct { + Username string + Password string + UserID string + AccessToken string + Homeserver string + RoomID string + Template string + } + + Plugin struct { + Repo Repo + Build Build + Job Job + Config Config + } +) + +func (p Plugin) Exec() error { + m, err := gomatrix.NewClient(p.Config.Homeserver, prepend("@", p.Config.UserID), p.Config.AccessToken) + + if err != nil { + return err + } + + if p.Config.UserID == "" || p.Config.AccessToken == "" { + r, err := m.Login(&gomatrix.ReqLogin{ + Type: "m.login.password", + User: p.Config.Username, + Password: p.Config.Password, + InitialDeviceDisplayName: "Drone", + }) + + if err != nil { + return err + } + + m.SetCredentials(r.UserID, r.AccessToken) + } + + joined, err := m.JoinRoom(p.Config.RoomID, "", nil) + + if err != nil { + return err + } + + message := message(p.Repo, p.Build) + + if p.Config.Template != "" { + if message, err = RenderTrim(p.Config.Template, p); err != nil { + return err + } + + if err != nil { + return err + } + } + + if _, err := m.SendNotice(joined.RoomID, message); err != nil { + return err + } + + return nil +} + +func message(repo Repo, build Build) string { + return fmt.Sprintf( + "Build %s <%s|%s/%s#%s> (%s) by %s", + build.Status, + build.Link, + repo.Owner, + repo.Name, + build.Commit[:8], + build.Branch, + build.Author, + ) +} + +func prepend(prefix, s string) string { + if s == "" { + return s + } + + if strings.HasPrefix(s, prefix) { + return s + } + + return prefix + s +} 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))) +} From 1df5f7d4e7c6c310ae2bc8eafc2a2afc903a052a Mon Sep 17 00:00:00 2001 From: Thomas Boerger Date: Tue, 9 Jan 2018 11:22:43 +0100 Subject: [PATCH 3/3] Added manifest definition --- .drone.yml | 11 +++++++++++ manifest.tmpl | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 manifest.tmpl diff --git a/.drone.yml b/.drone.yml index e6f0f11..de2e203 100644 --- a/.drone.yml +++ b/.drone.yml @@ -85,6 +85,7 @@ pipeline: group: docker repo: plugins/matrix auto_tag: true + auto_tag_suffix: amd64 dockerfile: Dockerfile when: event: [ push, tag ] @@ -125,6 +126,16 @@ pipeline: when: event: [ push, tag ] + manifests: + image: plugins/manifest:1 + pull: true + secrets: [ docker_username, docker_password ] + spec: manifest.tmpl + auto_tag: true + ignore_missing: true + when: + event: [ push, tag ] + microbadger: image: plugins/webhook:1 pull: true diff --git a/manifest.tmpl b/manifest.tmpl new file mode 100644 index 0000000..b3013a7 --- /dev/null +++ b/manifest.tmpl @@ -0,0 +1,33 @@ +image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} +{{/if}} +manifests: + - + image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}amd64 + platform: + architecture: amd64 + os: linux + - + image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}i386 + platform: + architecture: 386 + os: linux + - + image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}arm64 + platform: + architecture: arm64 + os: linux + - + image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}arm + platform: + architecture: arm + os: linux + - + image: plugins/matrix:{{#if build.tag}}{{trimPrefix build.tag "v"}}-{{/if}}windows + platform: + architecture: amd64 + os: windows